<commit_before>package main

import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"strconv"
	"sync"

	"github.com/julienschmidt/httprouter"
)

var shutdownMutex = &sync.Mutex{}
var shutdown bool

func toUint64(value string, dflt uint64) (uint64, error) {
	if value == "" {
		return dflt, nil
	}
	if i, err := strconv.ParseUint(value, 10, 64); err != nil {
		return 0, err
	} else {
		return i, nil
	}
}

func VersionHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	version := Version{
		Version: buildVersion,
		Commit:  buildCommit,
		Date:    buildDate,
		SDK:     buildGoSDK,
	}
	w.Header().Set("Content-type", "application/json")

	data, err := json.Marshal(&version)
	if err != nil {
		version = Version{Meta: Meta{Error: err.Error()}}
		data, _ := json.Marshal(&version)
		w.WriteHeader(500)
		w.Write(data)
		return
	}
	w.Write(data)
}

func TeamsHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	a := make([]Team, 0)
	p := getProjects()
	for _, v := range p {
		a = append(a, Team{Name: v.Team})
	}
	w.Header().Set("Content-type", "application/json")

	teams := Teams{Teams: a}
	data, err := json.Marshal(&teams)
	if err != nil {
		teams := Teams{Meta: Meta{Error: err.Error()}}
		data, _ := json.Marshal(&teams)
		w.WriteHeader(500)
		w.Write(data)
		return
	}
	w.Write(data)
}

func ProjectsHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	team := r.URL.Query().Get("team")
	arr := make([]Project, 0)
	if team != "" {
		for _, v := range getProjects() {
			if team == v.Team {
				arr = append(arr, v)
			}
		}
	} else {
		for _, v := range getProjects() {
			arr = append(arr, v)
		}
	}

	w.Header().Set("Content-type", "application/json")

	p := Projects{Projects: arr}
	data, err := json.Marshal(&p)
	if err != nil {
		p := Projects{Meta: Meta{Error: err.Error()}}
		data, _ := json.Marshal(&p)
		w.WriteHeader(500)
		w.Write(data)
		return
	}
	w.Write(data)
}

func ExecuteBuildHandler(decap Decap) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
		team := params.ByName("team")
		library := params.ByName("library")

		if _, present := projectByTeamLibrary(team, library); !present {
			w.WriteHeader(404)
			return
		}

		branches := r.URL.Query()["branch"]
		if len(branches) == 0 {
			// todo add a message
			w.WriteHeader(400)
			return
		}

		event := UserBuildEvent{TeamFld: team, LibraryFld: library, RefsFld: branches}
		go decap.LaunchBuild(event)
	}
}

func HooksHandler(buildScriptsRepo, buildScriptsBranch string, decap Decap) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
		repoManager := params.ByName("repomanager")

		if r.Body == nil {
			Log.Println("Expecting an HTTP entity")
			w.WriteHeader(400)
			return
		}

		data, err := ioutil.ReadAll(r.Body)
		if err != nil {
			Log.Println(err)
			w.WriteHeader(500)
			return
		}
		defer func() {
			r.Body.Close()
		}()

		Log.Printf("%s hook received: %s\n", repoManager, data)

		switch repoManager {
		case "buildscripts":
			p, err := assembleProjects(buildScriptsRepo, buildScriptsBranch)
			if err != nil {
				Log.Println(err)
				w.WriteHeader(500)
			} else {
				setProjects(p)
			}
		case "github":
			eventType := r.Header.Get("X-Github-Event")
			switch eventType {
			case "create":
				event := GithubEvent{}
				if err := json.Unmarshal(data, &event); err != nil {
					Log.Println(err)
					w.WriteHeader(500)
					return
				}
				go decap.LaunchBuild(event)
			case "push":
				event := GithubEvent{}
				if err := json.Unmarshal(data, &event); err != nil {
					Log.Println(err)
					w.WriteHeader(500)
					return
				}
				go decap.LaunchBuild(event)
			default:
				w.WriteHeader(400)
				return
			}
		default:
			Log.Printf("repomanager %s not supported\n", repoManager)
			w.WriteHeader(400)
			return
		}
		w.WriteHeader(200)
	}
}

// todo Cleanup with PreStop might help explain to the world the state of the container immediately before termination.
// See https://godoc.org/github.com/kubernetes/kubernetes/pkg/api/v1#Lifecycle
func StopBuildHandler(decap Decap) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
		buildID := params.ByName("id")
		if err := decap.DeletePod(buildID); err != nil {
			Log.Println(err)
		}
	}
}

// Handle requests for branches and tags on a project
func ProjectRefsHandler(repoClients map[string]SCMClient) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
		team := params.ByName("team")
		library := params.ByName("library")

		project, present := projectByTeamLibrary(team, library)
		if !present {
			w.WriteHeader(404)
			return
		}

		switch project.Descriptor.RepoManager {
		case "github":
			w.Header().Set("Content-type", "application/json")
			repoClient := repoClients["github"]
			nativeBranches, err := repoClient.GetRefs(project.Team, project.Library)
			if err != nil {
				Log.Print(err)
				data, _ := json.Marshal(&Refs{Meta: Meta{Error: err.Error()}})
				w.WriteHeader(500)
				w.Write(data)
				return
			}

			branches := Refs{Refs: nativeBranches}
			data, err := json.Marshal(&branches)
			if err != nil {
				Log.Print(err)
				data, _ := json.Marshal(&Refs{Meta: Meta{Error: err.Error()}})
				w.WriteHeader(500)
				w.Write(data)
				return
			}

			w.Write(data)
			return
		default:
			w.WriteHeader(400)
		}
	}
}

// Return gzipped console log, or console log in plain text if Accept: text/plain is set
func LogHandler(storageService StorageService) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
		buildID := params.ByName("id")
		data, err := storageService.GetConsoleLog(buildID)
		if err != nil {
			Log.Println(err)
			w.WriteHeader(500)
			return
		}
		if r.Header.Get("Accept") == "text/plain" {
			inputBuffer := bytes.NewBuffer(data)
			source, err := gzip.NewReader(inputBuffer)
			if err != nil {
				Log.Println(err)
				w.WriteHeader(500)
				return
			}
			defer func() {
				source.Close()
			}()

			var outputBuffer = new(bytes.Buffer)
			io.Copy(outputBuffer, source)
			w.Header().Set("Content-type", "text/plain")
			w.Write(outputBuffer.Bytes())

		} else {
			w.Header().Set("Content-type", "application/x-gzip")
			w.Write(data)
		}
	}
}

// Return artifacts gzipped tarball, or file listing in tarball if Accept: text/plain is set
func ArtifactsHandler(storageService StorageService) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
		buildID := params.ByName("id")
		data, err := storageService.GetArtifacts(buildID)
		if err != nil {
			Log.Println(err)
			w.WriteHeader(500)
			return
		}
		if r.Header.Get("Accept") == "text/plain" {
			// unzip
			inputBuffer := bytes.NewBuffer(data)
			source, err := gzip.NewReader(inputBuffer)
			if err != nil {
				Log.Println(err)
				w.WriteHeader(500)
				return
			}
			defer func() {
				source.Close()
			}()

			var outputBuffer = new(bytes.Buffer)
			io.Copy(outputBuffer, source)

			newData := outputBuffer.Bytes()

			// get tar manifest
			plainReader := bytes.NewReader(newData)
			tr := tar.NewReader(plainReader)
			records := ""
			for {
				hdr, err := tr.Next()
				if err == io.EOF {
					break
				}
				if err != nil {
					Log.Println(err)
					w.WriteHeader(500)
					return
				}
				records = fmt.Sprintf("%s\n%s", records, hdr.Name)
			}
			w.Header().Set("Content-type", "text/plain")
			w.Write([]byte(records))
		} else {
			w.Header().Set("Content-type", "application/x-gzip")
			w.Write(data)
		}
	}
}

func BuildsHandler(storageService StorageService) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
		project := params.ByName("team")
		library := params.ByName("library")

		since, err := toUint64(r.URL.Query().Get("since"), 0)
		if err != nil {
			builds := Builds{Meta: Meta{Error: err.Error()}}
			data, _ := json.Marshal(&builds)
			w.WriteHeader(400)
			w.Write(data)
			return
		}

		limit, err := toUint64(r.URL.Query().Get("limit"), 100)
		if err != nil {
			builds := Builds{Meta: Meta{Error: err.Error()}}
			data, _ := json.Marshal(&builds)
			w.WriteHeader(400)
			w.Write(data)
			return
		}

		buildList, err := storageService.GetBuildsByProject(Project{Team: project, Library: library}, since, limit)

		if err != nil {
			builds := Builds{Meta: Meta{Error: err.Error()}}
			data, _ := json.Marshal(&builds)
			w.WriteHeader(502)
			w.Write(data)
			return
		}

		builds := Builds{Builds: buildList}
		data, err := json.Marshal(&builds)
		if err != nil {
			builds := Builds{Meta: Meta{Error: err.Error()}}
			builds.Meta.Error = err.Error()
			data, _ := json.Marshal(&builds)
			w.WriteHeader(500)
			w.Write(data)
			return
		}
		w.Write(data)
	}
}

func ShutdownHandler(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	state := params.ByName("state")
	switch state {
	case "close":
		shutdownMutex.Lock()
		defer shutdownMutex.Unlock()
		shutdown = true
	case "open":
		shutdownMutex.Lock()
		defer shutdownMutex.Unlock()
		shutdown = false
	}
	w.WriteHeader(200)
}
<commit_msg>add generic json error messages to rest handlers where appropriate<commit_after>package main

import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"strconv"
	"sync"

	"github.com/julienschmidt/httprouter"
)

var shutdownMutex = &sync.Mutex{}
var shutdown bool

func toUint64(value string, dflt uint64) (uint64, error) {
	if value == "" {
		return dflt, nil
	}
	if i, err := strconv.ParseUint(value, 10, 64); err != nil {
		return 0, err
	} else {
		return i, nil
	}
}

func VersionHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	version := Version{
		Version: buildVersion,
		Commit:  buildCommit,
		Date:    buildDate,
		SDK:     buildGoSDK,
	}
	w.Header().Set("Content-type", "application/json")

	data, err := json.Marshal(&version)
	if err != nil {
		version = Version{Meta: Meta{Error: err.Error()}}
		data, _ := json.Marshal(&version)
		w.WriteHeader(500)
		w.Write(data)
		return
	}
	w.Write(data)
}

func TeamsHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	a := make([]Team, 0)
	p := getProjects()
	for _, v := range p {
		a = append(a, Team{Name: v.Team})
	}
	w.Header().Set("Content-type", "application/json")

	teams := Teams{Teams: a}
	data, err := json.Marshal(&teams)
	if err != nil {
		teams := Teams{Meta: Meta{Error: err.Error()}}
		data, _ := json.Marshal(&teams)
		w.WriteHeader(500)
		w.Write(data)
		return
	}
	w.Write(data)
}

func ProjectsHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	team := r.URL.Query().Get("team")
	arr := make([]Project, 0)
	if team != "" {
		for _, v := range getProjects() {
			if team == v.Team {
				arr = append(arr, v)
			}
		}
	} else {
		for _, v := range getProjects() {
			arr = append(arr, v)
		}
	}

	w.Header().Set("Content-type", "application/json")

	p := Projects{Projects: arr}
	data, err := json.Marshal(&p)
	if err != nil {
		p := Projects{Meta: Meta{Error: err.Error()}}
		data, _ := json.Marshal(&p)
		w.WriteHeader(500)
		w.Write(data)
		return
	}
	w.Write(data)
}

func ExecuteBuildHandler(decap Decap) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
		team := params.ByName("team")
		library := params.ByName("library")

		if _, present := projectByTeamLibrary(team, library); !present {
			Log.Printf("Unknown project %s/%s", team, library)
			w.WriteHeader(404)
			w.Write(simpleError(fmt.Errorf("Unknown project %s/%s", team, library)))
			return
		}

		branches := r.URL.Query()["branch"]
		if len(branches) == 0 {
			Log.Println("No branches specified")
			w.WriteHeader(400)
			w.Write(simpleError(fmt.Errorf("No branches specified")))
			return
		}

		event := UserBuildEvent{TeamFld: team, LibraryFld: library, RefsFld: branches}
		go decap.LaunchBuild(event)
	}
}

func simpleError(err error) []byte {
	m := Meta{Error: err.Error()}
	data, _ := json.Marshal(&m)
	return data
}

func HooksHandler(buildScriptsRepo, buildScriptsBranch string, decap Decap) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
		repoManager := params.ByName("repomanager")

		if r.Body == nil {
			Log.Println("Expecting an HTTP entity")
			w.WriteHeader(400)
			w.Write(simpleError(fmt.Errorf("Expecting an HTTP entity")))
			return
		}

		data, err := ioutil.ReadAll(r.Body)
		if err != nil {
			Log.Println(err)
			w.WriteHeader(500)
			w.Write(simpleError(err))
			return
		}
		defer func() {
			r.Body.Close()
		}()

		Log.Printf("%s hook received: %s\n", repoManager, data)

		switch repoManager {
		case "buildscripts":
			p, err := assembleProjects(buildScriptsRepo, buildScriptsBranch)
			if err != nil {
				Log.Println(err)
				w.WriteHeader(500)
				w.Write(simpleError(err))
			} else {
				setProjects(p)
			}
		case "github":
			eventType := r.Header.Get("X-Github-Event")
			switch eventType {
			case "create":
				event := GithubEvent{}
				if err := json.Unmarshal(data, &event); err != nil {
					Log.Println(err)
					w.WriteHeader(500)
					w.Write(simpleError(err))
					return
				}
				go decap.LaunchBuild(event)
			case "push":
				event := GithubEvent{}
				if err := json.Unmarshal(data, &event); err != nil {
					Log.Println(err)
					w.WriteHeader(500)
					w.Write(simpleError(err))
					return
				}
				go decap.LaunchBuild(event)
			default:
				w.WriteHeader(400)
				return
			}
		default:
			Log.Printf("repomanager %s not supported\n", repoManager)
			w.WriteHeader(400)
			w.Write(simpleError(fmt.Errorf("repomanager %s not supported", repoManager)))
			return
		}
		w.WriteHeader(200)
	}
}

// todo Cleanup with PreStop might help explain to the world the state of the container immediately before termination.
// See https://godoc.org/github.com/kubernetes/kubernetes/pkg/api/v1#Lifecycle
func StopBuildHandler(decap Decap) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
		buildID := params.ByName("id")
		if err := decap.DeletePod(buildID); err != nil {
			Log.Println(err)
			w.WriteHeader(500)
			w.Write(simpleError(err))
		}
	}
}

// Handle requests for branches and tags on a project
func ProjectRefsHandler(repoClients map[string]SCMClient) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
		team := params.ByName("team")
		library := params.ByName("library")

		project, present := projectByTeamLibrary(team, library)
		if !present {
			w.WriteHeader(404)
			w.Write(simpleError(fmt.Errorf("Unknown project %s/%s", team, library)))
			return
		}

		switch project.Descriptor.RepoManager {
		case "github":
			w.Header().Set("Content-type", "application/json")
			repoClient := repoClients["github"]
			nativeBranches, err := repoClient.GetRefs(project.Team, project.Library)
			if err != nil {
				Log.Print(err)
				data, _ := json.Marshal(&Refs{Meta: Meta{Error: err.Error()}})
				w.WriteHeader(500)
				w.Write(data)
				return
			}

			branches := Refs{Refs: nativeBranches}
			data, err := json.Marshal(&branches)
			if err != nil {
				Log.Print(err)
				data, _ := json.Marshal(&Refs{Meta: Meta{Error: err.Error()}})
				w.WriteHeader(500)
				w.Write(data)
				return
			}

			w.Write(data)
			return
		default:
			w.WriteHeader(400)
		}
	}
}

// Return gzipped console log, or console log in plain text if Accept: text/plain is set
func LogHandler(storageService StorageService) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
		buildID := params.ByName("id")
		data, err := storageService.GetConsoleLog(buildID)
		if err != nil {
			Log.Println(err)
			w.WriteHeader(500)
			w.Write(simpleError(err))
			return
		}
		if r.Header.Get("Accept") == "text/plain" {
			inputBuffer := bytes.NewBuffer(data)
			source, err := gzip.NewReader(inputBuffer)
			if err != nil {
				Log.Println(err)
				w.WriteHeader(500)
				w.Write(simpleError(err))
				return
			}
			defer func() {
				source.Close()
			}()

			var outputBuffer = new(bytes.Buffer)
			io.Copy(outputBuffer, source)
			w.Header().Set("Content-type", "text/plain")
			w.Write(outputBuffer.Bytes())

		} else {
			w.Header().Set("Content-type", "application/x-gzip")
			w.Write(data)
		}
	}
}

// Return artifacts gzipped tarball, or file listing in tarball if Accept: text/plain is set
func ArtifactsHandler(storageService StorageService) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
		buildID := params.ByName("id")
		data, err := storageService.GetArtifacts(buildID)
		if err != nil {
			Log.Println(err)
			w.WriteHeader(500)
			w.Write(simpleError(err))
			return
		}
		if r.Header.Get("Accept") == "text/plain" {
			// unzip
			inputBuffer := bytes.NewBuffer(data)
			source, err := gzip.NewReader(inputBuffer)
			if err != nil {
				Log.Println(err)
				w.WriteHeader(500)
				w.Write(simpleError(err))
				return
			}
			defer func() {
				source.Close()
			}()

			var outputBuffer = new(bytes.Buffer)
			io.Copy(outputBuffer, source)

			newData := outputBuffer.Bytes()

			// get tar manifest
			plainReader := bytes.NewReader(newData)
			tr := tar.NewReader(plainReader)
			records := ""
			for {
				hdr, err := tr.Next()
				if err == io.EOF {
					break
				}
				if err != nil {
					Log.Println(err)
					w.WriteHeader(500)
					w.Write(simpleError(err))
					return
				}
				records = fmt.Sprintf("%s\n%s", records, hdr.Name)
			}
			w.Header().Set("Content-type", "text/plain")
			w.Write([]byte(records))
		} else {
			w.Header().Set("Content-type", "application/x-gzip")
			w.Write(data)
		}
	}
}

func BuildsHandler(storageService StorageService) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
		project := params.ByName("team")
		library := params.ByName("library")

		since, err := toUint64(r.URL.Query().Get("since"), 0)
		if err != nil {
			builds := Builds{Meta: Meta{Error: err.Error()}}
			data, _ := json.Marshal(&builds)
			w.WriteHeader(400)
			w.Write(data)
			return
		}

		limit, err := toUint64(r.URL.Query().Get("limit"), 100)
		if err != nil {
			builds := Builds{Meta: Meta{Error: err.Error()}}
			data, _ := json.Marshal(&builds)
			w.WriteHeader(400)
			w.Write(data)
			return
		}

		buildList, err := storageService.GetBuildsByProject(Project{Team: project, Library: library}, since, limit)

		if err != nil {
			builds := Builds{Meta: Meta{Error: err.Error()}}
			data, _ := json.Marshal(&builds)
			w.WriteHeader(502)
			w.Write(data)
			return
		}

		builds := Builds{Builds: buildList}
		data, err := json.Marshal(&builds)
		if err != nil {
			builds := Builds{Meta: Meta{Error: err.Error()}}
			builds.Meta.Error = err.Error()
			data, _ := json.Marshal(&builds)
			w.WriteHeader(500)
			w.Write(data)
			return
		}
		w.Write(data)
	}
}

func ShutdownHandler(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	state := params.ByName("state")
	switch state {
	case "close":
		shutdownMutex.Lock()
		defer shutdownMutex.Unlock()
		shutdown = true
	case "open":
		shutdownMutex.Lock()
		defer shutdownMutex.Unlock()
		shutdown = false
	}
	w.WriteHeader(200)
}
<|endoftext|>


<commit_before>package websocket

import (
	"sync"

	"github.com/kataras/iris/context"

	"github.com/gorilla/websocket"
)

type connectionKV struct {
	key   string // the connection ID
	value *connection
}

type connections []connectionKV

func (cs *connections) add(key string, value *connection) {
	args := *cs
	n := len(args)
	// check if already id/key exist, if yes replace the conn
	for i := 0; i < n; i++ {
		kv := &args[i]
		if kv.key == key {
			kv.value = value
			return
		}
	}

	c := cap(args)
	// make the connections slice bigger and put the conn
	if c > n {
		args = args[:n+1]
		kv := &args[n]
		kv.key = key
		kv.value = value
		*cs = args
		return
	}
	// append to the connections slice and put the conn
	kv := connectionKV{}
	kv.key = key
	kv.value = value
	*cs = append(args, kv)
}

func (cs *connections) get(key string) *connection {
	args := *cs
	n := len(args)
	for i := 0; i < n; i++ {
		kv := &args[i]
		if kv.key == key {
			return kv.value
		}
	}
	return nil
}

// returns the connection which removed and a bool value of found or not
// the connection is useful to fire the disconnect events, we use that form in order to
// make work things faster without the need of get-remove, just -remove should do the job.
func (cs *connections) remove(key string) (*connection, bool) {
	args := *cs
	n := len(args)
	for i := 0; i < n; i++ {
		kv := &args[i]
		if kv.key == key {
			conn := kv.value
			// we found the index,
			// let's remove the item by appending to the temp and
			// after set the pointer of the slice to this temp args
			args = append(args[:i], args[i+1:]...)
			*cs = args
			return conn, true
		}
	}
	return nil, false
}

type (
	// ConnectionFunc is the callback which fires when a client/connection is connected to the Server.
	// Receives one parameter which is the Connection
	ConnectionFunc func(Connection)

	// websocketRoomPayload is used as payload from the connection to the Server
	websocketRoomPayload struct {
		roomName     string
		connectionID string
	}

	// payloads, connection -> Server
	websocketMessagePayload struct {
		from string
		to   string
		data []byte
	}

	// Server is the websocket Server's implementation.
	//
	// It listens for websocket clients (either from the javascript client-side or from any websocket implementation).
	// See `OnConnection` , to register a single event which will handle all incoming connections and
	// the `Handler` which builds the upgrader handler that you can register to a route based on an Endpoint.
	//
	// To serve the built'n javascript client-side library look the `websocket.ClientHandler`.
	Server struct {
		config                Config
		connections           connections
		rooms                 map[string][]string // by default a connection is joined to a room which has the connection id as its name
		mu                    sync.RWMutex        // for rooms
		onConnectionListeners []ConnectionFunc
		//connectionPool        sync.Pool // sadly we can't make this because the websocket connection is live until is closed.
		upgrader websocket.Upgrader
	}
)

// New returns a new websocket Server based on a configuration.
// See `OnConnection` , to register a single event which will handle all incoming connections and
// the `Handler` which builds the upgrader handler that you can register to a route based on an Endpoint.
//
// To serve the built'n javascript client-side library look the `websocket.ClientHandler`.
func New(cfg Config) *Server {
	cfg = cfg.Validate()
	return &Server{
		config: cfg,
		rooms:  make(map[string][]string, 0),
		onConnectionListeners: make([]ConnectionFunc, 0),
		upgrader: websocket.Upgrader{
			HandshakeTimeout:  cfg.HandshakeTimeout,
			ReadBufferSize:    cfg.ReadBufferSize,
			WriteBufferSize:   cfg.WriteBufferSize,
			Error:             cfg.Error,
			CheckOrigin:       cfg.CheckOrigin,
			Subprotocols:      cfg.Subprotocols,
			EnableCompression: cfg.EnableCompression,
		},
	}
}

// Handler builds the handler based on the configuration and returns it.
// It should be called once per Server, its result should be passed
// as a middleware to an iris route which will be responsible
// to register the websocket's endpoint.
//
// Endpoint is the path which the websocket Server will listen for clients/connections.
//
// To serve the built'n javascript client-side library look the `websocket.ClientHandler`.
func (s *Server) Handler() context.Handler {
	return func(ctx context.Context) {
		c := s.Upgrade(ctx)
		// NOTE TO ME: fire these first BEFORE startReader and startPinger
		// in order to set the events and any messages to send
		// the startPinger will send the OK to the client and only
		// then the client is able to send and receive from Server
		// when all things are ready and only then. DO NOT change this order.

		// fire the on connection event callbacks, if any
		for i := range s.onConnectionListeners {
			s.onConnectionListeners[i](c)
		}

		// start the ping and the messages reader
		c.Wait()
	}
}

// Upgrade upgrades the HTTP Server connection to the WebSocket protocol.
//
// The responseHeader is included in the response to the client's upgrade
// request. Use the responseHeader to specify cookies (Set-Cookie) and the
// application negotiated subprotocol (Sec--Protocol).
//
// If the upgrade fails, then Upgrade replies to the client with an HTTP error
// response and the return `Connection.Err()` is filled with that error.
//
// For a more high-level function use the `Handler()` and `OnConnecton` events.
// This one does not starts the connection's writer and reader, so after your `On/OnMessage` events registration
// the caller has to call the `Connection#Wait` function, otherwise the connection will be not handled.
func (s *Server) Upgrade(ctx context.Context) Connection {
	conn, err := s.upgrader.Upgrade(ctx.ResponseWriter(), ctx.Request(), ctx.ResponseWriter().Header())
	if err != nil {
		ctx.Application().Logger().Warnf("websocket error: %v\n", err)
		ctx.StatusCode(503) // Status Service Unavailable
		return &connection{err: err}
	}

	return s.handleConnection(ctx, conn)
}

// wrapConnection wraps an underline connection to an iris websocket connection.
// It does NOT starts its writer, reader and event mux, the caller is responsible for that.
func (s *Server) handleConnection(ctx context.Context, websocketConn UnderlineConnection) *connection {
	// use the config's id generator (or the default) to create a websocket client/connection id
	cid := s.config.IDGenerator(ctx)
	// create the new connection
	c := newConnection(ctx, s, websocketConn, cid)
	// add the connection to the Server's list
	s.connections.add(cid, c)

	// join to itself
	s.Join(c.ID(), c.ID())

	return c
}

/* Notes:
   We use the id as the signature of the connection because with the custom IDGenerator
	 the developer can share this ID with a database field, so we want to give the oportunnity to handle
	 his/her websocket connections without even use the connection itself.

	 Another question may be:
	 Q: Why you use Server as the main actioner for all of the connection actions?
	 	  For example the Server.Disconnect(connID) manages the connection internal fields, is this code-style correct?
	 A: It's the correct code-style for these type of applications and libraries, Server manages all, the connnection's functions
	 should just do some internal checks (if needed) and push the action to its parent, which is the Server, the Server is able to
	 remove a connection, the rooms of its connected and all these things, so in order to not split the logic, we have the main logic
	 here, in the Server, and let the connection with some exported functions whose exists for the per-connection action user's code-style.

	 Ok my english are s** I can feel it, but these comments are mostly for me.
*/

/*
   connection actions, same as the connection's method,
    but these methods accept the connection ID,
    which is useful when the developer maps
    this id with a database field (using config.IDGenerator).
*/

// OnConnection is the main event you, as developer, will work with each of the websocket connections.
func (s *Server) OnConnection(cb ConnectionFunc) {
	s.onConnectionListeners = append(s.onConnectionListeners, cb)
}

// IsConnected returns true if the connection with that ID is connected to the Server
// useful when you have defined a custom connection id generator (based on a database)
// and you want to check if that connection is already connected (on multiple tabs)
func (s *Server) IsConnected(connID string) bool {
	c := s.connections.get(connID)
	return c != nil
}

// Join joins a websocket client to a room,
// first parameter is the room name and the second the connection.ID()
//
// You can use connection.Join("room name") instead.
func (s *Server) Join(roomName string, connID string) {
	s.mu.Lock()
	s.join(roomName, connID)
	s.mu.Unlock()
}

// join used internally, no locks used.
func (s *Server) join(roomName string, connID string) {
	if s.rooms[roomName] == nil {
		s.rooms[roomName] = make([]string, 0)
	}
	s.rooms[roomName] = append(s.rooms[roomName], connID)
}

// LeaveAll kicks out a connection from ALL of its joined rooms
func (s *Server) LeaveAll(connID string) {
	s.mu.Lock()
	for name, _ := range s.rooms {
		s.leave(name, connID)
	}
	s.mu.Unlock()
}

// Leave leaves a websocket client from a room,
// first parameter is the room name and the second the connection.ID()
//
// You can use connection.Leave("room name") instead.
// Returns true if the connection has actually left from the particular room.
func (s *Server) Leave(roomName string, connID string) bool {
	s.mu.Lock()
	left := s.leave(roomName, connID)
	s.mu.Unlock()
	return left
}

// leave used internally, no locks used.
func (s *Server) leave(roomName string, connID string) (left bool) {
	///THINK: we could add locks to its room but we still use the lock for the whole rooms or we can just do what we do with connections
	// I will think about it on the next revision, so far we use the locks only for rooms so we are ok...
	if s.rooms[roomName] != nil {
		for i := range s.rooms[roomName] {
			if s.rooms[roomName][i] == connID {
				s.rooms[roomName] = append(s.rooms[roomName][:i], s.rooms[roomName][i+1:]...)
				left = true
				break
			}
		}
		if len(s.rooms[roomName]) == 0 { // if room is empty then delete it
			delete(s.rooms, roomName)
		}
	}

	if left {
		// fire the on room leave connection's listeners
		s.connections.get(connID).fireOnLeave(roomName)
	}
	return
}

// GetTotalConnections returns the number of total connections
func (s *Server) GetTotalConnections() int {
	s.mu.RLock()
	l := len(s.connections)
	s.mu.RUnlock()
	return l
}

// GetConnections returns all connections
func (s *Server) GetConnections() []Connection {
	s.mu.RLock()
	conns := make([]Connection, len(s.connections), len(s.connections))
	for i, c := range s.connections {
		conns[i] = c.value
	}
	s.mu.RUnlock()
	return conns
}

// GetConnection returns single connection
func (s *Server) GetConnection(key string) Connection {
	return s.connections.get(key)
}

// GetConnectionsByRoom returns a list of Connection
// which are joined to this room.
func (s *Server) GetConnectionsByRoom(roomName string) []Connection {
	s.mu.Lock()
	var conns []Connection
	if connIDs, found := s.rooms[roomName]; found {
		for _, connID := range connIDs {
			conns = append(conns, s.connections.get(connID))
		}

	}
	s.mu.Unlock()
	return conns
}

// emitMessage is the main 'router' of the messages coming from the connection
// this is the main function which writes the RAW websocket messages to the client.
// It sends them(messages) to the correct room (self, broadcast or to specific client)
//
// You don't have to use this generic method, exists only for extreme
// apps which you have an external goroutine with a list of custom connection list.
//
// You SHOULD use connection.EmitMessage/Emit/To().Emit/EmitMessage instead.
// let's keep it unexported for the best.
func (s *Server) emitMessage(from, to string, data []byte) {
	if to != All && to != Broadcast && s.rooms[to] != nil {
		// it suppose to send the message to a specific room/or a user inside its own room
		for _, connectionIDInsideRoom := range s.rooms[to] {
			if c := s.connections.get(connectionIDInsideRoom); c != nil {
				c.writeDefault(data) //send the message to the client(s)
			} else {
				// the connection is not connected but it's inside the room, we remove it on disconnect but for ANY CASE:
				cid := connectionIDInsideRoom
				if c != nil {
					cid = c.id
				}
				s.Leave(cid, to)
			}
		}
	} else {
		// it suppose to send the message to all opened connections or to all except the sender
		for _, cKV := range s.connections {
			connID := cKV.key
			if to != All && to != connID { // if it's not suppose to send to all connections (including itself)
				if to == Broadcast && from == connID { // if broadcast to other connections except this
					continue //here we do the opossite of previous block,
					// just skip this connection when it's suppose to send the message to all connections except the sender
				}

			}
			// send to the client(s) when the top validators passed
			cKV.value.writeDefault(data)
		}
	}
}

// Disconnect force-disconnects a websocket connection based on its connection.ID()
// What it does?
// 1. remove the connection from the list
// 2. leave from all joined rooms
// 3. fire the disconnect callbacks, if any
// 4. close the underline connection and return its error, if any.
//
// You can use the connection.Disconnect() instead.
func (s *Server) Disconnect(connID string) (err error) {
	// leave from all joined rooms before remove the actual connection from the list.
	// note: we cannot use that to send data if the client is actually closed.
	s.LeaveAll(connID)

	// remove the connection from the list
	if c, ok := s.connections.remove(connID); ok {
		if !c.disconnected {
			c.disconnected = true

			// fire the disconnect callbacks, if any
			c.fireDisconnect()
			// close the underline connection and return its error, if any.
			err = c.underline.Close()
		}
	}

	return
}
<commit_msg>a simple simplify came from the previous PR that merged<commit_after>package websocket

import (
	"sync"

	"github.com/kataras/iris/context"

	"github.com/gorilla/websocket"
)

type connectionKV struct {
	key   string // the connection ID
	value *connection
}

type connections []connectionKV

func (cs *connections) add(key string, value *connection) {
	args := *cs
	n := len(args)
	// check if already id/key exist, if yes replace the conn
	for i := 0; i < n; i++ {
		kv := &args[i]
		if kv.key == key {
			kv.value = value
			return
		}
	}

	c := cap(args)
	// make the connections slice bigger and put the conn
	if c > n {
		args = args[:n+1]
		kv := &args[n]
		kv.key = key
		kv.value = value
		*cs = args
		return
	}
	// append to the connections slice and put the conn
	kv := connectionKV{}
	kv.key = key
	kv.value = value
	*cs = append(args, kv)
}

func (cs *connections) get(key string) *connection {
	args := *cs
	n := len(args)
	for i := 0; i < n; i++ {
		kv := &args[i]
		if kv.key == key {
			return kv.value
		}
	}
	return nil
}

// returns the connection which removed and a bool value of found or not
// the connection is useful to fire the disconnect events, we use that form in order to
// make work things faster without the need of get-remove, just -remove should do the job.
func (cs *connections) remove(key string) (*connection, bool) {
	args := *cs
	n := len(args)
	for i := 0; i < n; i++ {
		kv := &args[i]
		if kv.key == key {
			conn := kv.value
			// we found the index,
			// let's remove the item by appending to the temp and
			// after set the pointer of the slice to this temp args
			args = append(args[:i], args[i+1:]...)
			*cs = args
			return conn, true
		}
	}
	return nil, false
}

type (
	// ConnectionFunc is the callback which fires when a client/connection is connected to the Server.
	// Receives one parameter which is the Connection
	ConnectionFunc func(Connection)

	// websocketRoomPayload is used as payload from the connection to the Server
	websocketRoomPayload struct {
		roomName     string
		connectionID string
	}

	// payloads, connection -> Server
	websocketMessagePayload struct {
		from string
		to   string
		data []byte
	}

	// Server is the websocket Server's implementation.
	//
	// It listens for websocket clients (either from the javascript client-side or from any websocket implementation).
	// See `OnConnection` , to register a single event which will handle all incoming connections and
	// the `Handler` which builds the upgrader handler that you can register to a route based on an Endpoint.
	//
	// To serve the built'n javascript client-side library look the `websocket.ClientHandler`.
	Server struct {
		config                Config
		connections           connections
		rooms                 map[string][]string // by default a connection is joined to a room which has the connection id as its name
		mu                    sync.RWMutex        // for rooms
		onConnectionListeners []ConnectionFunc
		//connectionPool        sync.Pool // sadly we can't make this because the websocket connection is live until is closed.
		upgrader websocket.Upgrader
	}
)

// New returns a new websocket Server based on a configuration.
// See `OnConnection` , to register a single event which will handle all incoming connections and
// the `Handler` which builds the upgrader handler that you can register to a route based on an Endpoint.
//
// To serve the built'n javascript client-side library look the `websocket.ClientHandler`.
func New(cfg Config) *Server {
	cfg = cfg.Validate()
	return &Server{
		config: cfg,
		rooms:  make(map[string][]string, 0),
		onConnectionListeners: make([]ConnectionFunc, 0),
		upgrader: websocket.Upgrader{
			HandshakeTimeout:  cfg.HandshakeTimeout,
			ReadBufferSize:    cfg.ReadBufferSize,
			WriteBufferSize:   cfg.WriteBufferSize,
			Error:             cfg.Error,
			CheckOrigin:       cfg.CheckOrigin,
			Subprotocols:      cfg.Subprotocols,
			EnableCompression: cfg.EnableCompression,
		},
	}
}

// Handler builds the handler based on the configuration and returns it.
// It should be called once per Server, its result should be passed
// as a middleware to an iris route which will be responsible
// to register the websocket's endpoint.
//
// Endpoint is the path which the websocket Server will listen for clients/connections.
//
// To serve the built'n javascript client-side library look the `websocket.ClientHandler`.
func (s *Server) Handler() context.Handler {
	return func(ctx context.Context) {
		c := s.Upgrade(ctx)
		// NOTE TO ME: fire these first BEFORE startReader and startPinger
		// in order to set the events and any messages to send
		// the startPinger will send the OK to the client and only
		// then the client is able to send and receive from Server
		// when all things are ready and only then. DO NOT change this order.

		// fire the on connection event callbacks, if any
		for i := range s.onConnectionListeners {
			s.onConnectionListeners[i](c)
		}

		// start the ping and the messages reader
		c.Wait()
	}
}

// Upgrade upgrades the HTTP Server connection to the WebSocket protocol.
//
// The responseHeader is included in the response to the client's upgrade
// request. Use the responseHeader to specify cookies (Set-Cookie) and the
// application negotiated subprotocol (Sec--Protocol).
//
// If the upgrade fails, then Upgrade replies to the client with an HTTP error
// response and the return `Connection.Err()` is filled with that error.
//
// For a more high-level function use the `Handler()` and `OnConnecton` events.
// This one does not starts the connection's writer and reader, so after your `On/OnMessage` events registration
// the caller has to call the `Connection#Wait` function, otherwise the connection will be not handled.
func (s *Server) Upgrade(ctx context.Context) Connection {
	conn, err := s.upgrader.Upgrade(ctx.ResponseWriter(), ctx.Request(), ctx.ResponseWriter().Header())
	if err != nil {
		ctx.Application().Logger().Warnf("websocket error: %v\n", err)
		ctx.StatusCode(503) // Status Service Unavailable
		return &connection{err: err}
	}

	return s.handleConnection(ctx, conn)
}

// wrapConnection wraps an underline connection to an iris websocket connection.
// It does NOT starts its writer, reader and event mux, the caller is responsible for that.
func (s *Server) handleConnection(ctx context.Context, websocketConn UnderlineConnection) *connection {
	// use the config's id generator (or the default) to create a websocket client/connection id
	cid := s.config.IDGenerator(ctx)
	// create the new connection
	c := newConnection(ctx, s, websocketConn, cid)
	// add the connection to the Server's list
	s.connections.add(cid, c)

	// join to itself
	s.Join(c.ID(), c.ID())

	return c
}

/* Notes:
   We use the id as the signature of the connection because with the custom IDGenerator
	 the developer can share this ID with a database field, so we want to give the oportunnity to handle
	 his/her websocket connections without even use the connection itself.

	 Another question may be:
	 Q: Why you use Server as the main actioner for all of the connection actions?
	 	  For example the Server.Disconnect(connID) manages the connection internal fields, is this code-style correct?
	 A: It's the correct code-style for these type of applications and libraries, Server manages all, the connnection's functions
	 should just do some internal checks (if needed) and push the action to its parent, which is the Server, the Server is able to
	 remove a connection, the rooms of its connected and all these things, so in order to not split the logic, we have the main logic
	 here, in the Server, and let the connection with some exported functions whose exists for the per-connection action user's code-style.

	 Ok my english are s** I can feel it, but these comments are mostly for me.
*/

/*
   connection actions, same as the connection's method,
    but these methods accept the connection ID,
    which is useful when the developer maps
    this id with a database field (using config.IDGenerator).
*/

// OnConnection is the main event you, as developer, will work with each of the websocket connections.
func (s *Server) OnConnection(cb ConnectionFunc) {
	s.onConnectionListeners = append(s.onConnectionListeners, cb)
}

// IsConnected returns true if the connection with that ID is connected to the Server
// useful when you have defined a custom connection id generator (based on a database)
// and you want to check if that connection is already connected (on multiple tabs)
func (s *Server) IsConnected(connID string) bool {
	c := s.connections.get(connID)
	return c != nil
}

// Join joins a websocket client to a room,
// first parameter is the room name and the second the connection.ID()
//
// You can use connection.Join("room name") instead.
func (s *Server) Join(roomName string, connID string) {
	s.mu.Lock()
	s.join(roomName, connID)
	s.mu.Unlock()
}

// join used internally, no locks used.
func (s *Server) join(roomName string, connID string) {
	if s.rooms[roomName] == nil {
		s.rooms[roomName] = make([]string, 0)
	}
	s.rooms[roomName] = append(s.rooms[roomName], connID)
}

// LeaveAll kicks out a connection from ALL of its joined rooms
func (s *Server) LeaveAll(connID string) {
	s.mu.Lock()
	for name := range s.rooms {
		s.leave(name, connID)
	}
	s.mu.Unlock()
}

// Leave leaves a websocket client from a room,
// first parameter is the room name and the second the connection.ID()
//
// You can use connection.Leave("room name") instead.
// Returns true if the connection has actually left from the particular room.
func (s *Server) Leave(roomName string, connID string) bool {
	s.mu.Lock()
	left := s.leave(roomName, connID)
	s.mu.Unlock()
	return left
}

// leave used internally, no locks used.
func (s *Server) leave(roomName string, connID string) (left bool) {
	///THINK: we could add locks to its room but we still use the lock for the whole rooms or we can just do what we do with connections
	// I will think about it on the next revision, so far we use the locks only for rooms so we are ok...
	if s.rooms[roomName] != nil {
		for i := range s.rooms[roomName] {
			if s.rooms[roomName][i] == connID {
				s.rooms[roomName] = append(s.rooms[roomName][:i], s.rooms[roomName][i+1:]...)
				left = true
				break
			}
		}
		if len(s.rooms[roomName]) == 0 { // if room is empty then delete it
			delete(s.rooms, roomName)
		}
	}

	if left {
		// fire the on room leave connection's listeners
		s.connections.get(connID).fireOnLeave(roomName)
	}
	return
}

// GetTotalConnections returns the number of total connections
func (s *Server) GetTotalConnections() int {
	s.mu.RLock()
	l := len(s.connections)
	s.mu.RUnlock()
	return l
}

// GetConnections returns all connections
func (s *Server) GetConnections() []Connection {
	s.mu.RLock()
	conns := make([]Connection, len(s.connections), len(s.connections))
	for i, c := range s.connections {
		conns[i] = c.value
	}
	s.mu.RUnlock()
	return conns
}

// GetConnection returns single connection
func (s *Server) GetConnection(key string) Connection {
	return s.connections.get(key)
}

// GetConnectionsByRoom returns a list of Connection
// which are joined to this room.
func (s *Server) GetConnectionsByRoom(roomName string) []Connection {
	s.mu.Lock()
	var conns []Connection
	if connIDs, found := s.rooms[roomName]; found {
		for _, connID := range connIDs {
			conns = append(conns, s.connections.get(connID))
		}

	}
	s.mu.Unlock()
	return conns
}

// emitMessage is the main 'router' of the messages coming from the connection
// this is the main function which writes the RAW websocket messages to the client.
// It sends them(messages) to the correct room (self, broadcast or to specific client)
//
// You don't have to use this generic method, exists only for extreme
// apps which you have an external goroutine with a list of custom connection list.
//
// You SHOULD use connection.EmitMessage/Emit/To().Emit/EmitMessage instead.
// let's keep it unexported for the best.
func (s *Server) emitMessage(from, to string, data []byte) {
	if to != All && to != Broadcast && s.rooms[to] != nil {
		// it suppose to send the message to a specific room/or a user inside its own room
		for _, connectionIDInsideRoom := range s.rooms[to] {
			if c := s.connections.get(connectionIDInsideRoom); c != nil {
				c.writeDefault(data) //send the message to the client(s)
			} else {
				// the connection is not connected but it's inside the room, we remove it on disconnect but for ANY CASE:
				cid := connectionIDInsideRoom
				if c != nil {
					cid = c.id
				}
				s.Leave(cid, to)
			}
		}
	} else {
		// it suppose to send the message to all opened connections or to all except the sender
		for _, cKV := range s.connections {
			connID := cKV.key
			if to != All && to != connID { // if it's not suppose to send to all connections (including itself)
				if to == Broadcast && from == connID { // if broadcast to other connections except this
					continue //here we do the opossite of previous block,
					// just skip this connection when it's suppose to send the message to all connections except the sender
				}

			}
			// send to the client(s) when the top validators passed
			cKV.value.writeDefault(data)
		}
	}
}

// Disconnect force-disconnects a websocket connection based on its connection.ID()
// What it does?
// 1. remove the connection from the list
// 2. leave from all joined rooms
// 3. fire the disconnect callbacks, if any
// 4. close the underline connection and return its error, if any.
//
// You can use the connection.Disconnect() instead.
func (s *Server) Disconnect(connID string) (err error) {
	// leave from all joined rooms before remove the actual connection from the list.
	// note: we cannot use that to send data if the client is actually closed.
	s.LeaveAll(connID)

	// remove the connection from the list
	if c, ok := s.connections.remove(connID); ok {
		if !c.disconnected {
			c.disconnected = true

			// fire the disconnect callbacks, if any
			c.fireDisconnect()
			// close the underline connection and return its error, if any.
			err = c.underline.Close()
		}
	}

	return
}
<|endoftext|>


<commit_before>package drivers

import (
	"fmt"
	"sync"

	"github.com/pkg/errors"

	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/device"
	deviceConfig "github.com/lxc/lxd/lxd/device/config"
	"github.com/lxc/lxd/lxd/instance"
	"github.com/lxc/lxd/lxd/instance/instancetype"
	"github.com/lxc/lxd/lxd/revert"
	"github.com/lxc/lxd/lxd/state"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	log "github.com/lxc/lxd/shared/log15"
	"github.com/lxc/lxd/shared/logger"
)

// Instance driver definitions.
var instanceDrivers = map[string]func() instance.Instance{
	"lxc":  func() instance.Instance { return &lxc{} },
	"qemu": func() instance.Instance { return &qemu{} },
}

// Supported instance types cache variables.
var supportedInstanceTypesMu sync.Mutex
var supportedInstanceTypes map[instancetype.Type]instance.Info

func init() {
	// Expose load to the instance package, to avoid circular imports.
	instance.Load = load

	// Expose validDevices to the instance package, to avoid circular imports.
	instance.ValidDevices = validDevices

	// Expose create to the instance package, to avoid circular imports.
	instance.Create = create
}

// load creates the underlying instance type struct and returns it as an Instance.
func load(s *state.State, args db.InstanceArgs, profiles []api.Profile) (instance.Instance, error) {
	var inst instance.Instance
	var err error

	if args.Type == instancetype.Container {
		inst, err = lxcLoad(s, args, profiles)
	} else if args.Type == instancetype.VM {
		inst, err = qemuLoad(s, args, profiles)
	} else {
		return nil, fmt.Errorf("Invalid instance type for instance %s", args.Name)
	}

	if err != nil {
		return nil, err
	}

	return inst, nil
}

// validDevices validate instance device configs.
func validDevices(state *state.State, cluster *db.Cluster, projectName string, instanceType instancetype.Type, devices deviceConfig.Devices, expanded bool) error {
	// Empty device list
	if devices == nil {
		return nil
	}

	instConf := &common{
		dbType:       instanceType,
		localDevices: devices.Clone(),
		project:      projectName,
	}

	// In non-expanded validation expensive checks should be avoided.
	if expanded {
		// The devices being validated are already expanded, so just use the same
		// devices clone as we used for the main devices config.
		instConf.expandedDevices = instConf.localDevices
	}

	// Check each device individually using the device package.
	// Use instConf.localDevices so that the cloned config is passed into the driver, so it cannot modify it.
	for name, config := range instConf.localDevices {
		err := device.Validate(instConf, state, name, config)
		if err != nil {
			return errors.Wrapf(err, "Device validation failed for %q", name)
		}

	}

	// Check we have a root disk if in expanded validation mode.
	if expanded {
		_, _, err := shared.GetRootDiskDevice(devices.CloneNative())
		if err != nil {
			return errors.Wrap(err, "Failed detecting root disk device")
		}
	}

	return nil
}

func create(s *state.State, args db.InstanceArgs, revert *revert.Reverter) (instance.Instance, error) {
	if args.Type == instancetype.Container {
		return lxcCreate(s, args, revert)
	} else if args.Type == instancetype.VM {
		return qemuCreate(s, args, revert)
	}

	return nil, fmt.Errorf("Instance type invalid")
}

// SupportedInstanceTypes returns a map of Info structs for all operational instance type drivers.
// The first time this function is called each of the instance drivers will be probed for support and the result
// will be cached internally to make subsequent calls faster.
func SupportedInstanceTypes() map[instancetype.Type]instance.Info {
	supportedInstanceTypesMu.Lock()
	defer supportedInstanceTypesMu.Unlock()

	if supportedInstanceTypes != nil {
		return supportedInstanceTypes
	}

	supportedInstanceTypes = make(map[instancetype.Type]instance.Info, len(instanceDrivers))

	for _, instanceDriver := range instanceDrivers {
		driverInfo := instanceDriver().Info()

		if driverInfo.Error != nil || driverInfo.Version == "" {
			logger.Warn("Instance type not operational", log.Ctx{"type": driverInfo.Type, "driver": driverInfo.Name, "err": driverInfo.Error})

			continue
		}

		supportedInstanceTypes[driverInfo.Type] = driverInfo
	}

	return supportedInstanceTypes
}
<commit_msg>lxd/instance/drivers/load: Adds volumeConfig arg to create<commit_after>package drivers

import (
	"fmt"
	"sync"

	"github.com/pkg/errors"

	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/device"
	deviceConfig "github.com/lxc/lxd/lxd/device/config"
	"github.com/lxc/lxd/lxd/instance"
	"github.com/lxc/lxd/lxd/instance/instancetype"
	"github.com/lxc/lxd/lxd/revert"
	"github.com/lxc/lxd/lxd/state"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	log "github.com/lxc/lxd/shared/log15"
	"github.com/lxc/lxd/shared/logger"
)

// Instance driver definitions.
var instanceDrivers = map[string]func() instance.Instance{
	"lxc":  func() instance.Instance { return &lxc{} },
	"qemu": func() instance.Instance { return &qemu{} },
}

// Supported instance types cache variables.
var supportedInstanceTypesMu sync.Mutex
var supportedInstanceTypes map[instancetype.Type]instance.Info

func init() {
	// Expose load to the instance package, to avoid circular imports.
	instance.Load = load

	// Expose validDevices to the instance package, to avoid circular imports.
	instance.ValidDevices = validDevices

	// Expose create to the instance package, to avoid circular imports.
	instance.Create = create
}

// load creates the underlying instance type struct and returns it as an Instance.
func load(s *state.State, args db.InstanceArgs, profiles []api.Profile) (instance.Instance, error) {
	var inst instance.Instance
	var err error

	if args.Type == instancetype.Container {
		inst, err = lxcLoad(s, args, profiles)
	} else if args.Type == instancetype.VM {
		inst, err = qemuLoad(s, args, profiles)
	} else {
		return nil, fmt.Errorf("Invalid instance type for instance %s", args.Name)
	}

	if err != nil {
		return nil, err
	}

	return inst, nil
}

// validDevices validate instance device configs.
func validDevices(state *state.State, cluster *db.Cluster, projectName string, instanceType instancetype.Type, devices deviceConfig.Devices, expanded bool) error {
	// Empty device list
	if devices == nil {
		return nil
	}

	instConf := &common{
		dbType:       instanceType,
		localDevices: devices.Clone(),
		project:      projectName,
	}

	// In non-expanded validation expensive checks should be avoided.
	if expanded {
		// The devices being validated are already expanded, so just use the same
		// devices clone as we used for the main devices config.
		instConf.expandedDevices = instConf.localDevices
	}

	// Check each device individually using the device package.
	// Use instConf.localDevices so that the cloned config is passed into the driver, so it cannot modify it.
	for name, config := range instConf.localDevices {
		err := device.Validate(instConf, state, name, config)
		if err != nil {
			return errors.Wrapf(err, "Device validation failed for %q", name)
		}

	}

	// Check we have a root disk if in expanded validation mode.
	if expanded {
		_, _, err := shared.GetRootDiskDevice(devices.CloneNative())
		if err != nil {
			return errors.Wrap(err, "Failed detecting root disk device")
		}
	}

	return nil
}

func create(s *state.State, args db.InstanceArgs, volumeConfig map[string]string, revert *revert.Reverter) (instance.Instance, error) {
	if args.Type == instancetype.Container {
		return lxcCreate(s, args, volumeConfig, revert)
	} else if args.Type == instancetype.VM {
		return qemuCreate(s, args, volumeConfig, revert)
	}

	return nil, fmt.Errorf("Instance type invalid")
}

// SupportedInstanceTypes returns a map of Info structs for all operational instance type drivers.
// The first time this function is called each of the instance drivers will be probed for support and the result
// will be cached internally to make subsequent calls faster.
func SupportedInstanceTypes() map[instancetype.Type]instance.Info {
	supportedInstanceTypesMu.Lock()
	defer supportedInstanceTypesMu.Unlock()

	if supportedInstanceTypes != nil {
		return supportedInstanceTypes
	}

	supportedInstanceTypes = make(map[instancetype.Type]instance.Info, len(instanceDrivers))

	for _, instanceDriver := range instanceDrivers {
		driverInfo := instanceDriver().Info()

		if driverInfo.Error != nil || driverInfo.Version == "" {
			logger.Warn("Instance type not operational", log.Ctx{"type": driverInfo.Type, "driver": driverInfo.Name, "err": driverInfo.Error})

			continue
		}

		supportedInstanceTypes[driverInfo.Type] = driverInfo
	}

	return supportedInstanceTypes
}
<|endoftext|>


<commit_before>package fuse

import (
	"bytes"
	"errors"
	"fmt"
	"log"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"runtime"
	"strings"

	"koding/kites/config"
	konfig "koding/klient/config"
	"koding/klient/fs"
	"koding/klient/machine/index"
	"koding/klient/machine/mount/notify"

	"github.com/jacobsa/fuse"
	"github.com/jacobsa/fuse/fuseutil"
	"github.com/koding/logging"
	"golang.org/x/net/context"
)

// TODO(rjeczalik): add promise update ops, so attrs updates are served
// right away, without waiting for index update.

// TODO(rjeczalik): symlink support: add node.Entry.Nlink field and
// add support for in Unlink, ForgotInode and CreateSymlink methods.

// Builder provides a default notify.Builder for the FUSE filesystem.
var Builder notify.Builder = builder{}

type builder struct{}

// Build implements the notify.Builder interface.
func (builder) Build(opts *notify.BuildOpts) (notify.Notifier, error) {
	di, err := fs.Statfs(opts.CacheDir)
	if err != nil {
		return nil, err
	}

	o := &Options{
		Index:    opts.Index,
		Disk:     di,
		Cache:    opts.Cache,
		CacheDir: opts.CacheDir,
		Mount:    filepath.Base(opts.Path),
		MountDir: opts.Path,
		// intentionally separate env to not enable fuse logging
		// for regular kd debug
		Debug: konfig.Konfig.Mount.Debug >= 9,
		Log:   opts.Log,
	}

	if err := o.Valid(); err != nil {
		return nil, err
	}

	return NewFilesystem(o)
}

// Options configures FUSE filesystem.
type Options struct {
	Index    *index.Index   // metadata index
	Disk     *fs.DiskInfo   // filesystem information
	Cache    notify.Cache   // used to request cache updates
	CacheDir string         // path of the cache directory of the mount
	Mount    string         // name of the mount
	MountDir string         // path of the mount directory
	User     *config.User   // owner of the mount; if nil, config.CurrentUser is used
	Debug    bool           // turns on fuse debug logging
	Log      logging.Logger // log mount specific info
}

// Valid checks if provided options are valid.
func (o *Options) Valid() error {
	const sep = string(os.PathSeparator)

	if o.Index == nil {
		return errors.New("index is nil")
	}
	if o.Disk == nil {
		return errors.New("disk info is nil")
	}
	if o.Cache == nil {
		return errors.New("cache is nil")
	}
	if o.MountDir == "" {
		return errors.New("mount directory is empty")
	}
	if o.CacheDir == "" {
		return errors.New("cache directory is empty")
	}
	if !strings.HasSuffix(o.CacheDir, sep) {
		o.CacheDir = o.CacheDir + sep
	}
	if !strings.HasSuffix(o.MountDir, sep) {
		o.MountDir = o.MountDir + sep
	}

	return nil
}

func (o *Options) user() *config.User {
	if o.User != nil {
		return o.User
	}
	return config.CurrentUser
}

var _ fuseutil.FileSystem = (*Filesystem)(nil)

// Filesystem implements fuseutil.FileSystem.
//
// List operations are backend by index.
// Write and read operations are backed by cache,
// which is populated lazily by notify.Cache.
type Filesystem struct {
	// NotImplementedFileSystem provides stubs for the following methods:
	//
	//   - MkNode
	//   - CreateSymlink
	//   - ReadSymlink
	//
	fuseutil.NotImplementedFileSystem
	Options

	// Cancel the background context of all fuse operatins.
	cancel func()

	// Dynamic filesystem state.
	dirHandles  *DirHandleGroup
	fileHandles *FileHandleGroup
}

// FSWrapFunc allows to attach middlerares to underling filesystems.
type FSWrapFunc func(fuseutil.FileSystem) fuseutil.FileSystem

// NewFilesystem creates new Filesystem value.
func NewFilesystem(opts *Options, wraps ...FSWrapFunc) (*Filesystem, error) {
	if err := opts.Valid(); err != nil {
		return nil, err
	}

	// Best-effort attempt of unmounting already existing mount.
	_ = Umount(opts.MountDir)

	// Ignore mkdir errors since it can return `file exists` error. Other errors
	// will cause FUSE backend fail.
	_ = os.MkdirAll(opts.MountDir, 0755)

	ctx, cancel := context.WithCancel(context.Background())

	gen := generator(MinHandleID)
	fs := &Filesystem{
		Options:     *opts,
		cancel:      cancel,
		dirHandles:  NewDirHandleGroup(opts.MountDir, gen),
		fileHandles: NewFileHandleGroup(gen),
	}

	// Attach additional filesystem wrappers if any.
	var fuseFS fuseutil.FileSystem = fs
	for _, wrap := range wraps {
		fuseFS = wrap(fuseFS)
	}

	cfg := fs.Config()

	// Debug information about filesystem.
	if opts.Log != nil {
		// User information.
		var userInfo string
		if u, err := user.Current(); err == nil {
			userInfo = fmt.Sprintf("%s, home: %s", u.Username, u.HomeDir)
		} else {
			userInfo = "cannot obtain user info: " + err.Error()
		}

		opts.Log.Info("Dir: %s; CacheDir: %s; Mounter: %s; Opts: %s; Usr: %s",
			opts.MountDir, opts.CacheDir, getFuserMountVer(), toOptionsString(cfg), userInfo)
	}

	m, err := fuse.Mount(opts.MountDir, fuseutil.NewFileSystemServer(fuseFS), cfg)
	if err != nil {
		return nil, err
	}

	go m.Join(ctx)

	return fs, nil
}

// Config constructs fuse configuration.
func (fs *Filesystem) Config() *fuse.MountConfig {
	var logger *log.Logger

	if fs.Debug {
		logger = log.New(os.Stderr, "fuse", log.LstdFlags|log.Lshortfile)
	}

	return &fuse.MountConfig{
		FSName:                  fs.Mount,
		VolumeName:              filepath.Base(fs.MountDir),
		DisableWritebackCaching: true,
		EnableVnodeCaching:      false,
		Options: map[string]string{
			"allow_other": "",
			"local":       "",
			"auto_xattr":  "",
		},
		DebugLogger: logger,
		ErrorLogger: logger,
	}
}

// Close implements the notify.Notifier interface. It cleans up filesystem
// resources and tries to umount created device.
func (fs *Filesystem) Close() error {
	fs.cancel()
	fs.Destroy()

	return Umount(fs.MountDir)
}

// Umount unmounts FUSE filesystem.
func Umount(dir string) error {
	umountCmd := func(name string, args ...string) error {
		if p, err := exec.Command(name, args...).CombinedOutput(); err != nil {
			return fmt.Errorf("%s: %s", err, bytes.TrimSpace(p))
		}

		return nil
	}

	if runtime.GOOS == "linux" {
		if err := fuse.Unmount(dir); err != nil {
			return umountCmd("fusermount", "-uz", dir) // Try lazy umount.
		}
		return nil
	}

	// Under Darwin fuse.Umount uses syscall.Umount without syscall.MNT_FORCE flag,
	// so we replace that implementation with diskutil.
	return umountCmd("diskutil", "unmount", "force", dir)
}

func getFuserMountVer() string {
	const fm = "fusermount"

	if runtime.GOOS == "linux" {
		return "unsupported OS " + runtime.GOOS
	}

	if _, err := exec.LookPath(fm); err != nil {
		return "cannot find " + fm + " executable in PATH"
	}

	p, err := exec.Command(fm, "--version").CombinedOutput()
	if err != nil {
		return "unknown " + fm + "version: " + err.Error()
	}

	return strings.TrimSpace(string(p))
}

// Create a map containing all of the key=value mount options to be given to
// the mount helper.
//
// This function is copied from jacobsa/fuse and is meant to provide exact
// command arguments passed to fusermount.
func toMap(c *fuse.MountConfig) (opts map[string]string) {
	isDarwin := runtime.GOOS == "darwin"
	opts = make(map[string]string)

	opts["default_permissions"] = ""
	fsname := c.FSName
	if runtime.GOOS == "linux" && fsname == "" {
		fsname = "some_fuse_file_system"
	}

	// Special file system name?
	if fsname != "" {
		opts["fsname"] = fsname
	}

	// Read only?
	if c.ReadOnly {
		opts["ro"] = ""
	}

	// Handle OS X options.
	if isDarwin {
		if !c.EnableVnodeCaching {
			opts["novncache"] = ""
		}

		if c.VolumeName != "" {
			opts["volname"] = c.VolumeName
		}
	}

	if isDarwin {
		opts["noappledouble"] = ""
	}

	for k, v := range c.Options {
		opts[k] = v
	}

	return
}

func escapeOptionsKey(s string) (res string) {
	res = s
	res = strings.Replace(res, `\`, `\\`, -1)
	res = strings.Replace(res, `,`, `\,`, -1)
	return
}

// Create an options string suitable for passing to the mount helper.
func toOptionsString(c *fuse.MountConfig) string {
	var components []string
	for k, v := range toMap(c) {
		k = escapeOptionsKey(k)

		component := k
		if v != "" {
			component = fmt.Sprintf("%s=%s", k, v)
		}

		components = append(components, component)
	}

	return strings.Join(components, ",")
}
<commit_msg>klient/fuse: get fusermount version only on linux<commit_after>package fuse

import (
	"bytes"
	"errors"
	"fmt"
	"log"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"runtime"
	"strings"

	"koding/kites/config"
	konfig "koding/klient/config"
	"koding/klient/fs"
	"koding/klient/machine/index"
	"koding/klient/machine/mount/notify"

	"github.com/jacobsa/fuse"
	"github.com/jacobsa/fuse/fuseutil"
	"github.com/koding/logging"
	"golang.org/x/net/context"
)

// TODO(rjeczalik): add promise update ops, so attrs updates are served
// right away, without waiting for index update.

// TODO(rjeczalik): symlink support: add node.Entry.Nlink field and
// add support for in Unlink, ForgotInode and CreateSymlink methods.

// Builder provides a default notify.Builder for the FUSE filesystem.
var Builder notify.Builder = builder{}

type builder struct{}

// Build implements the notify.Builder interface.
func (builder) Build(opts *notify.BuildOpts) (notify.Notifier, error) {
	di, err := fs.Statfs(opts.CacheDir)
	if err != nil {
		return nil, err
	}

	o := &Options{
		Index:    opts.Index,
		Disk:     di,
		Cache:    opts.Cache,
		CacheDir: opts.CacheDir,
		Mount:    filepath.Base(opts.Path),
		MountDir: opts.Path,
		// intentionally separate env to not enable fuse logging
		// for regular kd debug
		Debug: konfig.Konfig.Mount.Debug >= 9,
		Log:   opts.Log,
	}

	if err := o.Valid(); err != nil {
		return nil, err
	}

	return NewFilesystem(o)
}

// Options configures FUSE filesystem.
type Options struct {
	Index    *index.Index   // metadata index
	Disk     *fs.DiskInfo   // filesystem information
	Cache    notify.Cache   // used to request cache updates
	CacheDir string         // path of the cache directory of the mount
	Mount    string         // name of the mount
	MountDir string         // path of the mount directory
	User     *config.User   // owner of the mount; if nil, config.CurrentUser is used
	Debug    bool           // turns on fuse debug logging
	Log      logging.Logger // log mount specific info
}

// Valid checks if provided options are valid.
func (o *Options) Valid() error {
	const sep = string(os.PathSeparator)

	if o.Index == nil {
		return errors.New("index is nil")
	}
	if o.Disk == nil {
		return errors.New("disk info is nil")
	}
	if o.Cache == nil {
		return errors.New("cache is nil")
	}
	if o.MountDir == "" {
		return errors.New("mount directory is empty")
	}
	if o.CacheDir == "" {
		return errors.New("cache directory is empty")
	}
	if !strings.HasSuffix(o.CacheDir, sep) {
		o.CacheDir = o.CacheDir + sep
	}
	if !strings.HasSuffix(o.MountDir, sep) {
		o.MountDir = o.MountDir + sep
	}

	return nil
}

func (o *Options) user() *config.User {
	if o.User != nil {
		return o.User
	}
	return config.CurrentUser
}

var _ fuseutil.FileSystem = (*Filesystem)(nil)

// Filesystem implements fuseutil.FileSystem.
//
// List operations are backend by index.
// Write and read operations are backed by cache,
// which is populated lazily by notify.Cache.
type Filesystem struct {
	// NotImplementedFileSystem provides stubs for the following methods:
	//
	//   - MkNode
	//   - CreateSymlink
	//   - ReadSymlink
	//
	fuseutil.NotImplementedFileSystem
	Options

	// Cancel the background context of all fuse operatins.
	cancel func()

	// Dynamic filesystem state.
	dirHandles  *DirHandleGroup
	fileHandles *FileHandleGroup
}

// FSWrapFunc allows to attach middlerares to underling filesystems.
type FSWrapFunc func(fuseutil.FileSystem) fuseutil.FileSystem

// NewFilesystem creates new Filesystem value.
func NewFilesystem(opts *Options, wraps ...FSWrapFunc) (*Filesystem, error) {
	if err := opts.Valid(); err != nil {
		return nil, err
	}

	// Best-effort attempt of unmounting already existing mount.
	_ = Umount(opts.MountDir)

	// Ignore mkdir errors since it can return `file exists` error. Other errors
	// will cause FUSE backend fail.
	_ = os.MkdirAll(opts.MountDir, 0755)

	ctx, cancel := context.WithCancel(context.Background())

	gen := generator(MinHandleID)
	fs := &Filesystem{
		Options:     *opts,
		cancel:      cancel,
		dirHandles:  NewDirHandleGroup(opts.MountDir, gen),
		fileHandles: NewFileHandleGroup(gen),
	}

	// Attach additional filesystem wrappers if any.
	var fuseFS fuseutil.FileSystem = fs
	for _, wrap := range wraps {
		fuseFS = wrap(fuseFS)
	}

	cfg := fs.Config()

	// Debug information about filesystem.
	if opts.Log != nil {
		// User information.
		var userInfo string
		if u, err := user.Current(); err == nil {
			userInfo = fmt.Sprintf("%s, home: %s", u.Username, u.HomeDir)
		} else {
			userInfo = "cannot obtain user info: " + err.Error()
		}

		opts.Log.Info("Dir: %s; CacheDir: %s; Mounter: %s; Opts: %s; Usr: %s",
			opts.MountDir, opts.CacheDir, getFuserMountVer(), toOptionsString(cfg), userInfo)
	}

	m, err := fuse.Mount(opts.MountDir, fuseutil.NewFileSystemServer(fuseFS), cfg)
	if err != nil {
		return nil, err
	}

	go m.Join(ctx)

	return fs, nil
}

// Config constructs fuse configuration.
func (fs *Filesystem) Config() *fuse.MountConfig {
	var logger *log.Logger

	if fs.Debug {
		logger = log.New(os.Stderr, "fuse", log.LstdFlags|log.Lshortfile)
	}

	return &fuse.MountConfig{
		FSName:                  fs.Mount,
		VolumeName:              filepath.Base(fs.MountDir),
		DisableWritebackCaching: true,
		EnableVnodeCaching:      false,
		Options: map[string]string{
			"allow_other": "",
			"local":       "",
			"auto_xattr":  "",
		},
		DebugLogger: logger,
		ErrorLogger: logger,
	}
}

// Close implements the notify.Notifier interface. It cleans up filesystem
// resources and tries to umount created device.
func (fs *Filesystem) Close() error {
	fs.cancel()
	fs.Destroy()

	return Umount(fs.MountDir)
}

// Umount unmounts FUSE filesystem.
func Umount(dir string) error {
	umountCmd := func(name string, args ...string) error {
		if p, err := exec.Command(name, args...).CombinedOutput(); err != nil {
			return fmt.Errorf("%s: %s", err, bytes.TrimSpace(p))
		}

		return nil
	}

	if runtime.GOOS == "linux" {
		if err := fuse.Unmount(dir); err != nil {
			return umountCmd("fusermount", "-uz", dir) // Try lazy umount.
		}
		return nil
	}

	// Under Darwin fuse.Umount uses syscall.Umount without syscall.MNT_FORCE flag,
	// so we replace that implementation with diskutil.
	return umountCmd("diskutil", "unmount", "force", dir)
}

func getFuserMountVer() string {
	const fm = "fusermount"

	if runtime.GOOS != "linux" {
		return "unsupported OS " + runtime.GOOS
	}

	if _, err := exec.LookPath(fm); err != nil {
		return "cannot find " + fm + " executable in PATH"
	}

	p, err := exec.Command(fm, "--version").CombinedOutput()
	if err != nil {
		return "unknown " + fm + "version: " + err.Error()
	}

	return strings.TrimSpace(string(p))
}

// Create a map containing all of the key=value mount options to be given to
// the mount helper.
//
// This function is copied from jacobsa/fuse and is meant to provide exact
// command arguments passed to fusermount.
func toMap(c *fuse.MountConfig) (opts map[string]string) {
	isDarwin := runtime.GOOS == "darwin"
	opts = make(map[string]string)

	opts["default_permissions"] = ""
	fsname := c.FSName
	if runtime.GOOS == "linux" && fsname == "" {
		fsname = "some_fuse_file_system"
	}

	// Special file system name?
	if fsname != "" {
		opts["fsname"] = fsname
	}

	// Read only?
	if c.ReadOnly {
		opts["ro"] = ""
	}

	// Handle OS X options.
	if isDarwin {
		if !c.EnableVnodeCaching {
			opts["novncache"] = ""
		}

		if c.VolumeName != "" {
			opts["volname"] = c.VolumeName
		}
	}

	if isDarwin {
		opts["noappledouble"] = ""
	}

	for k, v := range c.Options {
		opts[k] = v
	}

	return
}

func escapeOptionsKey(s string) (res string) {
	res = s
	res = strings.Replace(res, `\`, `\\`, -1)
	res = strings.Replace(res, `,`, `\,`, -1)
	return
}

// Create an options string suitable for passing to the mount helper.
func toOptionsString(c *fuse.MountConfig) string {
	var components []string
	for k, v := range toMap(c) {
		k = escapeOptionsKey(k)

		component := k
		if v != "" {
			component = fmt.Sprintf("%s=%s", k, v)
		}

		components = append(components, component)
	}

	return strings.Join(components, ",")
}
<|endoftext|>


<commit_before>package wkhtmltopdf

import (
	"bytes"
	"io/ioutil"
	"os"
	"reflect"
	"runtime"
	"strings"
	"testing"
)

func newTestPDFGenerator(tb testing.TB) *PDFGenerator {

	pdfg, err := NewPDFGenerator()
	if err != nil {
		tb.Fatal(err)
	}

	pdfg.Dpi.Set(600)
	pdfg.NoCollate.Set(false)
	pdfg.PageSize.Set(PageSizeA4)
	pdfg.MarginBottom.Set(40)
	pdfg.MarginLeft.Set(0)

	page1 := NewPage("https://www.google.com")

	page1.DisableSmartShrinking.Set(true)
	page1.HeaderSpacing.Set(10.01)
	page1.Allow.Set("/usr/local/html")
	page1.Allow.Set("/usr/local/images")
	page1.CustomHeader.Set("X-AppKey", "abcdef")
	page1.ViewportSize.Set("3840x2160")

	if runtime.GOOS == "darwin" {
		page1.LoadErrorHandling.Set("ignore")
	}

	pdfg.AddPage(page1)

	pdfg.Cover.Input = "https://wkhtmltopdf.org/index.html"
	pdfg.Cover.Zoom.Set(0.75)

	pdfg.TOC.Include = true
	pdfg.TOC.DisableDottedLines.Set(true)

	return pdfg
}

func wantArgString() string {
	return "--dpi 600 --margin-bottom 40 --margin-left 0 --page-size A4 cover https://wkhtmltopdf.org/index.html --zoom 0.750 toc --disable-dotted-lines page https://www.google.com --allow /usr/local/html --allow /usr/local/images --custom-header X-AppKey abcdef --disable-smart-shrinking --viewport-size 3840x2160 --header-spacing 10.010 -"
}

func TestArgString(t *testing.T) {
	pdfg := newTestPDFGenerator(t)
	want := wantArgString()
	if pdfg.ArgString() != want {
		t.Errorf("Want argstring:\n%s\nHave:\n%s", want, pdfg.ArgString())
	}
	pdfg.SetPages(pdfg.pages)
	if pdfg.ArgString() != want {
		t.Errorf("Want argstring:\n%s\nHave:\n%s", want, pdfg.ArgString())
	}
}

func TestVersion(t *testing.T) {
	pdfg, err := NewPDFGenerator()
	if err != nil {
		t.Fatal(err)
	}
	pdfg.Version.Set(true)
	err = pdfg.Create()
	if err != nil {
		t.Fatal(err)
	}
}

func TestNoInput(t *testing.T) {
	pdfg, err := NewPDFGenerator()
	if err != nil {
		t.Fatal(err)
	}
	err = pdfg.Create()
	if err == nil {
		t.Fatal("Want an error when there is no input, have no error")
	}
	//TODO temp error check because older versions of wkhtmltopdf return a different error :(
	wantErrNew := "You need to specify at least one input file, and exactly one output file"
	wantErrOld := "You need to specify atleast one input file, and exactly one output file"
	if strings.HasPrefix(err.Error(), wantErrNew) == false && strings.HasPrefix(err.Error(), wantErrOld) == false {
		t.Errorf("Want error prefix %s or %s, have %s", wantErrNew, wantErrOld, err.Error())
	}
}

func TestGeneratePDF(t *testing.T) {
	pdfg := newTestPDFGenerator(t)
	err := pdfg.Create()
	if err != nil {
		t.Fatal(err)
	}
	err = pdfg.WriteFile("./testfiles/TestGeneratePDF.pdf")
	if err != nil {
		t.Fatal(err)
	}
	t.Logf("PDF size %vkB", len(pdfg.Bytes())/1024)
}

func TestGeneratePdfFromStdinSimple(t *testing.T) {
	//Use a new blank PDF generator
	pdfg, err := NewPDFGenerator()
	if err != nil {
		t.Fatal(err)
	}
	htmlfile, err := ioutil.ReadFile("./testfiles/htmlsimple.html")
	if err != nil {
		t.Fatal(err)
	}
	pdfg.AddPage(NewPageReader(bytes.NewReader(htmlfile)))
	err = pdfg.Create()
	if err != nil {
		t.Fatal(err)
	}
	err = pdfg.WriteFile("./testfiles/TestGeneratePdfFromStdinSimple.pdf")
	if err != nil {
		t.Fatal(err)
	}
	t.Logf("PDF size %vkB", len(pdfg.Bytes())/1024)
	if pdfg.Buffer().Len() != len(pdfg.Bytes()) {
		t.Errorf("Buffersize not equal")
	}
}

func TestPDFGeneratorOutputFile(t *testing.T) {
	pdfg, err := NewPDFGenerator()
	if err != nil {
		t.Fatal(err)
	}
	htmlfile, err := os.Open("./testfiles/htmlsimple.html")
	if err != nil {
		t.Fatal(err)
	}
	defer htmlfile.Close()

	pdfg.OutputFile = "./testfiles/TestPDFGeneratorOutputFile.pdf"

	pdfg.AddPage(NewPageReader(htmlfile))
	err = pdfg.Create()
	if err != nil {
		t.Fatal(err)
	}

	pdfFile, err := os.Open("./testfiles/TestPDFGeneratorOutputFile.pdf")
	if err != nil {
		t.Fatal(err)
	}
	defer pdfFile.Close()

	stat, err := pdfFile.Stat()
	if err != nil {
		t.Fatal(err)
	}
	if stat.Size() < 100 {
		t.Errorf("generated PDF is size under 100 bytes")
	}
}

func TestGeneratePdfFromStdinHtml5(t *testing.T) {
	//Use newTestPDFGenerator and append to page1 and TOC
	pdfg := newTestPDFGenerator(t)
	htmlfile, err := ioutil.ReadFile("./testfiles/html5.html")
	if err != nil {
		t.Fatal(err)
	}
	page2 := NewPageReader(bytes.NewReader(htmlfile))
	pdfg.AddPage(page2)
	err = pdfg.Create()
	if err != nil {
		t.Fatal(err)
	}
	err = pdfg.WriteFile("./testfiles/TestGeneratePdfFromStdinHtml5.pdf")
	if err != nil {
		t.Fatal(err)
	}
	t.Logf("PDF size %vkB", len(pdfg.Bytes())/1024)
}

func TestPath(t *testing.T) {
	path := "/usr/wkhtmltopdf/wkhtmltopdf"
	SetPath(path)
	defer SetPath("")
	if GetPath() != path {
		t.Errorf("Have path %q, want %q", GetPath(), path)
	}
}

func BenchmarkArgs(b *testing.B) {
	pdfg := newTestPDFGenerator(b)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		pdfg.Args()
	}
}

func TestStringOption(t *testing.T) {
	opt := stringOption{
		option: "stringopt",
	}
	opt.Set("value123")

	want := []string{"--stringopt", "value123"}

	if !reflect.DeepEqual(opt.Parse(), want) {
		t.Errorf("expected %v, have %v", want, opt.Parse())
	}

}

func TestSliceOption(t *testing.T) {
	opt := sliceOption{
		option: "sliceopt",
	}
	opt.Set("string15183")
	opt.Set("foo")
	opt.Set("bar")

	want := []string{"--sliceopt", "string15183", "--sliceopt", "foo", "--sliceopt", "bar"}

	if !reflect.DeepEqual(opt.Parse(), want) {
		t.Errorf("expected %v, have %v", want, opt.Parse())
	}

}

func TestMapOption(t *testing.T) {
	opt := mapOption{
		option: "mapopt",
	}

	opt.Set("key1", "foo")
	opt.Set("key2", "bar")
	opt.Set("key3", "Hello")

	result := strings.Join(opt.Parse(), " ")
	if !strings.Contains(result, "--mapopt key1 foo") {
		t.Error("missing map option key1")
	}
	if !strings.Contains(result, "--mapopt key2 bar") {
		t.Error("missing map option key2")
	}
	if !strings.Contains(result, "--mapopt key3 Hello") {
		t.Error("missing map option key3")
	}
}

func TestUIntOption(t *testing.T) {
	opt := uintOption{
		option: "uintopt",
	}
	opt.Set(14860)

	want := []string{"--uintopt", "14860"}

	if !reflect.DeepEqual(opt.Parse(), want) {
		t.Errorf("expected %v, have %v", want, opt.Parse())
	}
}

func TestFloatOption(t *testing.T) {
	opt := floatOption{
		option: "flopt",
	}
	opt.Set(239.75)

	want := []string{"--flopt", "239.750"}

	if !reflect.DeepEqual(opt.Parse(), want) {
		t.Errorf("expected %v, have %v", want, opt.Parse())
	}
}

func TestBoolOption(t *testing.T) {
	opt := boolOption{
		option: "boolopt",
	}
	opt.Set(true)

	want := []string{"--boolopt"}

	if !reflect.DeepEqual(opt.Parse(), want) {
		t.Errorf("expected %v, have %v", want, opt.Parse())
	}
}
<commit_msg>add tests for unset<commit_after>package wkhtmltopdf

import (
	"bytes"
	"io/ioutil"
	"os"
	"reflect"
	"runtime"
	"strings"
	"testing"
)

func newTestPDFGenerator(tb testing.TB) *PDFGenerator {

	pdfg, err := NewPDFGenerator()
	if err != nil {
		tb.Fatal(err)
	}

	pdfg.Dpi.Set(600)
	pdfg.NoCollate.Set(false)
	pdfg.PageSize.Set(PageSizeA4)
	pdfg.MarginBottom.Set(40)
	pdfg.MarginLeft.Set(0)

	page1 := NewPage("https://www.google.com")

	page1.DisableSmartShrinking.Set(true)
	page1.HeaderSpacing.Set(10.01)
	page1.Allow.Set("/usr/local/html")
	page1.Allow.Set("/usr/local/images")
	page1.CustomHeader.Set("X-AppKey", "abcdef")
	page1.ViewportSize.Set("3840x2160")

	if runtime.GOOS == "darwin" {
		page1.LoadErrorHandling.Set("ignore")
	}

	pdfg.AddPage(page1)

	pdfg.Cover.Input = "https://wkhtmltopdf.org/index.html"
	pdfg.Cover.Zoom.Set(0.75)

	pdfg.TOC.Include = true
	pdfg.TOC.DisableDottedLines.Set(true)

	return pdfg
}

func wantArgString() string {
	return "--dpi 600 --margin-bottom 40 --margin-left 0 --page-size A4 cover https://wkhtmltopdf.org/index.html --zoom 0.750 toc --disable-dotted-lines page https://www.google.com --allow /usr/local/html --allow /usr/local/images --custom-header X-AppKey abcdef --disable-smart-shrinking --viewport-size 3840x2160 --header-spacing 10.010 -"
}

func TestArgString(t *testing.T) {
	pdfg := newTestPDFGenerator(t)
	want := wantArgString()
	if pdfg.ArgString() != want {
		t.Errorf("Want argstring:\n%s\nHave:\n%s", want, pdfg.ArgString())
	}
	pdfg.SetPages(pdfg.pages)
	if pdfg.ArgString() != want {
		t.Errorf("Want argstring:\n%s\nHave:\n%s", want, pdfg.ArgString())
	}
}

func TestVersion(t *testing.T) {
	pdfg, err := NewPDFGenerator()
	if err != nil {
		t.Fatal(err)
	}
	pdfg.Version.Set(true)
	err = pdfg.Create()
	if err != nil {
		t.Fatal(err)
	}
}

func TestNoInput(t *testing.T) {
	pdfg, err := NewPDFGenerator()
	if err != nil {
		t.Fatal(err)
	}
	err = pdfg.Create()
	if err == nil {
		t.Fatal("Want an error when there is no input, have no error")
	}
	//TODO temp error check because older versions of wkhtmltopdf return a different error :(
	wantErrNew := "You need to specify at least one input file, and exactly one output file"
	wantErrOld := "You need to specify atleast one input file, and exactly one output file"
	if strings.HasPrefix(err.Error(), wantErrNew) == false && strings.HasPrefix(err.Error(), wantErrOld) == false {
		t.Errorf("Want error prefix %s or %s, have %s", wantErrNew, wantErrOld, err.Error())
	}
}

func TestGeneratePDF(t *testing.T) {
	pdfg := newTestPDFGenerator(t)
	err := pdfg.Create()
	if err != nil {
		t.Fatal(err)
	}
	err = pdfg.WriteFile("./testfiles/TestGeneratePDF.pdf")
	if err != nil {
		t.Fatal(err)
	}
	t.Logf("PDF size %vkB", len(pdfg.Bytes())/1024)
}

func TestGeneratePdfFromStdinSimple(t *testing.T) {
	//Use a new blank PDF generator
	pdfg, err := NewPDFGenerator()
	if err != nil {
		t.Fatal(err)
	}
	htmlfile, err := ioutil.ReadFile("./testfiles/htmlsimple.html")
	if err != nil {
		t.Fatal(err)
	}
	pdfg.AddPage(NewPageReader(bytes.NewReader(htmlfile)))
	err = pdfg.Create()
	if err != nil {
		t.Fatal(err)
	}
	err = pdfg.WriteFile("./testfiles/TestGeneratePdfFromStdinSimple.pdf")
	if err != nil {
		t.Fatal(err)
	}
	t.Logf("PDF size %vkB", len(pdfg.Bytes())/1024)
	if pdfg.Buffer().Len() != len(pdfg.Bytes()) {
		t.Errorf("Buffersize not equal")
	}
}

func TestPDFGeneratorOutputFile(t *testing.T) {
	pdfg, err := NewPDFGenerator()
	if err != nil {
		t.Fatal(err)
	}
	htmlfile, err := os.Open("./testfiles/htmlsimple.html")
	if err != nil {
		t.Fatal(err)
	}
	defer htmlfile.Close()

	pdfg.OutputFile = "./testfiles/TestPDFGeneratorOutputFile.pdf"

	pdfg.AddPage(NewPageReader(htmlfile))
	err = pdfg.Create()
	if err != nil {
		t.Fatal(err)
	}

	pdfFile, err := os.Open("./testfiles/TestPDFGeneratorOutputFile.pdf")
	if err != nil {
		t.Fatal(err)
	}
	defer pdfFile.Close()

	stat, err := pdfFile.Stat()
	if err != nil {
		t.Fatal(err)
	}
	if stat.Size() < 100 {
		t.Errorf("generated PDF is size under 100 bytes")
	}
}

func TestGeneratePdfFromStdinHtml5(t *testing.T) {
	//Use newTestPDFGenerator and append to page1 and TOC
	pdfg := newTestPDFGenerator(t)
	htmlfile, err := ioutil.ReadFile("./testfiles/html5.html")
	if err != nil {
		t.Fatal(err)
	}
	page2 := NewPageReader(bytes.NewReader(htmlfile))
	pdfg.AddPage(page2)
	err = pdfg.Create()
	if err != nil {
		t.Fatal(err)
	}
	err = pdfg.WriteFile("./testfiles/TestGeneratePdfFromStdinHtml5.pdf")
	if err != nil {
		t.Fatal(err)
	}
	t.Logf("PDF size %vkB", len(pdfg.Bytes())/1024)
}

func TestPath(t *testing.T) {
	path := "/usr/wkhtmltopdf/wkhtmltopdf"
	SetPath(path)
	defer SetPath("")
	if GetPath() != path {
		t.Errorf("Have path %q, want %q", GetPath(), path)
	}
}

func BenchmarkArgs(b *testing.B) {
	pdfg := newTestPDFGenerator(b)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		pdfg.Args()
	}
}

func TestStringOption(t *testing.T) {
	opt := stringOption{
		option: "stringopt",
	}
	opt.Set("value123")

	want := []string{"--stringopt", "value123"}

	if !reflect.DeepEqual(opt.Parse(), want) {
		t.Errorf("expected %v, have %v", want, opt.Parse())
	}

	opt.Unset()
	if !reflect.DeepEqual(opt.Parse(), []string{}) {
		t.Errorf("not empty after unset")
	}
}

func TestSliceOption(t *testing.T) {
	opt := sliceOption{
		option: "sliceopt",
	}
	opt.Set("string15183")
	opt.Set("foo")
	opt.Set("bar")

	want := []string{"--sliceopt", "string15183", "--sliceopt", "foo", "--sliceopt", "bar"}

	if !reflect.DeepEqual(opt.Parse(), want) {
		t.Errorf("expected %v, have %v", want, opt.Parse())
	}

	opt.Unset()
	if !reflect.DeepEqual(opt.Parse(), []string{}) {
		t.Errorf("not empty after unset")
	}
}

func TestMapOption(t *testing.T) {
	opt := mapOption{
		option: "mapopt",
	}

	opt.Set("key1", "foo")
	opt.Set("key2", "bar")
	opt.Set("key3", "Hello")

	result := strings.Join(opt.Parse(), " ")
	if !strings.Contains(result, "--mapopt key1 foo") {
		t.Error("missing map option key1")
	}
	if !strings.Contains(result, "--mapopt key2 bar") {
		t.Error("missing map option key2")
	}
	if !strings.Contains(result, "--mapopt key3 Hello") {
		t.Error("missing map option key3")
	}

	opt.Unset()
	if !reflect.DeepEqual(opt.Parse(), []string{}) {
		t.Errorf("not empty after unset")
	}
}

func TestUIntOption(t *testing.T) {
	opt := uintOption{
		option: "uintopt",
	}
	opt.Set(14860)

	want := []string{"--uintopt", "14860"}

	if !reflect.DeepEqual(opt.Parse(), want) {
		t.Errorf("expected %v, have %v", want, opt.Parse())
	}

	opt.Unset()
	if !reflect.DeepEqual(opt.Parse(), []string{}) {
		t.Errorf("not empty after unset")
	}
}

func TestFloatOption(t *testing.T) {
	opt := floatOption{
		option: "flopt",
	}
	opt.Set(239.75)

	want := []string{"--flopt", "239.750"}

	if !reflect.DeepEqual(opt.Parse(), want) {
		t.Errorf("expected %v, have %v", want, opt.Parse())
	}

	opt.Unset()
	if !reflect.DeepEqual(opt.Parse(), []string{}) {
		t.Errorf("not empty after unset")
	}
}

func TestBoolOption(t *testing.T) {
	opt := boolOption{
		option: "boolopt",
	}
	opt.Set(true)

	want := []string{"--boolopt"}

	if !reflect.DeepEqual(opt.Parse(), want) {
		t.Errorf("expected %v, have %v", want, opt.Parse())
	}

	opt.Unset()
	if !reflect.DeepEqual(opt.Parse(), []string{}) {
		t.Errorf("not empty after unset")
	}
}
<|endoftext|>