<commit_before>// Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree.

// Package collider implements a signaling server based on WebSocket.
package collider

import (
	"crypto/tls"
	"golang.org/x/net/websocket"
	"encoding/json"
	"errors"
	"html"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"strconv"
	"strings"
	"time"
)

const registerTimeoutSec = 10

// This is a temporary solution to avoid holding a zombie connection forever, by
// setting a 1 day timeout on reading from the WebSocket connection.
const wsReadTimeoutSec = 60 * 60 * 24

type Collider struct {
	*roomTable
	dash *dashboard
}

func NewCollider(rs string) *Collider {
	return &Collider{
		roomTable: newRoomTable(time.Second*registerTimeoutSec, rs),
		dash:      newDashboard(),
	}
}

// Run starts the collider server and blocks the thread until the program exits.
func (c *Collider) Run(p int, useTls bool) {
	http.Handle("/ws", websocket.Handler(c.wsHandler))
	http.HandleFunc("/status", c.httpStatusHandler)
	http.HandleFunc("/", c.httpHandler)

	var e error

	pstr := ":" + strconv.Itoa(p)
	if useTls {
		config := &tls.Config {
			// Only allow ciphers that support forward secrecy for iOS9 compatibility:
			// https://developer.apple.com/library/prerelease/ios/technotes/App-Transport-Security-Technote/
			CipherSuites: []uint16 {
				tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
				tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
				tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
				tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
			},
			PreferServerCipherSuites: true,
		}
		server := &http.Server{ Addr: pstr, Handler: nil, TLSConfig: config }

		e = server.ListenAndServeTLS("/cert/cert.pem", "/cert/key.pem")
	} else {
		e = http.ListenAndServe(pstr, nil)
	}

	if e != nil {
		log.Fatal("Run: " + e.Error())
	}
}

// httpStatusHandler is a HTTP handler that handles GET requests to get the
// status of collider.
func (c *Collider) httpStatusHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Access-Control-Allow-Origin", "*")
	w.Header().Add("Access-Control-Allow-Methods", "GET")

	rp := c.dash.getReport(c.roomTable)
	enc := json.NewEncoder(w)
	if err := enc.Encode(rp); err != nil {
		err = errors.New("Failed to encode to JSON: err=" + err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		c.dash.onHttpErr(err)
	}
}

// httpHandler is a HTTP handler that handles GET/POST/DELETE requests.
// POST request to path "/$ROOMID/$CLIENTID" is used to send a message to the other client of the room.
// $CLIENTID is the source client ID.
// The request must have a form value "msg", which is the message to send.
// DELETE request to path "/$ROOMID/$CLIENTID" is used to delete all records of a client, including the queued message from the client.
// "OK" is returned if the request is valid.
func (c *Collider) httpHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Access-Control-Allow-Origin", "*")
	w.Header().Add("Access-Control-Allow-Methods", "POST, DELETE")

	p := strings.Split(r.URL.Path, "/")
	if len(p) != 3 {
		c.httpError("Invalid path: "+html.EscapeString(r.URL.Path), w)
		return
	}
	rid, cid := p[1], p[2]

	switch r.Method {
	case "POST":
		body, err := ioutil.ReadAll(r.Body)
		if err != nil {
			c.httpError("Failed to read request body: "+err.Error(), w)
			return
		}
		m := string(body)
		if m == "" {
			c.httpError("Empty request body", w)
			return
		}
		if err := c.roomTable.send(rid, cid, m); err != nil {
			c.httpError("Failed to send the message: "+err.Error(), w)
			return
		}
	case "DELETE":
		c.roomTable.remove(rid, cid)
	default:
		return
	}

	io.WriteString(w, "OK\n")
}

// wsHandler is a WebSocket server that handles requests from the WebSocket client in the form of:
// 1. { 'cmd': 'register', 'roomid': $ROOM, 'clientid': $CLIENT' },
// which binds the WebSocket client to a client ID and room ID.
// A client should send this message only once right after the connection is open.
// or
// 2. { 'cmd': 'send', 'msg': $MSG }, which sends the message to the other client of the room.
// It should be sent to the server only after 'regiser' has been sent.
// The message may be cached by the server if the other client has not joined.
//
// Unexpected messages will cause the WebSocket connection to be closed.
func (c *Collider) wsHandler(ws *websocket.Conn) {
	var rid, cid string

	registered := false

	var msg wsClientMsg
loop:
	for {
		err := ws.SetReadDeadline(time.Now().Add(time.Duration(wsReadTimeoutSec) * time.Second))
		if err != nil {
			c.wsError("ws.SetReadDeadline error: "+err.Error(), ws)
			break
		}

		err = websocket.JSON.Receive(ws, &msg)
		if err != nil {
			if err.Error() != "EOF" {
				c.wsError("websocket.JSON.Receive error: "+err.Error(), ws)
			}
			break
		}

		switch msg.Cmd {
		case "register":
			if registered {
				c.wsError("Duplicated register request", ws)
				break loop
			}
			if msg.RoomID == "" || msg.ClientID == "" {
				c.wsError("Invalid register request: missing 'clientid' or 'roomid'", ws)
				break loop
			}
			if err = c.roomTable.register(msg.RoomID, msg.ClientID, ws); err != nil {
				c.wsError(err.Error(), ws)
				break loop
			}
			registered, rid, cid = true, msg.RoomID, msg.ClientID
			c.dash.incrWs()

			defer c.roomTable.deregister(rid, cid)
			break
		case "send":
			if !registered {
				c.wsError("Client not registered", ws)
				break loop
			}
			if msg.Msg == "" {
				c.wsError("Invalid send request: missing 'msg'", ws)
				break loop
			}
			c.roomTable.send(rid, cid, msg.Msg)
			break
		default:
			c.wsError("Invalid message: unexpected 'cmd'", ws)
			break
		}
	}
	// This should be unnecessary but just be safe.
	ws.Close()
}

func (c *Collider) httpError(msg string, w http.ResponseWriter) {
	err := errors.New(msg)
	http.Error(w, err.Error(), http.StatusInternalServerError)
	c.dash.onHttpErr(err)
}

func (c *Collider) wsError(msg string, ws *websocket.Conn) {
	err := errors.New(msg)
	sendServerErr(ws, msg)
	c.dash.onWsErr(err)
}
<commit_msg>Adding back TLS1.x ciphers to increase compatability<commit_after>// Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree.

// Package collider implements a signaling server based on WebSocket.
package collider

import (
	"crypto/tls"
	"golang.org/x/net/websocket"
	"encoding/json"
	"errors"
	"html"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"strconv"
	"strings"
	"time"
)

const registerTimeoutSec = 10

// This is a temporary solution to avoid holding a zombie connection forever, by
// setting a 1 day timeout on reading from the WebSocket connection.
const wsReadTimeoutSec = 60 * 60 * 24

type Collider struct {
	*roomTable
	dash *dashboard
}

func NewCollider(rs string) *Collider {
	return &Collider{
		roomTable: newRoomTable(time.Second*registerTimeoutSec, rs),
		dash:      newDashboard(),
	}
}

// Run starts the collider server and blocks the thread until the program exits.
func (c *Collider) Run(p int, useTls bool) {
	http.Handle("/ws", websocket.Handler(c.wsHandler))
	http.HandleFunc("/status", c.httpStatusHandler)
	http.HandleFunc("/", c.httpHandler)

	var e error

	pstr := ":" + strconv.Itoa(p)
	if useTls {
		config := &tls.Config {
			// Only allow ciphers that support forward secrecy for iOS9 compatibility:
			// https://developer.apple.com/library/prerelease/ios/technotes/App-Transport-Security-Technote/
			CipherSuites: []uint16 {
				tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
				tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
				tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
				tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
				tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
				tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
				tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
			},
			PreferServerCipherSuites: true,
		}
		server := &http.Server{ Addr: pstr, Handler: nil, TLSConfig: config }

		e = server.ListenAndServeTLS("/cert/cert.pem", "/cert/key.pem")
	} else {
		e = http.ListenAndServe(pstr, nil)
	}

	if e != nil {
		log.Fatal("Run: " + e.Error())
	}
}

// httpStatusHandler is a HTTP handler that handles GET requests to get the
// status of collider.
func (c *Collider) httpStatusHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Access-Control-Allow-Origin", "*")
	w.Header().Add("Access-Control-Allow-Methods", "GET")

	rp := c.dash.getReport(c.roomTable)
	enc := json.NewEncoder(w)
	if err := enc.Encode(rp); err != nil {
		err = errors.New("Failed to encode to JSON: err=" + err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		c.dash.onHttpErr(err)
	}
}

// httpHandler is a HTTP handler that handles GET/POST/DELETE requests.
// POST request to path "/$ROOMID/$CLIENTID" is used to send a message to the other client of the room.
// $CLIENTID is the source client ID.
// The request must have a form value "msg", which is the message to send.
// DELETE request to path "/$ROOMID/$CLIENTID" is used to delete all records of a client, including the queued message from the client.
// "OK" is returned if the request is valid.
func (c *Collider) httpHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Access-Control-Allow-Origin", "*")
	w.Header().Add("Access-Control-Allow-Methods", "POST, DELETE")

	p := strings.Split(r.URL.Path, "/")
	if len(p) != 3 {
		c.httpError("Invalid path: "+html.EscapeString(r.URL.Path), w)
		return
	}
	rid, cid := p[1], p[2]

	switch r.Method {
	case "POST":
		body, err := ioutil.ReadAll(r.Body)
		if err != nil {
			c.httpError("Failed to read request body: "+err.Error(), w)
			return
		}
		m := string(body)
		if m == "" {
			c.httpError("Empty request body", w)
			return
		}
		if err := c.roomTable.send(rid, cid, m); err != nil {
			c.httpError("Failed to send the message: "+err.Error(), w)
			return
		}
	case "DELETE":
		c.roomTable.remove(rid, cid)
	default:
		return
	}

	io.WriteString(w, "OK\n")
}

// wsHandler is a WebSocket server that handles requests from the WebSocket client in the form of:
// 1. { 'cmd': 'register', 'roomid': $ROOM, 'clientid': $CLIENT' },
// which binds the WebSocket client to a client ID and room ID.
// A client should send this message only once right after the connection is open.
// or
// 2. { 'cmd': 'send', 'msg': $MSG }, which sends the message to the other client of the room.
// It should be sent to the server only after 'regiser' has been sent.
// The message may be cached by the server if the other client has not joined.
//
// Unexpected messages will cause the WebSocket connection to be closed.
func (c *Collider) wsHandler(ws *websocket.Conn) {
	var rid, cid string

	registered := false

	var msg wsClientMsg
loop:
	for {
		err := ws.SetReadDeadline(time.Now().Add(time.Duration(wsReadTimeoutSec) * time.Second))
		if err != nil {
			c.wsError("ws.SetReadDeadline error: "+err.Error(), ws)
			break
		}

		err = websocket.JSON.Receive(ws, &msg)
		if err != nil {
			if err.Error() != "EOF" {
				c.wsError("websocket.JSON.Receive error: "+err.Error(), ws)
			}
			break
		}

		switch msg.Cmd {
		case "register":
			if registered {
				c.wsError("Duplicated register request", ws)
				break loop
			}
			if msg.RoomID == "" || msg.ClientID == "" {
				c.wsError("Invalid register request: missing 'clientid' or 'roomid'", ws)
				break loop
			}
			if err = c.roomTable.register(msg.RoomID, msg.ClientID, ws); err != nil {
				c.wsError(err.Error(), ws)
				break loop
			}
			registered, rid, cid = true, msg.RoomID, msg.ClientID
			c.dash.incrWs()

			defer c.roomTable.deregister(rid, cid)
			break
		case "send":
			if !registered {
				c.wsError("Client not registered", ws)
				break loop
			}
			if msg.Msg == "" {
				c.wsError("Invalid send request: missing 'msg'", ws)
				break loop
			}
			c.roomTable.send(rid, cid, msg.Msg)
			break
		default:
			c.wsError("Invalid message: unexpected 'cmd'", ws)
			break
		}
	}
	// This should be unnecessary but just be safe.
	ws.Close()
}

func (c *Collider) httpError(msg string, w http.ResponseWriter) {
	err := errors.New(msg)
	http.Error(w, err.Error(), http.StatusInternalServerError)
	c.dash.onHttpErr(err)
}

func (c *Collider) wsError(msg string, ws *websocket.Conn) {
	err := errors.New(msg)
	sendServerErr(ws, msg)
	c.dash.onWsErr(err)
}
<|endoftext|>


<commit_before>package catalog

import (
	"fmt"
	"log"
	"sync"

	"github.com/dnaeon/gru/graph"
	"github.com/dnaeon/gru/resource"
	"github.com/dnaeon/gru/utils"
	"github.com/layeh/gopher-luar"
	"github.com/yuin/gopher-lua"
)

// Catalog type contains a collection of resources
type Catalog struct {
	// Unsorted contains the list of resources created by Lua
	Unsorted []resource.Resource `luar:"-"`

	// Collection contains the unsorted resources as a collection
	collection resource.Collection `luar:"-"`

	// Sorted contains the resources after a topological sort.
	sorted []*graph.Node `luar:"-"`

	// Reversed contains the resource dependency graph in reverse
	// order. It is used for finding the reverse dependencies of
	// resources.
	reversed *graph.Graph `luar:"-"`

	// Status contains status information about resources
	status *status `luar:"-"`

	// Configuration settings
	config *Config `luar:"-"`
}

// Config type represents a set of settings to use when
// creating and processing the catalog
type Config struct {
	// Name of the Lua module to load and execute
	Module string

	// Do not take any actions, just report what would be done
	DryRun bool

	// Writer used to log events
	Logger *log.Logger

	// Path to the site repo containing module and data files
	SiteRepo string

	// The Lua state
	L *lua.LState

	// Number of goroutines to use for concurrent processing
	Concurrency int
}

// status type contains status information about processed resources
type status struct {
	sync.RWMutex

	// Items contain the result of resource processing and any
	// errors that might have occurred during processing.
	// Keys of the map are the resource ids and their
	// values are the errors returned by resources.
	items map[string]error
}

// set sets the status for a resource
func (s *status) set(id string, err error) {
	s.Lock()
	defer s.Unlock()
	s.items[id] = err
}

// get retrieves the status of a resource
func (s *status) get(id string) (error, bool) {
	s.Lock()
	defer s.Unlock()
	err, ok := s.items[id]

	return err, ok
}

// New creates a new empty catalog with the provided configuration
func New(config *Config) *Catalog {
	c := &Catalog{
		config:     config,
		collection: make(resource.Collection),
		sorted:     make([]*graph.Node, 0),
		reversed:   graph.New(),
		status: &status{
			items: make(map[string]error),
		},
		Unsorted: make([]resource.Resource, 0),
	}

	// Inject the configuration for resources
	resource.DefaultConfig = &resource.Config{
		Logger:   config.Logger,
		SiteRepo: config.SiteRepo,
	}

	// Register the catalog type in Lua and also register
	// metamethods for the catalog, so that we can use
	// the catalog in a more Lua-friendly way
	mt := luar.MT(config.L, c)
	mt.RawSetString("__len", luar.New(config.L, (*Catalog).luaLen))
	config.L.SetGlobal("catalog", luar.New(config.L, c))

	return c
}

// Add adds a resource to the catalog.
// This method is called from Lua when adding new resources
func (c *Catalog) Add(resources ...resource.Resource) {
	for _, r := range resources {
		if r != nil {
			c.Unsorted = append(c.Unsorted, r)
		}
	}
}

// Load loads resources into the catalog
func (c *Catalog) Load() error {
	// Register the resource providers and catalog in Lua
	resource.LuaRegisterBuiltin(c.config.L)
	if err := c.config.L.DoFile(c.config.Module); err != nil {
		return err
	}

	// Perform a topological sort of the resources
	collection, err := resource.CreateCollection(c.Unsorted)
	if err != nil {
		return err
	}

	collectionGraph, err := collection.DependencyGraph()
	if err != nil {
		return err
	}

	reversed := collectionGraph.Reversed()

	sorted, err := collectionGraph.Sort()
	if err != nil {
		return err
	}

	// Set catalog fields
	c.collection = collection
	c.sorted = sorted
	c.reversed = reversed

	c.config.Logger.Printf("Loaded %d resources\n", len(c.sorted))

	return nil
}

// Run processes the resources from catalog
func (c *Catalog) Run() error {
	// process executes a single resource
	process := func(r resource.Resource) {
		id := r.ID()
		err := c.execute(r)
		c.status.set(id, err)
		if err != nil {
			c.config.Logger.Printf("%s %s\n", id, err)
		}
	}

	// Start goroutines for concurrent processing
	var wg sync.WaitGroup
	ch := make(chan resource.Resource, 1024)
	c.config.Logger.Printf("Starting %d goroutines for concurrent processing\n", c.config.Concurrency)
	for i := 0; i < c.config.Concurrency; i++ {
		wg.Add(1)
		worker := func() {
			defer wg.Done()
			for r := range ch {
				c.config.Logger.Printf("%s is concurrent", r.ID())
				process(r)
			}
		}
		go worker()
	}

	// Process the resources
	for _, node := range c.sorted {
		r := c.collection[node.Name]
		switch {
		// Resource is concurrent and has no forward or reverse dependencies
		case r.IsConcurrent() && len(r.Dependencies()) == 0 && len(c.reversed.Nodes[r.ID()].Edges) == 0:
			ch <- r
			continue
		// Resource is concurrent and has no reverse dependencies
		case r.IsConcurrent() && len(c.reversed.Nodes[r.ID()].Edges) == 0:
			ch <- r
			continue
		// Resource is not concurrent
		default:
			process(r)
		}
	}

	close(ch)
	wg.Wait()

	// Print summary report
	if !c.config.DryRun {
		failed := 0
		successful := 0
		for _, err := range c.status.items {
			if err != nil {
				failed++
			} else {
				successful++
			}
		}
		c.config.Logger.Printf("Applied %d resources, %d of which have failed and %d have succeeded\n", len(c.sorted), failed, successful)
	}

	return nil
}

// execute processes a single resource
func (c *Catalog) execute(r resource.Resource) error {
	// Check if the resource has failed dependencies
	for _, dep := range r.Dependencies() {
		if err, _ := c.status.get(dep); err != nil {
			return fmt.Errorf("failed dependency for %s", dep)
		}
	}

	if err := r.Validate(); err != nil {
		return err
	}

	state, err := r.Evaluate()
	if err != nil {
		return err
	}

	if c.config.DryRun {
		return nil
	}

	// Current and wanted states for the resource
	want := utils.NewString(state.Want)
	current := utils.NewString(state.Current)

	// The list of present and absent states for the resource
	present := utils.NewList(r.GetPresentStates()...)
	absent := utils.NewList(r.GetAbsentStates()...)

	id := r.ID()
	var action func() error
	switch {
	case want.IsInList(present) && current.IsInList(absent):
		action = r.Create
		c.config.Logger.Printf("%s is %s, should be %s\n", id, current, want)
	case want.IsInList(absent) && current.IsInList(present):
		action = r.Delete
		c.config.Logger.Printf("%s is %s, should be %s\n", id, current, want)
	case state.Outdated:
		action = r.Update
		c.config.Logger.Printf("%s is out of date\n", id)
	}

	if action != nil {
		return action()
	}

	return nil
}

// luaLen returns the number of unsorted resources in catalog.
// This method is called from Lua.
func (c *Catalog) luaLen() int {
	return len(c.Unsorted)
}
<commit_msg>catalog: comment updates<commit_after>package catalog

import (
	"fmt"
	"log"
	"sync"

	"github.com/dnaeon/gru/graph"
	"github.com/dnaeon/gru/resource"
	"github.com/dnaeon/gru/utils"
	"github.com/layeh/gopher-luar"
	"github.com/yuin/gopher-lua"
)

// Catalog type contains a collection of resources
type Catalog struct {
	// Unsorted contains the list of resources created by Lua
	Unsorted []resource.Resource `luar:"-"`

	// Collection contains the unsorted resources as a collection
	collection resource.Collection `luar:"-"`

	// Sorted contains the resources after a topological sort.
	sorted []*graph.Node `luar:"-"`

	// Reversed contains the resource dependency graph in reverse
	// order. It is used for finding the reverse dependencies of
	// resources.
	reversed *graph.Graph `luar:"-"`

	// Status contains status information about resources
	status *status `luar:"-"`

	// Configuration settings
	config *Config `luar:"-"`
}

// Config type represents a set of settings to use when
// creating and processing the catalog
type Config struct {
	// Name of the Lua module to load and execute
	Module string

	// Do not take any actions, just report what would be done
	DryRun bool

	// Writer used to log events
	Logger *log.Logger

	// Path to the site repo containing module and data files
	SiteRepo string

	// The Lua state
	L *lua.LState

	// Number of goroutines to use for concurrent processing
	Concurrency int
}

// status type contains status information about processed resources
type status struct {
	sync.RWMutex

	// Items contain the result of resource processing and any
	// errors that might have occurred during processing.
	// Keys of the map are the resource ids and their
	// values are the errors returned by resources.
	items map[string]error
}

// set sets the status for a resource
func (s *status) set(id string, err error) {
	s.Lock()
	defer s.Unlock()
	s.items[id] = err
}

// get retrieves the status of a resource
func (s *status) get(id string) (error, bool) {
	s.Lock()
	defer s.Unlock()
	err, ok := s.items[id]

	return err, ok
}

// New creates a new empty catalog with the provided configuration
func New(config *Config) *Catalog {
	c := &Catalog{
		config:     config,
		collection: make(resource.Collection),
		sorted:     make([]*graph.Node, 0),
		reversed:   graph.New(),
		status: &status{
			items: make(map[string]error),
		},
		Unsorted: make([]resource.Resource, 0),
	}

	// Inject the configuration for resources
	resource.DefaultConfig = &resource.Config{
		Logger:   config.Logger,
		SiteRepo: config.SiteRepo,
	}

	// Register the catalog type in Lua and also register
	// metamethods for the catalog, so that we can use
	// the catalog in a more Lua-friendly way
	mt := luar.MT(config.L, c)
	mt.RawSetString("__len", luar.New(config.L, (*Catalog).luaLen))
	config.L.SetGlobal("catalog", luar.New(config.L, c))

	return c
}

// Add adds a resource to the catalog.
// This method is called from Lua when adding new resources
func (c *Catalog) Add(resources ...resource.Resource) {
	for _, r := range resources {
		if r != nil {
			c.Unsorted = append(c.Unsorted, r)
		}
	}
}

// Load loads resources into the catalog
func (c *Catalog) Load() error {
	// Register the resource providers and catalog in Lua
	resource.LuaRegisterBuiltin(c.config.L)
	if err := c.config.L.DoFile(c.config.Module); err != nil {
		return err
	}

	// Perform a topological sort of the resources
	collection, err := resource.CreateCollection(c.Unsorted)
	if err != nil {
		return err
	}

	collectionGraph, err := collection.DependencyGraph()
	if err != nil {
		return err
	}

	reversed := collectionGraph.Reversed()

	sorted, err := collectionGraph.Sort()
	if err != nil {
		return err
	}

	// Set catalog fields
	c.collection = collection
	c.sorted = sorted
	c.reversed = reversed

	c.config.Logger.Printf("Loaded %d resources\n", len(c.sorted))

	return nil
}

// Run processes the resources from catalog
func (c *Catalog) Run() error {
	// process executes a single resource
	process := func(r resource.Resource) {
		id := r.ID()
		err := c.execute(r)
		c.status.set(id, err)
		if err != nil {
			c.config.Logger.Printf("%s %s\n", id, err)
		}
	}

	// Start goroutines for concurrent processing
	var wg sync.WaitGroup
	ch := make(chan resource.Resource, 1024)
	c.config.Logger.Printf("Starting %d goroutines for concurrent processing\n", c.config.Concurrency)
	for i := 0; i < c.config.Concurrency; i++ {
		wg.Add(1)
		worker := func() {
			defer wg.Done()
			for r := range ch {
				c.config.Logger.Printf("%s is concurrent", r.ID())
				process(r)
			}
		}
		go worker()
	}

	// Process the resources
	for _, node := range c.sorted {
		r := c.collection[node.Name]
		switch {
		// Resource is concurrent and is an isolated node
		case r.IsConcurrent() && len(r.Dependencies()) == 0 && len(c.reversed.Nodes[r.ID()].Edges) == 0:
			ch <- r
			continue
		// Resource is concurrent and has no reverse dependencies
		case r.IsConcurrent() && len(c.reversed.Nodes[r.ID()].Edges) == 0:
			ch <- r
			continue
		// Resource is not concurrent
		default:
			process(r)
		}
	}

	close(ch)
	wg.Wait()

	// Print summary report
	if !c.config.DryRun {
		failed := 0
		successful := 0
		for _, err := range c.status.items {
			if err != nil {
				failed++
			} else {
				successful++
			}
		}
		c.config.Logger.Printf("Applied %d resources, %d of which have failed and %d have succeeded\n", len(c.sorted), failed, successful)
	}

	return nil
}

// execute processes a single resource
func (c *Catalog) execute(r resource.Resource) error {
	// Check if the resource has failed dependencies
	for _, dep := range r.Dependencies() {
		if err, _ := c.status.get(dep); err != nil {
			return fmt.Errorf("failed dependency for %s", dep)
		}
	}

	if err := r.Validate(); err != nil {
		return err
	}

	state, err := r.Evaluate()
	if err != nil {
		return err
	}

	if c.config.DryRun {
		return nil
	}

	// Current and wanted states for the resource
	want := utils.NewString(state.Want)
	current := utils.NewString(state.Current)

	// The list of present and absent states for the resource
	present := utils.NewList(r.GetPresentStates()...)
	absent := utils.NewList(r.GetAbsentStates()...)

	id := r.ID()
	var action func() error
	switch {
	case want.IsInList(present) && current.IsInList(absent):
		action = r.Create
		c.config.Logger.Printf("%s is %s, should be %s\n", id, current, want)
	case want.IsInList(absent) && current.IsInList(present):
		action = r.Delete
		c.config.Logger.Printf("%s is %s, should be %s\n", id, current, want)
	case state.Outdated:
		action = r.Update
		c.config.Logger.Printf("%s is out of date\n", id)
	}

	if action != nil {
		return action()
	}

	return nil
}

// luaLen returns the number of unsorted resources in catalog.
// This method is called from Lua.
func (c *Catalog) luaLen() int {
	return len(c.Unsorted)
}
<|endoftext|>


<commit_before>package wiki
<commit_msg>DisplayImage<commit_after>package wiki

// DisplayImage represents an image to display.
type DisplayImage struct {

	// basename of the scaled image file
	File string

	// absolute path to the scaled image.
	// this file should be served to the user
	Path string

	// absolute path to the full-size image.
	// if the full-size image is being displayed, same as Path
	FullsizePath string

	// image type
	// 'png' or 'jpeg'
	ImageType string

	// mime 'image/png' or 'image/jpeg'
	// suitable for the Content-Type header
	Mime string

	// binary image data
	// omitted with dont_open option
	Content string

	// bytelength of image data
	// suitable for use in the Content-Length header
	Length int64

	// UNIX timestamp of when the image was last modified.
	// if Generated is true, this is the current time.
	// if FromCache is true, this is the modified date of the cache file.
	// otherwise, this is the modified date of the image file itself.
	ModUnix int64

	// like ModUnix except in HTTP date format
	// suitable for Last-Modified header
	Modified string

	// true if the content being sered was read from a cache file.
	// opposite of Generated
	FromCache bool

	// true if the content being served was just generated.
	// opposite of FromCache
	Generated bool

	// true if the content generated in order to fulfill this request was
	// written to cache. this can only been true when Generated is true
	CacheGenerated bool
}
<|endoftext|>


<commit_before>/*
Copyright 2018 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package commands

import (
	"os"
	"os/exec"
	"strconv"
	"strings"
	"syscall"

	"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
	"github.com/GoogleContainerTools/kaniko/pkg/util"
	"github.com/docker/docker/builder/dockerfile/instructions"
	"github.com/google/go-containerregistry/pkg/v1"
	"github.com/sirupsen/logrus"
)

type RunCommand struct {
	cmd *instructions.RunCommand
}

func (r *RunCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error {
	var newCommand []string
	if r.cmd.PrependShell {
		// This is the default shell on Linux
		var shell []string
		if len(config.Shell) > 0 {
			shell = config.Shell
		} else {
			shell = append(shell, "/bin/sh", "-c")
		}

		newCommand = append(shell, strings.Join(r.cmd.CmdLine, " "))
	} else {
		newCommand = r.cmd.CmdLine
	}

	logrus.Infof("cmd: %s", newCommand[0])
	logrus.Infof("args: %s", newCommand[1:])

	cmd := exec.Command(newCommand[0], newCommand[1:]...)
	cmd.Dir = config.WorkingDir
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	replacementEnvs := buildArgs.ReplacementEnvs(config.Env)
	cmd.Env = replacementEnvs

	// If specified, run the command as a specific user
	if config.User != "" {
		userAndGroup := strings.Split(config.User, ":")
		userStr := userAndGroup[0]
		var groupStr string
		if len(userAndGroup) > 1 {
			groupStr = userAndGroup[1]
		}

		uidStr, gidStr, err := util.GetUserFromUsername(userStr, groupStr)
		if err != nil {
			return err
		}

		// uid and gid need to be uint32
		uid64, err := strconv.ParseUint(uidStr, 10, 32)
		if err != nil {
			return err
		}
		uid := uint32(uid64)
		var gid uint32
		if gidStr != "" {
			gid64, err := strconv.ParseUint(gidStr, 10, 32)
			if err != nil {
				return err
			}
			gid = uint32(gid64)
		}
		cmd.SysProcAttr = &syscall.SysProcAttr{}
		cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uid, Gid: gid}
	}
	return cmd.Run()
}

// FilesToSnapshot returns nil for this command because we don't know which files
// have changed, so we snapshot the entire system.
func (r *RunCommand) FilesToSnapshot() []string {
	return nil
}

// CreatedBy returns some information about the command for the image config
func (r *RunCommand) CreatedBy() string {
	cmdLine := strings.Join(r.cmd.CmdLine, " ")
	if r.cmd.PrependShell {
		// TODO: Support shell command here
		shell := []string{"/bin/sh", "-c"}
		return strings.Join(append(shell, cmdLine), " ")
	}
	return cmdLine
}
<commit_msg>fixes #247 killing grandchildren processes<commit_after>/*
Copyright 2018 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package commands

import (
	"os"
	"os/exec"
	"strconv"
	"strings"
	"syscall"

	"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
	"github.com/GoogleContainerTools/kaniko/pkg/util"
	"github.com/docker/docker/builder/dockerfile/instructions"
	"github.com/google/go-containerregistry/pkg/v1"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

type RunCommand struct {
	cmd *instructions.RunCommand
}

func (r *RunCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error {
	var newCommand []string
	if r.cmd.PrependShell {
		// This is the default shell on Linux
		var shell []string
		if len(config.Shell) > 0 {
			shell = config.Shell
		} else {
			shell = append(shell, "/bin/sh", "-c")
		}

		newCommand = append(shell, strings.Join(r.cmd.CmdLine, " "))
	} else {
		newCommand = r.cmd.CmdLine
	}

	logrus.Infof("cmd: %s", newCommand[0])
	logrus.Infof("args: %s", newCommand[1:])

	cmd := exec.Command(newCommand[0], newCommand[1:]...)
	cmd.Dir = config.WorkingDir
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	replacementEnvs := buildArgs.ReplacementEnvs(config.Env)
	cmd.Env = replacementEnvs

	// If specified, run the command as a specific user
	if config.User != "" {
		userAndGroup := strings.Split(config.User, ":")
		userStr := userAndGroup[0]
		var groupStr string
		if len(userAndGroup) > 1 {
			groupStr = userAndGroup[1]
		}

		uidStr, gidStr, err := util.GetUserFromUsername(userStr, groupStr)
		if err != nil {
			return err
		}

		// uid and gid need to be uint32
		uid64, err := strconv.ParseUint(uidStr, 10, 32)
		if err != nil {
			return err
		}
		uid := uint32(uid64)
		var gid uint32
		if gidStr != "" {
			gid64, err := strconv.ParseUint(gidStr, 10, 32)
			if err != nil {
				return err
			}
			gid = uint32(gid64)
		}
		cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
		cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uid, Gid: gid}
	}

	if err := cmd.Start(); err != nil {
		return errors.Wrap(err, "starting command")
	}

	pgid, err := syscall.Getpgid(cmd.Process.Pid)
	if err != nil {
		return errors.Wrap(err, "getting group id for process")
	}

	if err := cmd.Wait(); err != nil {
		return errors.Wrap(err, "waiting for process to exit")
	}

	//it's not an error if there are no grandchildren
	if err := syscall.Kill(-pgid, syscall.SIGKILL); err != nil && err.Error() != "no such process" {
		return err
	}

	return nil
}

// FilesToSnapshot returns nil for this command because we don't know which files
// have changed, so we snapshot the entire system.
func (r *RunCommand) FilesToSnapshot() []string {
	return nil
}

// CreatedBy returns some information about the command for the image config
func (r *RunCommand) CreatedBy() string {
	cmdLine := strings.Join(r.cmd.CmdLine, " ")
	if r.cmd.PrependShell {
		// TODO: Support shell command here
		shell := []string{"/bin/sh", "-c"}
		return strings.Join(append(shell, cmdLine), " ")
	}
	return cmdLine
}
<|endoftext|>


<commit_before>/*
   Copyright 2020 Docker Compose CLI authors

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package compose

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/compose-spec/compose-go/types"
	"github.com/docker/cli/cli/registry/client"
	moby "github.com/docker/docker/api/types"
	"github.com/docker/docker/errdefs"
	"golang.org/x/sync/errgroup"

	"github.com/docker/compose/v2/pkg/api"
	"github.com/docker/compose/v2/pkg/progress"
)

type downOp func() error

func (s *composeService) Down(ctx context.Context, projectName string, options api.DownOptions) error {
	return progress.Run(ctx, func(ctx context.Context) error {
		return s.down(ctx, strings.ToLower(projectName), options)
	})
}

func (s *composeService) down(ctx context.Context, projectName string, options api.DownOptions) error {
	w := progress.ContextWriter(ctx)
	resourceToRemove := false

	var containers Containers
	containers, err := s.getContainers(ctx, projectName, oneOffInclude, true)
	if err != nil {
		return err
	}

	project := options.Project
	if project == nil {
		project, err = s.getProjectWithResources(ctx, containers, projectName)
		if err != nil {
			return err
		}
	}

	if len(containers) > 0 {
		resourceToRemove = true
	}

	err = InReverseDependencyOrder(ctx, project, func(c context.Context, service string) error {
		serviceContainers := containers.filter(isService(service))
		err := s.removeContainers(ctx, w, serviceContainers, options.Timeout, options.Volumes)
		return err
	})
	if err != nil {
		return err
	}

	orphans := containers.filter(isNotService(project.ServiceNames()...))
	if options.RemoveOrphans && len(orphans) > 0 {
		err := s.removeContainers(ctx, w, orphans, options.Timeout, false)
		if err != nil {
			return err
		}
	}

	ops := s.ensureNetworksDown(ctx, project, w)

	if options.Images != "" {
		ops = append(ops, s.ensureImagesDown(ctx, project, options, w)...)
	}

	if options.Volumes {
		ops = append(ops, s.ensureVolumesDown(ctx, project, w)...)
	}

	if !resourceToRemove && len(ops) == 0 {
		w.Event(progress.NewEvent(projectName, progress.Done, "Warning: No resource found to remove"))
	}

	eg, _ := errgroup.WithContext(ctx)
	for _, op := range ops {
		eg.Go(op)
	}
	return eg.Wait()
}

func (s *composeService) ensureVolumesDown(ctx context.Context, project *types.Project, w progress.Writer) []downOp {
	var ops []downOp
	for _, vol := range project.Volumes {
		if vol.External.External {
			continue
		}
		volumeName := vol.Name
		ops = append(ops, func() error {
			return s.removeVolume(ctx, volumeName, w)
		})
	}
	return ops
}

func (s *composeService) ensureImagesDown(ctx context.Context, project *types.Project, options api.DownOptions, w progress.Writer) []downOp {
	var ops []downOp
	for image := range s.getServiceImages(options, project) {
		image := image
		ops = append(ops, func() error {
			return s.removeImage(ctx, image, w)
		})
	}
	return ops
}

func (s *composeService) ensureNetworksDown(ctx context.Context, project *types.Project, w progress.Writer) []downOp {
	var ops []downOp
	for _, n := range project.Networks {
		if n.External.External {
			continue
		}
		networkName := n.Name
		_, err := s.apiClient().NetworkInspect(ctx, networkName, moby.NetworkInspectOptions{})
		if client.IsNotFound(err) {
			return nil
		}

		ops = append(ops, func() error {
			return s.removeNetwork(ctx, networkName, w)
		})
	}
	return ops
}

func (s *composeService) getServiceImages(options api.DownOptions, project *types.Project) map[string]struct{} {
	images := map[string]struct{}{}
	for _, service := range project.Services {
		image := service.Image
		if options.Images == "local" && image != "" {
			continue
		}
		if image == "" {
			image = getImageName(service, project.Name)
		}
		images[image] = struct{}{}
	}
	return images
}

func (s *composeService) removeImage(ctx context.Context, image string, w progress.Writer) error {
	id := fmt.Sprintf("Image %s", image)
	w.Event(progress.NewEvent(id, progress.Working, "Removing"))
	_, err := s.apiClient().ImageRemove(ctx, image, moby.ImageRemoveOptions{})
	if err == nil {
		w.Event(progress.NewEvent(id, progress.Done, "Removed"))
		return nil
	}
	if errdefs.IsNotFound(err) {
		w.Event(progress.NewEvent(id, progress.Done, "Warning: No resource found to remove"))
		return nil
	}
	return err
}

func (s *composeService) removeVolume(ctx context.Context, id string, w progress.Writer) error {
	resource := fmt.Sprintf("Volume %s", id)
	w.Event(progress.NewEvent(resource, progress.Working, "Removing"))
	err := s.apiClient().VolumeRemove(ctx, id, true)
	if err == nil {
		w.Event(progress.NewEvent(resource, progress.Done, "Removed"))
		return nil
	}
	if errdefs.IsNotFound(err) {
		w.Event(progress.NewEvent(resource, progress.Done, "Warning: No resource found to remove"))
		return nil
	}
	return err
}

func (s *composeService) stopContainers(ctx context.Context, w progress.Writer, containers []moby.Container, timeout *time.Duration) error {
	eg, ctx := errgroup.WithContext(ctx)
	for _, container := range containers {
		container := container
		eg.Go(func() error {
			eventName := getContainerProgressName(container)
			w.Event(progress.StoppingEvent(eventName))
			err := s.apiClient().ContainerStop(ctx, container.ID, timeout)
			if err != nil {
				w.Event(progress.ErrorMessageEvent(eventName, "Error while Stopping"))
				return err
			}
			w.Event(progress.StoppedEvent(eventName))
			return nil
		})
	}
	return eg.Wait()
}

func (s *composeService) removeContainers(ctx context.Context, w progress.Writer, containers []moby.Container, timeout *time.Duration, volumes bool) error {
	eg, _ := errgroup.WithContext(ctx)
	for _, container := range containers {
		container := container
		eg.Go(func() error {
			eventName := getContainerProgressName(container)
			w.Event(progress.StoppingEvent(eventName))
			err := s.stopContainers(ctx, w, []moby.Container{container}, timeout)
			if err != nil {
				w.Event(progress.ErrorMessageEvent(eventName, "Error while Stopping"))
				return err
			}
			w.Event(progress.RemovingEvent(eventName))
			err = s.apiClient().ContainerRemove(ctx, container.ID, moby.ContainerRemoveOptions{
				Force:         true,
				RemoveVolumes: volumes,
			})
			if err != nil {
				w.Event(progress.ErrorMessageEvent(eventName, "Error while Removing"))
				return err
			}
			w.Event(progress.RemovedEvent(eventName))
			return nil
		})
	}
	return eg.Wait()
}

func (s *composeService) getProjectWithResources(ctx context.Context, containers Containers, projectName string) (*types.Project, error) {
	containers = containers.filter(isNotOneOff)
	project, _ := s.projectFromName(containers, projectName)

	volumes, err := s.actualVolumes(ctx, projectName)
	if err != nil {
		return nil, err
	}
	project.Volumes = volumes

	networks, err := s.actualNetworks(ctx, projectName)
	if err != nil {
		return nil, err
	}
	project.Networks = networks
	return project, nil
}
<commit_msg>don't ignore error<commit_after>/*
   Copyright 2020 Docker Compose CLI authors

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package compose

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/compose-spec/compose-go/types"
	"github.com/docker/cli/cli/registry/client"
	moby "github.com/docker/docker/api/types"
	"github.com/docker/docker/errdefs"
	"golang.org/x/sync/errgroup"

	"github.com/docker/compose/v2/pkg/api"
	"github.com/docker/compose/v2/pkg/progress"
)

type downOp func() error

func (s *composeService) Down(ctx context.Context, projectName string, options api.DownOptions) error {
	return progress.Run(ctx, func(ctx context.Context) error {
		return s.down(ctx, strings.ToLower(projectName), options)
	})
}

func (s *composeService) down(ctx context.Context, projectName string, options api.DownOptions) error {
	w := progress.ContextWriter(ctx)
	resourceToRemove := false

	var containers Containers
	containers, err := s.getContainers(ctx, projectName, oneOffInclude, true)
	if err != nil {
		return err
	}

	project := options.Project
	if project == nil {
		project, err = s.getProjectWithResources(ctx, containers, projectName)
		if err != nil {
			return err
		}
	}

	if len(containers) > 0 {
		resourceToRemove = true
	}

	err = InReverseDependencyOrder(ctx, project, func(c context.Context, service string) error {
		serviceContainers := containers.filter(isService(service))
		err := s.removeContainers(ctx, w, serviceContainers, options.Timeout, options.Volumes)
		return err
	})
	if err != nil {
		return err
	}

	orphans := containers.filter(isNotService(project.ServiceNames()...))
	if options.RemoveOrphans && len(orphans) > 0 {
		err := s.removeContainers(ctx, w, orphans, options.Timeout, false)
		if err != nil {
			return err
		}
	}

	ops := s.ensureNetworksDown(ctx, project, w)

	if options.Images != "" {
		ops = append(ops, s.ensureImagesDown(ctx, project, options, w)...)
	}

	if options.Volumes {
		ops = append(ops, s.ensureVolumesDown(ctx, project, w)...)
	}

	if !resourceToRemove && len(ops) == 0 {
		w.Event(progress.NewEvent(projectName, progress.Done, "Warning: No resource found to remove"))
	}

	eg, _ := errgroup.WithContext(ctx)
	for _, op := range ops {
		eg.Go(op)
	}
	return eg.Wait()
}

func (s *composeService) ensureVolumesDown(ctx context.Context, project *types.Project, w progress.Writer) []downOp {
	var ops []downOp
	for _, vol := range project.Volumes {
		if vol.External.External {
			continue
		}
		volumeName := vol.Name
		ops = append(ops, func() error {
			return s.removeVolume(ctx, volumeName, w)
		})
	}
	return ops
}

func (s *composeService) ensureImagesDown(ctx context.Context, project *types.Project, options api.DownOptions, w progress.Writer) []downOp {
	var ops []downOp
	for image := range s.getServiceImages(options, project) {
		image := image
		ops = append(ops, func() error {
			return s.removeImage(ctx, image, w)
		})
	}
	return ops
}

func (s *composeService) ensureNetworksDown(ctx context.Context, project *types.Project, w progress.Writer) []downOp {
	var ops []downOp
	for _, n := range project.Networks {
		if n.External.External {
			continue
		}
		networkName := n.Name
		_, err := s.apiClient().NetworkInspect(ctx, networkName, moby.NetworkInspectOptions{})
		if client.IsNotFound(err) {
			return nil
		}

		ops = append(ops, func() error {
			return s.removeNetwork(ctx, networkName, w)
		})
	}
	return ops
}

func (s *composeService) getServiceImages(options api.DownOptions, project *types.Project) map[string]struct{} {
	images := map[string]struct{}{}
	for _, service := range project.Services {
		image := service.Image
		if options.Images == "local" && image != "" {
			continue
		}
		if image == "" {
			image = getImageName(service, project.Name)
		}
		images[image] = struct{}{}
	}
	return images
}

func (s *composeService) removeImage(ctx context.Context, image string, w progress.Writer) error {
	id := fmt.Sprintf("Image %s", image)
	w.Event(progress.NewEvent(id, progress.Working, "Removing"))
	_, err := s.apiClient().ImageRemove(ctx, image, moby.ImageRemoveOptions{})
	if err == nil {
		w.Event(progress.NewEvent(id, progress.Done, "Removed"))
		return nil
	}
	if errdefs.IsNotFound(err) {
		w.Event(progress.NewEvent(id, progress.Done, "Warning: No resource found to remove"))
		return nil
	}
	return err
}

func (s *composeService) removeVolume(ctx context.Context, id string, w progress.Writer) error {
	resource := fmt.Sprintf("Volume %s", id)
	w.Event(progress.NewEvent(resource, progress.Working, "Removing"))
	err := s.apiClient().VolumeRemove(ctx, id, true)
	if err == nil {
		w.Event(progress.NewEvent(resource, progress.Done, "Removed"))
		return nil
	}
	if errdefs.IsNotFound(err) {
		w.Event(progress.NewEvent(resource, progress.Done, "Warning: No resource found to remove"))
		return nil
	}
	return err
}

func (s *composeService) stopContainers(ctx context.Context, w progress.Writer, containers []moby.Container, timeout *time.Duration) error {
	eg, ctx := errgroup.WithContext(ctx)
	for _, container := range containers {
		container := container
		eg.Go(func() error {
			eventName := getContainerProgressName(container)
			w.Event(progress.StoppingEvent(eventName))
			err := s.apiClient().ContainerStop(ctx, container.ID, timeout)
			if err != nil {
				w.Event(progress.ErrorMessageEvent(eventName, "Error while Stopping"))
				return err
			}
			w.Event(progress.StoppedEvent(eventName))
			return nil
		})
	}
	return eg.Wait()
}

func (s *composeService) removeContainers(ctx context.Context, w progress.Writer, containers []moby.Container, timeout *time.Duration, volumes bool) error {
	eg, _ := errgroup.WithContext(ctx)
	for _, container := range containers {
		container := container
		eg.Go(func() error {
			eventName := getContainerProgressName(container)
			w.Event(progress.StoppingEvent(eventName))
			err := s.stopContainers(ctx, w, []moby.Container{container}, timeout)
			if err != nil {
				w.Event(progress.ErrorMessageEvent(eventName, "Error while Stopping"))
				return err
			}
			w.Event(progress.RemovingEvent(eventName))
			err = s.apiClient().ContainerRemove(ctx, container.ID, moby.ContainerRemoveOptions{
				Force:         true,
				RemoveVolumes: volumes,
			})
			if err != nil {
				w.Event(progress.ErrorMessageEvent(eventName, "Error while Removing"))
				return err
			}
			w.Event(progress.RemovedEvent(eventName))
			return nil
		})
	}
	return eg.Wait()
}

func (s *composeService) getProjectWithResources(ctx context.Context, containers Containers, projectName string) (*types.Project, error) {
	containers = containers.filter(isNotOneOff)
	project, err := s.projectFromName(containers, projectName)
	if err != nil {
		return nil, err
	}

	volumes, err := s.actualVolumes(ctx, projectName)
	if err != nil {
		return nil, err
	}
	project.Volumes = volumes

	networks, err := s.actualNetworks(ctx, projectName)
	if err != nil {
		return nil, err
	}
	project.Networks = networks
	return project, nil
}
<|endoftext|>


<commit_before>/*
Copyright © 2014–5 Brad Ackerman.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

*/

package dbaccess

import (
	"database/sql"
	"fmt"
	"log"
	"strings"

	"github.com/backerman/evego"
	"github.com/jmoiron/sqlx"
)

type sqlDb struct {
	dbType                        databaseType
	db                            *sqlx.DB
	compStatement                 *sqlx.Stmt
	itemInfoStatement             *sqlx.Stmt
	itemIDInfoStatement           *sqlx.Stmt
	catTreeFromItemStatement      *sqlx.Stmt
	systemInfoStatement           *sqlx.Stmt
	systemIDInfoStatement         *sqlx.Stmt
	regionInfoStatement           *sqlx.Stmt
	stationIDInfoStatement        *sqlx.Stmt
	stationNameInfoStatement      *sqlx.Stmt
	blueprintProducesStmt         *sqlx.Stmt
	inputMaterialsToBlueprintStmt *sqlx.Stmt
	blueprintProducedByStmt       *sqlx.Stmt
	matsForBPProductionStmt       *sqlx.Stmt
	countJumpsStmt                *sqlx.Stmt
}

// DatabaseType is the SQL vendor that we're using.
type databaseType int

// The possible databse types
const (
	Unknown databaseType = iota
	SQLite
	PostgreSQL
)

// SQLDatabase returns an EveDatabase object that can be used to access an SQL backend.
func SQLDatabase(driver, dataSource string) evego.Database {
	evedb := new(sqlDb)
	var err error
	evedb.db, err = sqlx.Connect(driver, dataSource)
	db := evedb.db // shortcut
	if err != nil {
		log.Fatalf("Unable to open item database (driver: %s, datasource: %s): %v",
			driver, dataSource, err)
	}

	// Prepare statements
	stmts := []struct {
		preparedStatement **sqlx.Stmt
		statementText     string
	}{
		// Pointer magic, stage 1: Pass the address of the pointer.
		{&evedb.compStatement, materialComposition},
		{&evedb.itemInfoStatement, itemInfo},
		{&evedb.itemIDInfoStatement, itemIDInfo},
		{&evedb.catTreeFromItemStatement, catTree},
		{&evedb.systemInfoStatement, systemInfo},
		{&evedb.systemIDInfoStatement, systemIDInfo},
		{&evedb.regionInfoStatement, regionInfo},
		{&evedb.stationIDInfoStatement, stationIDInfo},
		{&evedb.stationNameInfoStatement, stationNameInfo},
		{&evedb.blueprintProducesStmt, blueprintProduces},
		{&evedb.inputMaterialsToBlueprintStmt, inputMaterialsToBlueprint},
		{&evedb.blueprintProducedByStmt, blueprintProducedBy},
		{&evedb.matsForBPProductionStmt, materialsForBlueprintProduction},
	}

	for _, s := range stmts {
		prepared, err := db.Preparex(db.Rebind(s.statementText))
		if err != nil {
			log.Fatalf("Unable to prepare statement: %v", err)
		}
		// Pointer magic, stage 2: Dereference the pointer to the pointer
		// and set it to point to the statement we just prepared.
		*s.preparedStatement = prepared
	}

	// Routing is not standardized, so we need to treat these statements
	// specially.
	if strings.Index(driver, "sqlite3") != -1 {
		// This is SQLite.
		evedb.dbType = SQLite
		evedb.countJumpsStmt, err = db.Preparex(countJumpsSQLite)
		// If the statement preparation returned an error and it's
		// "no such module: virtualnetwork", we're trying to do something
		// that requires Spatialite, but the Spatialite module has not been
		// loaded. Don't complain now; only complain if the caller attempts
		// to actually use spatial functionality.
		if err != nil && strings.Index(err.Error(), "virtualnetwork") < 0 {
			log.Fatalf("Unable to prepare statement: %v", err)
		}
	} else {
		// Unknown database. PostgreSQL will be supported, but it isn't
		// right now.
		log.Fatalf("Unknown database driver %v", driver)
	}
	return evedb
}

// ItemForName returns a populated Item object for a given item title.
func (db *sqlDb) ItemForName(itemName string) (*evego.Item, error) {
	var err error
	object := evego.Item{}
	row := db.itemInfoStatement.QueryRowx(itemName)
	err = row.StructScan(&object)
	if err == sql.ErrNoRows {
		return nil, err
	}
	object.Type = db.itemType(&object)

	return &object, err
}

func (db *sqlDb) ItemForID(itemID int) (*evego.Item, error) {
	var err error
	object := evego.Item{}
	row := db.itemIDInfoStatement.QueryRowx(itemID)
	err = row.StructScan(&object)
	if err == sql.ErrNoRows {
		return nil, err
	}

	object.Type = db.itemType(&object)
	return &object, err
}

// itemComposition returns the composition of a named Eve item.
func (db *sqlDb) ItemComposition(itemID int) ([]evego.InventoryLine, error) {
	rows, err := db.compStatement.Query(itemID)
	if err != nil {
		log.Fatalf("Unable to execute composition query for item %d: %v", itemID, err)
	}
	defer rows.Close()

	var results []evego.InventoryLine
	for rows.Next() {
		var (
			id       int
			quantity int
		)
		err = rows.Scan(&id, &quantity)
		if err != nil {
			log.Fatalf("Unable to execute query for item %d: %v", itemID, err)
		}
		item, err := db.ItemForID(id)
		if err != nil {
			log.Fatalf("Unable to execute query for item %d component %d: %v", itemID, id, err)
		}
		results = append(results, evego.InventoryLine{Quantity: quantity, Item: item})
	}
	return results, nil
}

// MarketGroupForItem returns the parent groups of the market item.
func (db *sqlDb) MarketGroupForItem(item *evego.Item) (*evego.MarketGroup, error) {
	rows, err := db.catTreeFromItemStatement.Query(item.ID)
	// The query doesn't return ErrNoRows, so we'll check for that case
	// below.
	if err != nil {
		log.Fatalf("Unable to execute query: %v", err)
	}
	defer rows.Close()
	var itemGroup *evego.MarketGroup
	var curLevel *evego.MarketGroup
	// The SQL query returns the market group hierarchy for the queried item,
	// beginning with the item's group and walking the group hierarchy until
	// the most broad group is found.
	var (
		groupID           int
		groupName         string
		description       string
		parentID          int
		parentName        string
		parentDescription string
	)
	// Set hasRows to false here and true if we get some rows.
	hasRows := false
	for rows.Next() {
		hasRows = true
		rows.Scan(&groupID, &groupName, &description, &parentID, &parentName, &parentDescription)
		nextLevel := &evego.MarketGroup{
			ID:          groupID,
			Name:        groupName,
			Parent:      nil,
			Description: description,
		}

		if itemGroup == nil {
			// itemGroup is our return value - the item's immediate parent group.
			itemGroup = nextLevel
		}
		if curLevel != nil {
			// This level is the parent of a previous level.
			curLevel.Parent = nextLevel
		}
		curLevel = nextLevel
	}
	if !hasRows {
		// No rows when expected, so return an error.
		return nil, sql.ErrNoRows
	}
	// The last row's parentID and parentName are the first-level market category;
	// add them as the final parent.
	curLevel.Parent = &evego.MarketGroup{
		ID:          parentID,
		Name:        parentName,
		Parent:      nil,
		Description: parentDescription,
	}

	return itemGroup, nil
}

// itemType returns the type of this item, as required for reprocessing yield
// calculation. It's either ore, ice, or other.
func (db *sqlDb) itemType(item *evego.Item) evego.ItemType {
	catTree, err := db.MarketGroupForItem(item)
	if err != nil {
		return evego.UnknownItemType
	}
	for cur := catTree; cur != nil; cur = cur.Parent {
		if cur.Name == "Ore" {
			return evego.Ore
		}
		if cur.Name == "Ice Ore" {
			return evego.Ice
		}
	}
	// Ret
	return evego.Other
}

func (db *sqlDb) Close() error {
	return db.db.Close()
}

func (db *sqlDb) SolarSystemForName(systemName string) (*evego.SolarSystem, error) {
	row := db.systemInfoStatement.QueryRowx(systemName)
	system := &evego.SolarSystem{}
	err := row.StructScan(system)
	return system, err
}

func (db *sqlDb) SolarSystemsForPattern(systemName string) ([]evego.SolarSystem, error) {
	rows, err := db.systemInfoStatement.Queryx(systemName)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var systems []evego.SolarSystem
	for rows.Next() {
		system := evego.SolarSystem{}
		rows.StructScan(&system)
		systems = append(systems, system)
	}
	if len(systems) == 0 {
		err = sql.ErrNoRows
	}
	return systems, err
}

func (db *sqlDb) SolarSystemForID(systemID int) (*evego.SolarSystem, error) {
	row := db.systemIDInfoStatement.QueryRowx(systemID)
	system := &evego.SolarSystem{}
	err := row.StructScan(system)
	return system, err
}

func (db *sqlDb) RegionForName(regionName string) (*evego.Region, error) {
	row := db.regionInfoStatement.QueryRowx(regionName)
	region := &evego.Region{}
	err := row.StructScan(region)
	return region, err
}

func (db *sqlDb) StationForID(stationID int) (*evego.Station, error) {
	row := db.stationIDInfoStatement.QueryRowx(stationID)
	station := &evego.Station{}
	err := row.StructScan(station)
	return station, err
}

func (db *sqlDb) StationsForName(stationName string) ([]evego.Station, error) {
	rows, err := db.stationNameInfoStatement.Queryx(stationName)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var stations []evego.Station
	for rows.Next() {
		station := evego.Station{}
		rows.StructScan(&station)
		stations = append(stations, station)
	}
	if len(stations) == 0 {
		err = sql.ErrNoRows
	}
	return stations, err
}

func activityToTypeCode(activityStr string) evego.ActivityType {
	switch activityStr {
	case "Manufacturing":
		return evego.Manufacturing
	case "Researching Technology":
		return evego.ResearchingTechnology
	case "Researching Time Efficiency":
		return evego.ResearchingTE
	case "Researching Material Efficiency":
		return evego.ResearchingME
	case "Copying":
		return evego.Copying
	case "Duplicating":
		return evego.Duplicating
	case "Reverse Engineering":
		return evego.ReverseEngineering
	case "Invention":
		return evego.Invention
	}
	// Unknown
	return evego.None
}

func (db *sqlDb) blueprintQuery(stmt *sqlx.Stmt, query string) ([]evego.IndustryActivity, error) {
	rows, err := stmt.Queryx(query)
	if err != nil {
		log.Fatalf("Unable to execute query: %v", err)
	}
	defer rows.Close()
	var results []evego.IndustryActivity
	for rows.Next() {
		row := struct {
			InputItem        string `db:"inputItem"`
			ActivityName     string `db:"activityName"`
			OutputProduct    string `db:"outputProduct"`
			OutputProductQty int    `db:"outputProductQty"`
		}{}
		err = rows.StructScan(&row)
		if err != nil {
			return nil, fmt.Errorf("Error parsing returned industry query: %v", err)
		}
		input, err := db.ItemForName(row.InputItem)
		if err != nil {
			// This would indicate some major error in the database, but we like
			// checking for such errors even if it puts a dent in our unit-test
			// coverage numbers.
			return nil, fmt.Errorf("Couldn't find item %v with row: %#v (%d results so far)",
				row.InputItem, row, len(results))
		}
		output, err := db.ItemForName(row.OutputProduct)
		if err != nil {
			return nil, err
		}
		rowActivity := evego.IndustryActivity{
			InputItem:      input,
			OutputItem:     output,
			OutputQuantity: row.OutputProductQty,
		}
		rowActivity.ActivityType = activityToTypeCode(row.ActivityName)
		results = append(results, rowActivity)
	}

	return results, nil
}

func (db *sqlDb) BlueprintOutputs(typeName string) ([]evego.IndustryActivity, error) {
	return db.blueprintQuery(db.blueprintProducesStmt, typeName)
}

func (db *sqlDb) BlueprintForProduct(typeName string) ([]evego.IndustryActivity, error) {
	return db.blueprintQuery(db.blueprintProducedByStmt, typeName)
}

func (db *sqlDb) BlueprintsUsingMaterial(typeName string) ([]evego.IndustryActivity, error) {

	return db.blueprintQuery(db.inputMaterialsToBlueprintStmt, typeName)
}

func (db *sqlDb) BlueprintProductionInputs(
	typeName string, outputTypeName string) ([]evego.InventoryLine, error) {
	rows, err := db.matsForBPProductionStmt.Queryx(typeName, outputTypeName)
	if err != nil {
		log.Fatalf("Unable to execute query: %v", err)
	}
	defer rows.Close()
	var results []evego.InventoryLine
	for rows.Next() {
		row := struct {
			InputItem        string `db:"inputItem"`
			ActivityName     string `db:"activityName"`
			InputMaterial    string `db:"inputMaterial"`
			OutputProduct    string `db:"outputProduct"`
			InputMaterialQty int    `db:"inputMaterialQty"`
			OutputProductQty int    `db:"outputProductQty"`
			Consume          bool   `db:"consume"`
		}{}
		rows.StructScan(&row)

		// Process into an InventoryLine.
		inputMat, err := db.ItemForName(row.InputMaterial)
		if err != nil {
			log.Fatalf("Database inconsistency error: item %#v not available; %v",
				row.InputItem, err)
		}
		result := evego.InventoryLine{
			Quantity: row.InputMaterialQty,
			Item:     inputMat,
		}
		results = append(results, result)
	}

	return results, nil
}
<commit_msg>Don't error on startup when Spatialite network table not present.<commit_after>/*
Copyright © 2014–5 Brad Ackerman.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

*/

package dbaccess

import (
	"database/sql"
	"fmt"
	"log"
	"strings"

	"github.com/backerman/evego"
	"github.com/jmoiron/sqlx"
)

type sqlDb struct {
	dbType                        databaseType
	db                            *sqlx.DB
	compStatement                 *sqlx.Stmt
	itemInfoStatement             *sqlx.Stmt
	itemIDInfoStatement           *sqlx.Stmt
	catTreeFromItemStatement      *sqlx.Stmt
	systemInfoStatement           *sqlx.Stmt
	systemIDInfoStatement         *sqlx.Stmt
	regionInfoStatement           *sqlx.Stmt
	stationIDInfoStatement        *sqlx.Stmt
	stationNameInfoStatement      *sqlx.Stmt
	blueprintProducesStmt         *sqlx.Stmt
	inputMaterialsToBlueprintStmt *sqlx.Stmt
	blueprintProducedByStmt       *sqlx.Stmt
	matsForBPProductionStmt       *sqlx.Stmt
	countJumpsStmt                *sqlx.Stmt
}

// DatabaseType is the SQL vendor that we're using.
type databaseType int

// The possible databse types
const (
	Unknown databaseType = iota
	SQLite
	PostgreSQL
)

// SQLDatabase returns an EveDatabase object that can be used to access an SQL backend.
func SQLDatabase(driver, dataSource string) evego.Database {
	evedb := new(sqlDb)
	var err error
	evedb.db, err = sqlx.Connect(driver, dataSource)
	db := evedb.db // shortcut
	if err != nil {
		log.Fatalf("Unable to open item database (driver: %s, datasource: %s): %v",
			driver, dataSource, err)
	}

	// Prepare statements
	stmts := []struct {
		preparedStatement **sqlx.Stmt
		statementText     string
	}{
		// Pointer magic, stage 1: Pass the address of the pointer.
		{&evedb.compStatement, materialComposition},
		{&evedb.itemInfoStatement, itemInfo},
		{&evedb.itemIDInfoStatement, itemIDInfo},
		{&evedb.catTreeFromItemStatement, catTree},
		{&evedb.systemInfoStatement, systemInfo},
		{&evedb.systemIDInfoStatement, systemIDInfo},
		{&evedb.regionInfoStatement, regionInfo},
		{&evedb.stationIDInfoStatement, stationIDInfo},
		{&evedb.stationNameInfoStatement, stationNameInfo},
		{&evedb.blueprintProducesStmt, blueprintProduces},
		{&evedb.inputMaterialsToBlueprintStmt, inputMaterialsToBlueprint},
		{&evedb.blueprintProducedByStmt, blueprintProducedBy},
		{&evedb.matsForBPProductionStmt, materialsForBlueprintProduction},
	}

	for _, s := range stmts {
		prepared, err := db.Preparex(db.Rebind(s.statementText))
		if err != nil {
			log.Fatalf("Unable to prepare statement: %v", err)
		}
		// Pointer magic, stage 2: Dereference the pointer to the pointer
		// and set it to point to the statement we just prepared.
		*s.preparedStatement = prepared
	}

	// Routing is not standardized, so we need to treat these statements
	// specially.
	if strings.Index(driver, "sqlite3") != -1 {
		// This is SQLite.
		evedb.dbType = SQLite
		evedb.countJumpsStmt, err = db.Preparex(countJumpsSQLite)
		// If the statement preparation returned an error and it's
		// "no such module: virtualnetwork", we're trying to do something
		// that requires Spatialite, but the Spatialite module has not been
		// loaded. Don't complain now; only complain if the caller attempts
		// to actually use spatial functionality.
		if err != nil &&
			strings.Index(err.Error(), "virtualnetwork") < 0 &&
			strings.Index(err.Error(), "no such table") < 0 {
			log.Fatalf("Unable to prepare statement: %v", err)
		}
	} else {
		// Unknown database. PostgreSQL will be supported, but it isn't
		// right now.
		log.Fatalf("Unknown database driver %v", driver)
	}
	return evedb
}

// ItemForName returns a populated Item object for a given item title.
func (db *sqlDb) ItemForName(itemName string) (*evego.Item, error) {
	var err error
	object := evego.Item{}
	row := db.itemInfoStatement.QueryRowx(itemName)
	err = row.StructScan(&object)
	if err == sql.ErrNoRows {
		return nil, err
	}
	object.Type = db.itemType(&object)

	return &object, err
}

func (db *sqlDb) ItemForID(itemID int) (*evego.Item, error) {
	var err error
	object := evego.Item{}
	row := db.itemIDInfoStatement.QueryRowx(itemID)
	err = row.StructScan(&object)
	if err == sql.ErrNoRows {
		return nil, err
	}

	object.Type = db.itemType(&object)
	return &object, err
}

// itemComposition returns the composition of a named Eve item.
func (db *sqlDb) ItemComposition(itemID int) ([]evego.InventoryLine, error) {
	rows, err := db.compStatement.Query(itemID)
	if err != nil {
		log.Fatalf("Unable to execute composition query for item %d: %v", itemID, err)
	}
	defer rows.Close()

	var results []evego.InventoryLine
	for rows.Next() {
		var (
			id       int
			quantity int
		)
		err = rows.Scan(&id, &quantity)
		if err != nil {
			log.Fatalf("Unable to execute query for item %d: %v", itemID, err)
		}
		item, err := db.ItemForID(id)
		if err != nil {
			log.Fatalf("Unable to execute query for item %d component %d: %v", itemID, id, err)
		}
		results = append(results, evego.InventoryLine{Quantity: quantity, Item: item})
	}
	return results, nil
}

// MarketGroupForItem returns the parent groups of the market item.
func (db *sqlDb) MarketGroupForItem(item *evego.Item) (*evego.MarketGroup, error) {
	rows, err := db.catTreeFromItemStatement.Query(item.ID)
	// The query doesn't return ErrNoRows, so we'll check for that case
	// below.
	if err != nil {
		log.Fatalf("Unable to execute query: %v", err)
	}
	defer rows.Close()
	var itemGroup *evego.MarketGroup
	var curLevel *evego.MarketGroup
	// The SQL query returns the market group hierarchy for the queried item,
	// beginning with the item's group and walking the group hierarchy until
	// the most broad group is found.
	var (
		groupID           int
		groupName         string
		description       string
		parentID          int
		parentName        string
		parentDescription string
	)
	// Set hasRows to false here and true if we get some rows.
	hasRows := false
	for rows.Next() {
		hasRows = true
		rows.Scan(&groupID, &groupName, &description, &parentID, &parentName, &parentDescription)
		nextLevel := &evego.MarketGroup{
			ID:          groupID,
			Name:        groupName,
			Parent:      nil,
			Description: description,
		}

		if itemGroup == nil {
			// itemGroup is our return value - the item's immediate parent group.
			itemGroup = nextLevel
		}
		if curLevel != nil {
			// This level is the parent of a previous level.
			curLevel.Parent = nextLevel
		}
		curLevel = nextLevel
	}
	if !hasRows {
		// No rows when expected, so return an error.
		return nil, sql.ErrNoRows
	}
	// The last row's parentID and parentName are the first-level market category;
	// add them as the final parent.
	curLevel.Parent = &evego.MarketGroup{
		ID:          parentID,
		Name:        parentName,
		Parent:      nil,
		Description: parentDescription,
	}

	return itemGroup, nil
}

// itemType returns the type of this item, as required for reprocessing yield
// calculation. It's either ore, ice, or other.
func (db *sqlDb) itemType(item *evego.Item) evego.ItemType {
	catTree, err := db.MarketGroupForItem(item)
	if err != nil {
		return evego.UnknownItemType
	}
	for cur := catTree; cur != nil; cur = cur.Parent {
		if cur.Name == "Ore" {
			return evego.Ore
		}
		if cur.Name == "Ice Ore" {
			return evego.Ice
		}
	}
	// Ret
	return evego.Other
}

func (db *sqlDb) Close() error {
	return db.db.Close()
}

func (db *sqlDb) SolarSystemForName(systemName string) (*evego.SolarSystem, error) {
	row := db.systemInfoStatement.QueryRowx(systemName)
	system := &evego.SolarSystem{}
	err := row.StructScan(system)
	return system, err
}

func (db *sqlDb) SolarSystemsForPattern(systemName string) ([]evego.SolarSystem, error) {
	rows, err := db.systemInfoStatement.Queryx(systemName)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var systems []evego.SolarSystem
	for rows.Next() {
		system := evego.SolarSystem{}
		rows.StructScan(&system)
		systems = append(systems, system)
	}
	if len(systems) == 0 {
		err = sql.ErrNoRows
	}
	return systems, err
}

func (db *sqlDb) SolarSystemForID(systemID int) (*evego.SolarSystem, error) {
	row := db.systemIDInfoStatement.QueryRowx(systemID)
	system := &evego.SolarSystem{}
	err := row.StructScan(system)
	return system, err
}

func (db *sqlDb) RegionForName(regionName string) (*evego.Region, error) {
	row := db.regionInfoStatement.QueryRowx(regionName)
	region := &evego.Region{}
	err := row.StructScan(region)
	return region, err
}

func (db *sqlDb) StationForID(stationID int) (*evego.Station, error) {
	row := db.stationIDInfoStatement.QueryRowx(stationID)
	station := &evego.Station{}
	err := row.StructScan(station)
	return station, err
}

func (db *sqlDb) StationsForName(stationName string) ([]evego.Station, error) {
	rows, err := db.stationNameInfoStatement.Queryx(stationName)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var stations []evego.Station
	for rows.Next() {
		station := evego.Station{}
		rows.StructScan(&station)
		stations = append(stations, station)
	}
	if len(stations) == 0 {
		err = sql.ErrNoRows
	}
	return stations, err
}

func activityToTypeCode(activityStr string) evego.ActivityType {
	switch activityStr {
	case "Manufacturing":
		return evego.Manufacturing
	case "Researching Technology":
		return evego.ResearchingTechnology
	case "Researching Time Efficiency":
		return evego.ResearchingTE
	case "Researching Material Efficiency":
		return evego.ResearchingME
	case "Copying":
		return evego.Copying
	case "Duplicating":
		return evego.Duplicating
	case "Reverse Engineering":
		return evego.ReverseEngineering
	case "Invention":
		return evego.Invention
	}
	// Unknown
	return evego.None
}

func (db *sqlDb) blueprintQuery(stmt *sqlx.Stmt, query string) ([]evego.IndustryActivity, error) {
	rows, err := stmt.Queryx(query)
	if err != nil {
		log.Fatalf("Unable to execute query: %v", err)
	}
	defer rows.Close()
	var results []evego.IndustryActivity
	for rows.Next() {
		row := struct {
			InputItem        string `db:"inputItem"`
			ActivityName     string `db:"activityName"`
			OutputProduct    string `db:"outputProduct"`
			OutputProductQty int    `db:"outputProductQty"`
		}{}
		err = rows.StructScan(&row)
		if err != nil {
			return nil, fmt.Errorf("Error parsing returned industry query: %v", err)
		}
		input, err := db.ItemForName(row.InputItem)
		if err != nil {
			// This would indicate some major error in the database, but we like
			// checking for such errors even if it puts a dent in our unit-test
			// coverage numbers.
			return nil, fmt.Errorf("Couldn't find item %v with row: %#v (%d results so far)",
				row.InputItem, row, len(results))
		}
		output, err := db.ItemForName(row.OutputProduct)
		if err != nil {
			return nil, err
		}
		rowActivity := evego.IndustryActivity{
			InputItem:      input,
			OutputItem:     output,
			OutputQuantity: row.OutputProductQty,
		}
		rowActivity.ActivityType = activityToTypeCode(row.ActivityName)
		results = append(results, rowActivity)
	}

	return results, nil
}

func (db *sqlDb) BlueprintOutputs(typeName string) ([]evego.IndustryActivity, error) {
	return db.blueprintQuery(db.blueprintProducesStmt, typeName)
}

func (db *sqlDb) BlueprintForProduct(typeName string) ([]evego.IndustryActivity, error) {
	return db.blueprintQuery(db.blueprintProducedByStmt, typeName)
}

func (db *sqlDb) BlueprintsUsingMaterial(typeName string) ([]evego.IndustryActivity, error) {

	return db.blueprintQuery(db.inputMaterialsToBlueprintStmt, typeName)
}

func (db *sqlDb) BlueprintProductionInputs(
	typeName string, outputTypeName string) ([]evego.InventoryLine, error) {
	rows, err := db.matsForBPProductionStmt.Queryx(typeName, outputTypeName)
	if err != nil {
		log.Fatalf("Unable to execute query: %v", err)
	}
	defer rows.Close()
	var results []evego.InventoryLine
	for rows.Next() {
		row := struct {
			InputItem        string `db:"inputItem"`
			ActivityName     string `db:"activityName"`
			InputMaterial    string `db:"inputMaterial"`
			OutputProduct    string `db:"outputProduct"`
			InputMaterialQty int    `db:"inputMaterialQty"`
			OutputProductQty int    `db:"outputProductQty"`
			Consume          bool   `db:"consume"`
		}{}
		rows.StructScan(&row)

		// Process into an InventoryLine.
		inputMat, err := db.ItemForName(row.InputMaterial)
		if err != nil {
			log.Fatalf("Database inconsistency error: item %#v not available; %v",
				row.InputItem, err)
		}
		result := evego.InventoryLine{
			Quantity: row.InputMaterialQty,
			Item:     inputMat,
		}
		results = append(results, result)
	}

	return results, nil
}
<|endoftext|>


<commit_before>// Copyright 2014 Wandoujia Inc. All Rights Reserved.
// Licensed under the MIT (MIT-LICENSE.txt) license.

package models

import "sort"

const MaxGroupId = 1e4 - 1

type Group struct {
	Id        int      `json:"id"`
	Servers   []string `json:"servers"`
	Promoting bool     `json:"promoting,omitempty"`
}

func (g *Group) Encode() []byte {
	return jsonEncode(g)
}

func (g *Group) Decode(b []byte) error {
	return jsonDecode(g, b)
}

type groupSorter struct {
	list []*Group
	less func(g1, g2 *Group) bool
}

func (s *groupSorter) Len() int {
	return len(s.list)
}

func (s *groupSorter) Swap(i, j int) {
	s.list[i], s.list[j] = s.list[j], s.list[i]
}

func (s *groupSorter) Less(i, j int) bool {
	return s.less(s.list[i], s.list[j])
}

func SortGroup(list []*Group, less func(g1, g2 *Group) bool) {
	sort.Sort(&groupSorter{list, less})
}
<commit_msg>Add, use GroupServer to record codis-server<commit_after>// Copyright 2014 Wandoujia Inc. All Rights Reserved.
// Licensed under the MIT (MIT-LICENSE.txt) license.

package models

import "sort"

const MaxGroupId = 9999

type Group struct {
	Id      int            `json:"id"`
	Servers []*GroupServer `json:"servers"`

	Promoting struct {
		Index int    `json:"index,omitempty"`
		State string `json:"state,omitempty"`
	} `json:"promoting"`
}

type GroupServer struct {
	Addr string `json:"addr"`

	Action struct {
		Index int    `json:"index,omitempty"`
		State string `json:"state,omitempty"`
	} `json:"action"`
}

func (g *Group) Encode() []byte {
	return jsonEncode(g)
}

func (g *Group) Decode(b []byte) error {
	return jsonDecode(g, b)
}

type groupSorter struct {
	list []*Group
	less func(g1, g2 *Group) bool
}

func (s *groupSorter) Len() int {
	return len(s.list)
}

func (s *groupSorter) Swap(i, j int) {
	s.list[i], s.list[j] = s.list[j], s.list[i]
}

func (s *groupSorter) Less(i, j int) bool {
	return s.less(s.list[i], s.list[j])
}

func SortGroup(list []*Group, less func(g1, g2 *Group) bool) {
	sort.Sort(&groupSorter{list, less})
}

func SortGroupById(list []*Group) {
	SortGroup(list, func(g1, g2 *Group) bool {
		return g1.Id < g2.Id
	})
}
<|endoftext|>


<commit_before>package ovs

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/golang/glog"

	utilversion "k8s.io/kubernetes/pkg/util/version"
	"k8s.io/utils/exec"
)

// Interface represents an interface to OVS
type Interface interface {
	// AddBridge creates the bridge associated with the interface, optionally setting
	// properties on it (as with "ovs-vsctl set Bridge ..."). If the bridge already
	// exists this errors.
	AddBridge(properties ...string) error

	// DeleteBridge deletes the bridge associated with the interface. The boolean
	// that can be passed determines if a bridge not existing is an error. Passing
	// true will delete bridge --if-exists, passing false will error if the bridge
	// does not exist.
	DeleteBridge(ifExists bool) error

	// AddPort adds an interface to the bridge, requesting the indicated port
	// number, and optionally setting properties on it (as with "ovs-vsctl set
	// Interface ..."). Returns the allocated port number (or an error).
	AddPort(port string, ofportRequest int, properties ...string) (int, error)

	// DeletePort removes an interface from the bridge. (It is not an
	// error if the interface is not currently a bridge port.)
	DeletePort(port string) error

	// GetOFPort returns the OpenFlow port number of a given network interface
	// attached to a bridge.
	GetOFPort(port string) (int, error)

	// SetFrags sets the fragmented-packet-handling mode (as with
	// "ovs-ofctl set-frags")
	SetFrags(mode string) error

	// Create creates a record in the OVS database, as with "ovs-vsctl create" and
	// returns the UUID of the newly-created item.
	// NOTE: This only works for QoS; for all other tables the created object will
	// immediately be garbage-collected; we'd need an API that calls "create" and "set"
	// in the same "ovs-vsctl" call.
	Create(table string, values ...string) (string, error)

	// Destroy deletes the indicated record in the OVS database. It is not an error if
	// the record does not exist
	Destroy(table, record string) error

	// Get gets the indicated value from the OVS database. For multi-valued or
	// map-valued columns, the data is returned in the same format as "ovs-vsctl get".
	Get(table, record, column string) (string, error)

	// Set sets one or more columns on a record in the OVS database, as with
	// "ovs-vsctl set"
	Set(table, record string, values ...string) error

	// Clear unsets the indicated columns in the OVS database. It is not an error if
	// the value is already unset
	Clear(table, record string, columns ...string) error

	// Find finds records in the OVS database that match the given condition.
	// It returns the value of the given column of matching records.
	Find(table, column, condition string) ([]string, error)

	// DumpFlows dumps the flow table for the bridge and returns it as an array of
	// strings, one per flow. If flow is not "" then it describes the flows to dump.
	DumpFlows(flow string, args ...interface{}) ([]string, error)

	// NewTransaction begins a new OVS transaction. If an error occurs at
	// any step in the transaction, it will be recorded until
	// EndTransaction(), and any further calls on the transaction will be
	// ignored.
	NewTransaction() Transaction
}

// Transaction manages a single set of OVS flow modifications
type Transaction interface {
	// AddFlow adds a flow to the bridge. The arguments are passed to fmt.Sprintf().
	AddFlow(flow string, args ...interface{})

	// DeleteFlows deletes all matching flows from the bridge. The arguments are
	// passed to fmt.Sprintf().
	DeleteFlows(flow string, args ...interface{})

	// EndTransaction ends an OVS transaction and returns any error that occurred
	// during the transaction. You should not use the transaction again after
	// calling this function.
	EndTransaction() error
}

const (
	OVS_OFCTL = "ovs-ofctl"
	OVS_VSCTL = "ovs-vsctl"
)

// ovsExec implements ovs.Interface via calls to ovs-ofctl and ovs-vsctl
type ovsExec struct {
	execer exec.Interface
	bridge string
}

// New returns a new ovs.Interface
func New(execer exec.Interface, bridge string, minVersion string) (Interface, error) {
	if _, err := execer.LookPath(OVS_OFCTL); err != nil {
		return nil, fmt.Errorf("OVS is not installed")
	}
	if _, err := execer.LookPath(OVS_VSCTL); err != nil {
		return nil, fmt.Errorf("OVS is not installed")
	}

	ovsif := &ovsExec{execer: execer, bridge: bridge}

	if minVersion != "" {
		minVer := utilversion.MustParseGeneric(minVersion)

		out, err := ovsif.exec(OVS_VSCTL, "--version")
		if err != nil {
			return nil, fmt.Errorf("could not check OVS version is %s or higher", minVersion)
		}
		// First output line should end with version
		lines := strings.Split(out, "\n")
		spc := strings.LastIndex(lines[0], " ")
		instVer, err := utilversion.ParseGeneric(lines[0][spc+1:])
		if err != nil {
			return nil, fmt.Errorf("could not find OVS version in %q", lines[0])
		}
		if !instVer.AtLeast(minVer) {
			return nil, fmt.Errorf("found OVS %v, need %s or later", instVer, minVersion)
		}
	}

	return ovsif, nil
}

func (ovsif *ovsExec) exec(cmd string, args ...string) (string, error) {
	if cmd == OVS_OFCTL {
		args = append([]string{"-O", "OpenFlow13"}, args...)
	}
	glog.V(5).Infof("Executing: %s %s", cmd, strings.Join(args, " "))

	output, err := ovsif.execer.Command(cmd, args...).CombinedOutput()
	if err != nil {
		glog.V(2).Infof("Error executing %s: %s", cmd, string(output))
		return "", err
	}

	outStr := string(output)
	if outStr != "" {
		// If output is a single line, strip the trailing newline
		nl := strings.Index(outStr, "\n")
		if nl == len(outStr)-1 {
			outStr = outStr[:nl]
		}
	}
	return outStr, nil
}

func (ovsif *ovsExec) AddBridge(properties ...string) error {
	args := []string{"add-br", ovsif.bridge}
	if len(properties) > 0 {
		args = append(args, "--", "set", "Bridge", ovsif.bridge)
		args = append(args, properties...)
	}
	_, err := ovsif.exec(OVS_VSCTL, args...)
	return err
}

func (ovsif *ovsExec) DeleteBridge(ifExists bool) error {
	args := []string{"del-br", ovsif.bridge}

	if ifExists {
		args = append([]string{"--if-exists"}, args...)
	}
	_, err := ovsif.exec(OVS_VSCTL, args...)
	return err
}

func (ovsif *ovsExec) GetOFPort(port string) (int, error) {
	ofportStr, err := ovsif.exec(OVS_VSCTL, "get", "Interface", port, "ofport")
	if err != nil {
		return -1, fmt.Errorf("failed to get OVS port for %s: %v", port, err)
	}
	ofport, err := strconv.Atoi(ofportStr)
	if err != nil {
		return -1, fmt.Errorf("could not parse allocated ofport %q: %v", ofportStr, err)
	}
	if ofport == -1 {
		errStr, err := ovsif.exec(OVS_VSCTL, "get", "Interface", port, "error")
		if err != nil || errStr == "" {
			errStr = "unknown error"
		}
		return -1, fmt.Errorf("error on port %s: %s", port, errStr)
	}
	return ofport, nil
}

func (ovsif *ovsExec) AddPort(port string, ofportRequest int, properties ...string) (int, error) {
	args := []string{"--may-exist", "add-port", ovsif.bridge, port}
	if ofportRequest > 0 || len(properties) > 0 {
		args = append(args, "--", "set", "Interface", port)
		if ofportRequest > 0 {
			args = append(args, fmt.Sprintf("ofport_request=%d", ofportRequest))
		}
		if len(properties) > 0 {
			args = append(args, properties...)
		}
	}
	_, err := ovsif.exec(OVS_VSCTL, args...)
	if err != nil {
		return -1, err
	}
	ofport, err := ovsif.GetOFPort(port)
	if err != nil {
		return -1, err
	}
	if ofportRequest > 0 && ofportRequest != ofport {
		return -1, fmt.Errorf("allocated ofport (%d) did not match request (%d)", ofport, ofportRequest)
	}
	return ofport, nil
}

func (ovsif *ovsExec) DeletePort(port string) error {
	_, err := ovsif.exec(OVS_VSCTL, "--if-exists", "del-port", ovsif.bridge, port)
	return err
}

func (ovsif *ovsExec) SetFrags(mode string) error {
	_, err := ovsif.exec(OVS_OFCTL, "set-frags", ovsif.bridge, mode)
	return err
}

func (ovsif *ovsExec) Create(table string, values ...string) (string, error) {
	args := append([]string{"create", table}, values...)
	return ovsif.exec(OVS_VSCTL, args...)
}

func (ovsif *ovsExec) Destroy(table, record string) error {
	_, err := ovsif.exec(OVS_VSCTL, "--if-exists", "destroy", table, record)
	return err
}

func (ovsif *ovsExec) Get(table, record, column string) (string, error) {
	return ovsif.exec(OVS_VSCTL, "get", table, record, column)
}

func (ovsif *ovsExec) Set(table, record string, values ...string) error {
	args := append([]string{"set", table, record}, values...)
	_, err := ovsif.exec(OVS_VSCTL, args...)
	return err
}

// Returns the given column of records that match the condition
func (ovsif *ovsExec) Find(table, column, condition string) ([]string, error) {
	output, err := ovsif.exec(OVS_VSCTL, "--no-heading", "--columns="+column, "find", table, condition)
	if err != nil {
		return nil, err
	}
	values := strings.Split(output, "\n\n")
	// We want "bare" values for strings, but we can't pass --bare to ovs-vsctl because
	// it breaks more complicated types. So try passing each value through Unquote();
	// if it fails, that means the value wasn't a quoted string, so use it as-is.
	for i, val := range values {
		if unquoted, err := strconv.Unquote(val); err == nil {
			values[i] = unquoted
		}
	}
	return values, nil
}

func (ovsif *ovsExec) Clear(table, record string, columns ...string) error {
	args := append([]string{"--if-exists", "clear", table, record}, columns...)
	_, err := ovsif.exec(OVS_VSCTL, args...)
	return err
}

type ovsExecTx struct {
	ovsif *ovsExec
	err   error
}

func (tx *ovsExecTx) exec(cmd string, args ...string) (string, error) {
	out := ""
	if tx.err == nil {
		out, tx.err = tx.ovsif.exec(cmd, args...)
	}
	return out, tx.err
}

func (ovsif *ovsExec) NewTransaction() Transaction {
	return &ovsExecTx{ovsif: ovsif}
}

func (tx *ovsExecTx) AddFlow(flow string, args ...interface{}) {
	if len(args) > 0 {
		flow = fmt.Sprintf(flow, args...)
	}
	tx.exec(OVS_OFCTL, "add-flow", tx.ovsif.bridge, flow)
}

func (tx *ovsExecTx) DeleteFlows(flow string, args ...interface{}) {
	if len(args) > 0 {
		flow = fmt.Sprintf(flow, args...)
	}
	tx.exec(OVS_OFCTL, "del-flows", tx.ovsif.bridge, flow)
}

func (tx *ovsExecTx) EndTransaction() error {
	err := tx.err
	tx.err = nil
	return err
}

func (ovsif *ovsExec) DumpFlows(flow string, args ...interface{}) ([]string, error) {
	if len(args) > 0 {
		flow = fmt.Sprintf(flow, args...)
	}
	out, err := ovsif.exec(OVS_OFCTL, "dump-flows", ovsif.bridge, flow)
	if err != nil {
		return nil, err
	}

	lines := strings.Split(out, "\n")
	flows := make([]string, 0, len(lines))
	for _, line := range lines {
		if strings.Contains(line, "cookie=") {
			flows = append(flows, line)
		}
	}
	return flows, nil
}
<commit_msg>log OVS commands at level 4<commit_after>package ovs

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/golang/glog"

	utilversion "k8s.io/kubernetes/pkg/util/version"
	"k8s.io/utils/exec"
)

// Interface represents an interface to OVS
type Interface interface {
	// AddBridge creates the bridge associated with the interface, optionally setting
	// properties on it (as with "ovs-vsctl set Bridge ..."). If the bridge already
	// exists this errors.
	AddBridge(properties ...string) error

	// DeleteBridge deletes the bridge associated with the interface. The boolean
	// that can be passed determines if a bridge not existing is an error. Passing
	// true will delete bridge --if-exists, passing false will error if the bridge
	// does not exist.
	DeleteBridge(ifExists bool) error

	// AddPort adds an interface to the bridge, requesting the indicated port
	// number, and optionally setting properties on it (as with "ovs-vsctl set
	// Interface ..."). Returns the allocated port number (or an error).
	AddPort(port string, ofportRequest int, properties ...string) (int, error)

	// DeletePort removes an interface from the bridge. (It is not an
	// error if the interface is not currently a bridge port.)
	DeletePort(port string) error

	// GetOFPort returns the OpenFlow port number of a given network interface
	// attached to a bridge.
	GetOFPort(port string) (int, error)

	// SetFrags sets the fragmented-packet-handling mode (as with
	// "ovs-ofctl set-frags")
	SetFrags(mode string) error

	// Create creates a record in the OVS database, as with "ovs-vsctl create" and
	// returns the UUID of the newly-created item.
	// NOTE: This only works for QoS; for all other tables the created object will
	// immediately be garbage-collected; we'd need an API that calls "create" and "set"
	// in the same "ovs-vsctl" call.
	Create(table string, values ...string) (string, error)

	// Destroy deletes the indicated record in the OVS database. It is not an error if
	// the record does not exist
	Destroy(table, record string) error

	// Get gets the indicated value from the OVS database. For multi-valued or
	// map-valued columns, the data is returned in the same format as "ovs-vsctl get".
	Get(table, record, column string) (string, error)

	// Set sets one or more columns on a record in the OVS database, as with
	// "ovs-vsctl set"
	Set(table, record string, values ...string) error

	// Clear unsets the indicated columns in the OVS database. It is not an error if
	// the value is already unset
	Clear(table, record string, columns ...string) error

	// Find finds records in the OVS database that match the given condition.
	// It returns the value of the given column of matching records.
	Find(table, column, condition string) ([]string, error)

	// DumpFlows dumps the flow table for the bridge and returns it as an array of
	// strings, one per flow. If flow is not "" then it describes the flows to dump.
	DumpFlows(flow string, args ...interface{}) ([]string, error)

	// NewTransaction begins a new OVS transaction. If an error occurs at
	// any step in the transaction, it will be recorded until
	// EndTransaction(), and any further calls on the transaction will be
	// ignored.
	NewTransaction() Transaction
}

// Transaction manages a single set of OVS flow modifications
type Transaction interface {
	// AddFlow adds a flow to the bridge. The arguments are passed to fmt.Sprintf().
	AddFlow(flow string, args ...interface{})

	// DeleteFlows deletes all matching flows from the bridge. The arguments are
	// passed to fmt.Sprintf().
	DeleteFlows(flow string, args ...interface{})

	// EndTransaction ends an OVS transaction and returns any error that occurred
	// during the transaction. You should not use the transaction again after
	// calling this function.
	EndTransaction() error
}

const (
	OVS_OFCTL = "ovs-ofctl"
	OVS_VSCTL = "ovs-vsctl"
)

// ovsExec implements ovs.Interface via calls to ovs-ofctl and ovs-vsctl
type ovsExec struct {
	execer exec.Interface
	bridge string
}

// New returns a new ovs.Interface
func New(execer exec.Interface, bridge string, minVersion string) (Interface, error) {
	if _, err := execer.LookPath(OVS_OFCTL); err != nil {
		return nil, fmt.Errorf("OVS is not installed")
	}
	if _, err := execer.LookPath(OVS_VSCTL); err != nil {
		return nil, fmt.Errorf("OVS is not installed")
	}

	ovsif := &ovsExec{execer: execer, bridge: bridge}

	if minVersion != "" {
		minVer := utilversion.MustParseGeneric(minVersion)

		out, err := ovsif.exec(OVS_VSCTL, "--version")
		if err != nil {
			return nil, fmt.Errorf("could not check OVS version is %s or higher", minVersion)
		}
		// First output line should end with version
		lines := strings.Split(out, "\n")
		spc := strings.LastIndex(lines[0], " ")
		instVer, err := utilversion.ParseGeneric(lines[0][spc+1:])
		if err != nil {
			return nil, fmt.Errorf("could not find OVS version in %q", lines[0])
		}
		if !instVer.AtLeast(minVer) {
			return nil, fmt.Errorf("found OVS %v, need %s or later", instVer, minVersion)
		}
	}

	return ovsif, nil
}

func (ovsif *ovsExec) exec(cmd string, args ...string) (string, error) {
	if cmd == OVS_OFCTL {
		args = append([]string{"-O", "OpenFlow13"}, args...)
	}
	glog.V(4).Infof("Executing: %s %s", cmd, strings.Join(args, " "))

	output, err := ovsif.execer.Command(cmd, args...).CombinedOutput()
	if err != nil {
		glog.V(2).Infof("Error executing %s: %s", cmd, string(output))
		return "", err
	}

	outStr := string(output)
	if outStr != "" {
		// If output is a single line, strip the trailing newline
		nl := strings.Index(outStr, "\n")
		if nl == len(outStr)-1 {
			outStr = outStr[:nl]
		}
	}
	return outStr, nil
}

func (ovsif *ovsExec) AddBridge(properties ...string) error {
	args := []string{"add-br", ovsif.bridge}
	if len(properties) > 0 {
		args = append(args, "--", "set", "Bridge", ovsif.bridge)
		args = append(args, properties...)
	}
	_, err := ovsif.exec(OVS_VSCTL, args...)
	return err
}

func (ovsif *ovsExec) DeleteBridge(ifExists bool) error {
	args := []string{"del-br", ovsif.bridge}

	if ifExists {
		args = append([]string{"--if-exists"}, args...)
	}
	_, err := ovsif.exec(OVS_VSCTL, args...)
	return err
}

func (ovsif *ovsExec) GetOFPort(port string) (int, error) {
	ofportStr, err := ovsif.exec(OVS_VSCTL, "get", "Interface", port, "ofport")
	if err != nil {
		return -1, fmt.Errorf("failed to get OVS port for %s: %v", port, err)
	}
	ofport, err := strconv.Atoi(ofportStr)
	if err != nil {
		return -1, fmt.Errorf("could not parse allocated ofport %q: %v", ofportStr, err)
	}
	if ofport == -1 {
		errStr, err := ovsif.exec(OVS_VSCTL, "get", "Interface", port, "error")
		if err != nil || errStr == "" {
			errStr = "unknown error"
		}
		return -1, fmt.Errorf("error on port %s: %s", port, errStr)
	}
	return ofport, nil
}

func (ovsif *ovsExec) AddPort(port string, ofportRequest int, properties ...string) (int, error) {
	args := []string{"--may-exist", "add-port", ovsif.bridge, port}
	if ofportRequest > 0 || len(properties) > 0 {
		args = append(args, "--", "set", "Interface", port)
		if ofportRequest > 0 {
			args = append(args, fmt.Sprintf("ofport_request=%d", ofportRequest))
		}
		if len(properties) > 0 {
			args = append(args, properties...)
		}
	}
	_, err := ovsif.exec(OVS_VSCTL, args...)
	if err != nil {
		return -1, err
	}
	ofport, err := ovsif.GetOFPort(port)
	if err != nil {
		return -1, err
	}
	if ofportRequest > 0 && ofportRequest != ofport {
		return -1, fmt.Errorf("allocated ofport (%d) did not match request (%d)", ofport, ofportRequest)
	}
	return ofport, nil
}

func (ovsif *ovsExec) DeletePort(port string) error {
	_, err := ovsif.exec(OVS_VSCTL, "--if-exists", "del-port", ovsif.bridge, port)
	return err
}

func (ovsif *ovsExec) SetFrags(mode string) error {
	_, err := ovsif.exec(OVS_OFCTL, "set-frags", ovsif.bridge, mode)
	return err
}

func (ovsif *ovsExec) Create(table string, values ...string) (string, error) {
	args := append([]string{"create", table}, values...)
	return ovsif.exec(OVS_VSCTL, args...)
}

func (ovsif *ovsExec) Destroy(table, record string) error {
	_, err := ovsif.exec(OVS_VSCTL, "--if-exists", "destroy", table, record)
	return err
}

func (ovsif *ovsExec) Get(table, record, column string) (string, error) {
	return ovsif.exec(OVS_VSCTL, "get", table, record, column)
}

func (ovsif *ovsExec) Set(table, record string, values ...string) error {
	args := append([]string{"set", table, record}, values...)
	_, err := ovsif.exec(OVS_VSCTL, args...)
	return err
}

// Returns the given column of records that match the condition
func (ovsif *ovsExec) Find(table, column, condition string) ([]string, error) {
	output, err := ovsif.exec(OVS_VSCTL, "--no-heading", "--columns="+column, "find", table, condition)
	if err != nil {
		return nil, err
	}
	values := strings.Split(output, "\n\n")
	// We want "bare" values for strings, but we can't pass --bare to ovs-vsctl because
	// it breaks more complicated types. So try passing each value through Unquote();
	// if it fails, that means the value wasn't a quoted string, so use it as-is.
	for i, val := range values {
		if unquoted, err := strconv.Unquote(val); err == nil {
			values[i] = unquoted
		}
	}
	return values, nil
}

func (ovsif *ovsExec) Clear(table, record string, columns ...string) error {
	args := append([]string{"--if-exists", "clear", table, record}, columns...)
	_, err := ovsif.exec(OVS_VSCTL, args...)
	return err
}

type ovsExecTx struct {
	ovsif *ovsExec
	err   error
}

func (tx *ovsExecTx) exec(cmd string, args ...string) (string, error) {
	out := ""
	if tx.err == nil {
		out, tx.err = tx.ovsif.exec(cmd, args...)
	}
	return out, tx.err
}

func (ovsif *ovsExec) NewTransaction() Transaction {
	return &ovsExecTx{ovsif: ovsif}
}

func (tx *ovsExecTx) AddFlow(flow string, args ...interface{}) {
	if len(args) > 0 {
		flow = fmt.Sprintf(flow, args...)
	}
	tx.exec(OVS_OFCTL, "add-flow", tx.ovsif.bridge, flow)
}

func (tx *ovsExecTx) DeleteFlows(flow string, args ...interface{}) {
	if len(args) > 0 {
		flow = fmt.Sprintf(flow, args...)
	}
	tx.exec(OVS_OFCTL, "del-flows", tx.ovsif.bridge, flow)
}

func (tx *ovsExecTx) EndTransaction() error {
	err := tx.err
	tx.err = nil
	return err
}

func (ovsif *ovsExec) DumpFlows(flow string, args ...interface{}) ([]string, error) {
	if len(args) > 0 {
		flow = fmt.Sprintf(flow, args...)
	}
	out, err := ovsif.exec(OVS_OFCTL, "dump-flows", ovsif.bridge, flow)
	if err != nil {
		return nil, err
	}

	lines := strings.Split(out, "\n")
	flows := make([]string, 0, len(lines))
	for _, line := range lines {
		if strings.Contains(line, "cookie=") {
			flows = append(flows, line)
		}
	}
	return flows, nil
}
<|endoftext|>