<commit_before>package vault

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"net/http"
	"net/url"
	"time"

	"github.com/cloudfoundry-community/vaultkv"
)

type Client struct {
	Prefix string

	vault vaultkv.Client
	kv    *vaultkv.KV
}

type Credentials struct {
	SealKey   string `json:"seal_key"`
	RootToken string `json:"root_token"`
}

func Connect(uri, cacert string) (*Client, error) {
	pool := x509.NewCertPool()
	if cacert != "" {
		if ok := pool.AppendCertsFromPEM([]byte(cacert)); !ok {
			return nil, fmt.Errorf("Invalid or malformed CA Certificate")
		}
	}

	vaultURI, err := url.Parse(uri)
	if err != nil {
		return nil, fmt.Errorf("Invalid or malformed Vault URI '%s': %s", uri, err)
	}

	c := &Client{
		Prefix: "secret/secret",
		vault: vaultkv.Client{
			VaultURL: vaultURI,
			Client: &http.Client{
				Timeout: 30 * time.Second,
				Transport: &http.Transport{
					TLSClientConfig: &tls.Config{
						RootCAs: pool,
					},
				},
			},
		},
	}
	c.kv = c.vault.NewKV()

	return c, nil
}

type Status int

const (
	Unknown Status = iota
	Blank
	Locked
	Ready
)

func (c *Client) Status() (Status, error) {
	err := c.vault.TokenIsValid()
	if err != nil {
		return Locked, nil
	}
	err = c.vault.Health(false)
	if err == nil {
		return Ready, nil
	}
	if vaultkv.IsUninitialized(err) {
		return Blank, nil
	}
	if vaultkv.IsSealed(err) {
		return Locked, nil
	}
	return Unknown, err
}
func (c *Client) StatusString() (string, error) {
	st, err := c.Status()
	if err != nil {
		return "unknown", err
	}
	switch st {
	case Blank:
		return "uninitialized", nil
	case Locked:
		return "locked", nil
	case Ready:
		return "unlocked", nil
	}
	return "unknown", nil
}

func (c *Client) Initialize(crypt, master string) (string, error) {
	/* initialize the vault, with one seal key */
	creds, err := c.vault.InitVault(vaultkv.InitConfig{
		Shares:    1,
		Threshold: 1,
	})
	if err != nil {
		return "", fmt.Errorf("failed to initialize the vault: %s", err)
	}

	if err := WriteCrypt(crypt, master, &Credentials{
		SealKey:   creds.Keys[0],
		RootToken: creds.RootToken,
	}); err != nil {
		return "", err
	}

	if err := creds.Unseal(); err != nil {
		return "", err
	}

	k, p, err := GenerateFixedParameters()
	if err != nil {
		return "", err
	}
	if err := c.StoreFixed(p); err != nil {
		return "", err
	}

	return k, nil
}

func (c *Client) Unseal(crypt, master string) error {
	/* retrieve our seal keys from the crypt file */
	creds, err := ReadCrypt(crypt, master)
	if err != nil {
		return err
	}
	c.vault.SetAuthToken(creds.RootToken)
	/* unseal the vault */
	_, err = c.vault.Unseal(creds.SealKey)
	return err
}

func (c *Client) Seal() error {
	return c.vault.Seal()
}
<commit_msg>fixed status sealed vs uninit priority<commit_after>package vault

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"net/http"
	"net/url"
	"time"

	"github.com/cloudfoundry-community/vaultkv"
)

type Client struct {
	Prefix string

	vault vaultkv.Client
	kv    *vaultkv.KV
}

type Credentials struct {
	SealKey   string `json:"seal_key"`
	RootToken string `json:"root_token"`
}

func Connect(uri, cacert string) (*Client, error) {
	pool := x509.NewCertPool()
	if cacert != "" {
		if ok := pool.AppendCertsFromPEM([]byte(cacert)); !ok {
			return nil, fmt.Errorf("Invalid or malformed CA Certificate")
		}
	}

	vaultURI, err := url.Parse(uri)
	if err != nil {
		return nil, fmt.Errorf("Invalid or malformed Vault URI '%s': %s", uri, err)
	}

	c := &Client{
		Prefix: "secret/secret",
		vault: vaultkv.Client{
			VaultURL: vaultURI,
			Client: &http.Client{
				Timeout: 30 * time.Second,
				Transport: &http.Transport{
					TLSClientConfig: &tls.Config{
						RootCAs: pool,
					},
				},
			},
		},
	}
	c.kv = c.vault.NewKV()

	return c, nil
}

type Status int

const (
	Unknown Status = iota
	Blank
	Locked
	Ready
)

func (c *Client) Status() (Status, error) {
	err := c.vault.Health(false)
	if err == nil {
		return Ready, nil
	}
	if vaultkv.IsUninitialized(err) {
		return Blank, nil
	}
	if vaultkv.IsSealed(err) {
		return Locked, nil
	}
	err = c.vault.TokenIsValid()
	if err != nil {
		return Locked, nil
	}
	return Unknown, err
}
func (c *Client) StatusString() (string, error) {
	st, err := c.Status()
	if err != nil {
		return "unknown", err
	}
	switch st {
	case Blank:
		return "uninitialized", nil
	case Locked:
		return "locked", nil
	case Ready:
		return "unlocked", nil
	}
	return "unknown", nil
}

func (c *Client) Initialize(crypt, master string) (string, error) {
	/* initialize the vault, with one seal key */
	creds, err := c.vault.InitVault(vaultkv.InitConfig{
		Shares:    1,
		Threshold: 1,
	})
	if err != nil {
		return "", fmt.Errorf("failed to initialize the vault: %s", err)
	}

	if err := WriteCrypt(crypt, master, &Credentials{
		SealKey:   creds.Keys[0],
		RootToken: creds.RootToken,
	}); err != nil {
		return "", err
	}

	if err := creds.Unseal(); err != nil {
		return "", err
	}

	k, p, err := GenerateFixedParameters()
	if err != nil {
		return "", err
	}
	if err := c.StoreFixed(p); err != nil {
		return "", err
	}

	return k, nil
}

func (c *Client) Unseal(crypt, master string) error {
	/* retrieve our seal keys from the crypt file */
	creds, err := ReadCrypt(crypt, master)
	if err != nil {
		return err
	}
	c.vault.SetAuthToken(creds.RootToken)
	/* unseal the vault */
	_, err = c.vault.Unseal(creds.SealKey)
	return err
}

func (c *Client) Seal() error {
	return c.vault.Seal()
}
<|endoftext|>


<commit_before>/* Copyright 2013 Google Inc. All Rights Reserved.

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 frontend provides a HTTP server that accepts input for symbolization
	in various formats and returns the symbolized output.
*/
package frontend

import (
	"bytes"
	"container/list"
	"fmt"
	"html/template"
	"io"
	"net/http"
	"path"
	"sync"

	"flag"
	"github.com/chromium/crsym/breakpad"
	"log"
)

var (
	// Path to the static files directory for the frontend.
	frontendFiles string

	cacheSize = flag.Int("symbol_cache_size", 30, "Number of symbol files to keep in an MRU cache")

	// Extra data to put on the homepage.
	statusData []template.HTML
)

// SetFilesPath sets the path to where the static frontend files reside on disk.
func SetFilesPath(p string) {
	frontendFiles = p
}

// SetHomePageStatus adds extra strings to the top-right corner of the main UI.
func SetHomePageStatus(status []string) {
	statusData = make([]template.HTML, len(status))
	for i, s := range status {
		statusData[i] = template.HTML(s)
	}
}

// RegisterHandlers adds the frontend endpoints to the provided ServeMux and
// returns the Handler state. SetFilesPath should be called before this.
func RegisterHandlers(mux *http.ServeMux) *Handler {
	mux.HandleFunc("/", indexHandler)

	staticDir := "/static/"
	staticHandler := http.FileServer(http.Dir(frontendFiles))
	mux.Handle(staticDir, http.StripPrefix("/static", staticHandler))

	handler := &Handler{
		mu:          new(sync.Mutex),
		mru:         list.New(),
		symbolCache: make(map[string]*list.Element),
	}
	// Initialize the cache with an empty list of size |cacheSize|.
	for i := 0; i < *cacheSize; i++ {
		handler.mru.PushBack(nil)
	}
	mux.Handle("/_/service", handler)

	return handler
}

func indexHandler(rw http.ResponseWriter, req *http.Request) {
	tpl, err := template.ParseFiles(path.Join(frontendFiles, "home.html"))
	if err != nil {
		rw.WriteHeader(http.StatusInternalServerError)
		fmt.Fprint(rw, err)
		return
	}

	rw.Header().Set("Content-type", "text/html")
	tpl.Execute(rw, struct {
		StatusData []template.HTML
	}{
		statusData,
	})
}

// Type Handler stores the breakpad.Supplier and other server state.
type Handler struct {
	supplier          breakpad.Supplier
	frameService      breakpad.AnnotatedFrameService
	moduleInfoService breakpad.ModuleInfoService

	// mu is the mutex that protects the two objects below.
	mu *sync.Mutex
	// mru contains a list of SymbolTable objects most recently fetched from the
	// supplier, with newest at the end.
	mru *list.List
	// symbolCache maps SymbolTable.Identifier() to elements in |mru| for fast
	// cache lookup.
	symbolCache map[string]*list.Element
}

// Init sets the breakpad supplier to use. This should be called before starting
// the server.
func (h *Handler) Init(supplier breakpad.Supplier) {
	h.supplier = supplier
}

// SetAnnotatedFrameService sets the backend implementation that fetches crash
// report frame information. If nil, the CrashKeyParser cannot be used.
func (h *Handler) SetAnnotatedFrameService(s breakpad.AnnotatedFrameService) {
	h.frameService = s
}

// SetModuleInfoService sets the backend for querying for module information.
// If nil, the module_info input type cannot be used.
func (h *Handler) SetModuleInfoService(s breakpad.ModuleInfoService) {
	h.moduleInfoService = s
}

func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	logRequest(req)

	if req.Method != "POST" {
		replyError(req, rw, http.StatusMethodNotAllowed, "Only POSTs allowed")
		return
	}

	input := req.FormValue("input")
	inputRequired := true

	var parser InputParser
	switch req.FormValue("input_type") {
	case "fragment":
		parser = h.handleFragment(rw, req)
	case "apple":
		parser = new(AppleInputParser)
	case "stackwalk":
		parser = NewStackwalkInputParser()
	case "crash_key":
		parser = h.handleCrashKey(rw, req)
		inputRequired = false
	case "module_info":
		parser = h.handleModuleInfo(rw, req)
		inputRequired = false
	case "android":
		parser = h.handleAndroid(rw, req)
	default:
		replyError(req, rw, http.StatusNotImplemented, "Unknown input_type")
	}

	if parser == nil {
		return
	}
	if input == "" && inputRequired {
		replyError(req, rw, http.StatusBadRequest, "Missing input")
		return
	}

	if err := parser.ParseInput(input); err != nil {
		replyError(req, rw, http.StatusBadRequest, err.Error())
		return
	}

	requiredModules := parser.RequiredModules()
	if parser.FilterModules() {
		requiredModules = h.supplier.FilterAvailableModules(requiredModules)
	}

	var tables []breakpad.SymbolTable
	for _, moduleRequest := range requiredModules {
		table, err := h.getTable(moduleRequest)
		if err != nil {
			replyError(req, rw, 404, err.Error())
			return
		}
		tables = append(tables, table)
	}

	output := parser.Symbolize(tables)
	io.WriteString(rw, output)
}

// getTable looks up the requested module in the server cache and returns it
// if present. If it is not, this performs a blocking call to the Supplier and
// caches the result.
func (h *Handler) getTable(request breakpad.SupplierRequest) (breakpad.SymbolTable, error) {
	table := h.loadCachedTable(request)
	if table != nil {
		return table, nil
	}

	// Not cached, so fetch it from the supplier.
	resp := <-h.supplier.TableForModule(request)
	if resp.Error != nil {
		return nil, resp.Error
	}

	// Take the LRU item from the cache and remove it.
	h.mu.Lock()
	defer h.mu.Unlock()
	elm := h.mru.Front()
	if elm.Value != nil {
		delete(h.symbolCache, elm.Value.(breakpad.SymbolTable).Identifier())
	}

	// Insert the new table as the MRU one.
	ident := resp.Table.Identifier()
	elm.Value = resp.Table
	h.symbolCache[ident] = elm

	h.mru.MoveToBack(elm)

	return resp.Table, nil
}

// loadCachedTable looks in the cache for the requested symbol table, marks it
// as recently used if found, and returns it. Returns nil for no cache entry.
func (h *Handler) loadCachedTable(request breakpad.SupplierRequest) breakpad.SymbolTable {
	h.mu.Lock()
	defer h.mu.Unlock()

	if elm, ok := h.symbolCache[request.Identifier]; ok {
		h.mru.MoveToBack(elm)
		return elm.Value.(breakpad.SymbolTable)
	}
	return nil
}

// handleFragment extracts fragment-specific input from the HTTP request and
// returns a FragmentInputParser if successful.
func (h *Handler) handleFragment(rw http.ResponseWriter, req *http.Request) InputParser {
	module := req.FormValue("module")
	ident := req.FormValue("ident")
	if module == "" || ident == "" {
		replyError(req, rw, http.StatusBadRequest, "Missing module or ident")
		return nil
	}

	loadAddress, err := breakpad.ParseAddress(req.FormValue("load_address"))
	if err != nil {
		replyError(req, rw, http.StatusBadRequest, fmt.Sprintf("Load address: %s", err))
		return nil
	}

	return NewFragmentInputParser(module, ident, loadAddress)
}

// handleCrashKey extracts the crash-key-specific input and returns an input
// parser if successful.
func (h *Handler) handleCrashKey(rw http.ResponseWriter, req *http.Request) InputParser {
	reportID := req.FormValue("report_id")
	key := req.FormValue("crash_key")
	if reportID == "" || key == "" {
		replyError(req, rw, http.StatusBadRequest, "Missing report ID or crash key")
		return nil
	}

	return NewCrashKeyInputParser(h.frameService, reportID, key)
}

// handleModuleInfo just looks up the module information for a product and version.
func (h *Handler) handleModuleInfo(rw http.ResponseWriter, req *http.Request) InputParser {
	product := req.FormValue("product_name")
	version := req.FormValue("product_version")
	if product == "" || version == "" {
		replyError(req, rw, http.StatusBadRequest, "Missing product name or version")
		return nil
	}

	return NewModuleInfoInputParser(h.moduleInfoService, product, version)
}

// handleAndroid parses a debug log (logcat) and outputs the stack.  Version number
// of the android chrome build is an optional input.
func (h *Handler) handleAndroid(rw http.ResponseWriter, req *http.Request) InputParser {
	version := req.FormValue("android_chrome_version")
	return NewAndroidInputParser(h.moduleInfoService, version)
}

func replyError(req *http.Request, rw http.ResponseWriter, code int, message string) {
	log.Infof("ERROR reply for %s, code %d (%q)", getUserIp(req), code, message)
	rw.WriteHeader(code)
	io.WriteString(rw, message)
}

// CacheStatus returns a HTML fragment that displays the current status of the
// symbol cache.
func (h *Handler) CacheStatus() string {
	h.mu.Lock()
	defer h.mu.Unlock()

	data := struct {
		NumEntries, CacheSize int
		Cache                 []string
	}{
		NumEntries: len(h.symbolCache),
		CacheSize:  *cacheSize,
		Cache:      make([]string, 0),
	}

	for e := h.mru.Front(); e != nil; e = e.Next() {
		v := "<nil>"
		if e.Value != nil {
			v = e.Value.(breakpad.SymbolTable).String()
		}
		data.Cache = append(data.Cache, v)
	}

	buf := bytes.NewBuffer(nil)
	if err := cacheStatusTemplate.Execute(buf, data); err != nil {
		return fmt.Sprintf("Error: %s", err.Error())
	}
	return buf.String()
}

func getUserIp(req *http.Request) string {
	ip := req.Header.Get("X-Proxied-User-Ip")
	if ip == "" {
		ip = req.Header.Get("X-Forwarded-For")
	}
	if ip == "" {
		ip = req.RemoteAddr
	}
	return ip
}

func logRequest(req *http.Request) {
	log.Infof("REQUEST to symbolize input type %q from %s", req.FormValue("input_type"), getUserIp(req))
}

var cacheStatusTemplate = template.Must(template.New("cache").Parse(
	`

	Capacity: {{.NumEntries}} / {{.CacheSize}}

	{{range .Cache}}

{{.}}

	{{end}}

`))
<commit_msg>Use glog instead of stdlib log in open-source crsym.<commit_after>/* Copyright 2013 Google Inc. All Rights Reserved.

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 frontend provides a HTTP server that accepts input for symbolization
	in various formats and returns the symbolized output.
*/
package frontend

import (
	"bytes"
	"container/list"
	"fmt"
	"html/template"
	"io"
	"net/http"
	"path"
	"sync"

	"flag"
	"github.com/chromium/crsym/breakpad"
	log "github.com/golang/glog"
)

var (
	// Path to the static files directory for the frontend.
	frontendFiles string

	cacheSize = flag.Int("symbol_cache_size", 30, "Number of symbol files to keep in an MRU cache")

	// Extra data to put on the homepage.
	statusData []template.HTML
)

// SetFilesPath sets the path to where the static frontend files reside on disk.
func SetFilesPath(p string) {
	frontendFiles = p
}

// SetHomePageStatus adds extra strings to the top-right corner of the main UI.
func SetHomePageStatus(status []string) {
	statusData = make([]template.HTML, len(status))
	for i, s := range status {
		statusData[i] = template.HTML(s)
	}
}

// RegisterHandlers adds the frontend endpoints to the provided ServeMux and
// returns the Handler state. SetFilesPath should be called before this.
func RegisterHandlers(mux *http.ServeMux) *Handler {
	mux.HandleFunc("/", indexHandler)

	staticDir := "/static/"
	staticHandler := http.FileServer(http.Dir(frontendFiles))
	mux.Handle(staticDir, http.StripPrefix("/static", staticHandler))

	handler := &Handler{
		mu:          new(sync.Mutex),
		mru:         list.New(),
		symbolCache: make(map[string]*list.Element),
	}
	// Initialize the cache with an empty list of size |cacheSize|.
	for i := 0; i < *cacheSize; i++ {
		handler.mru.PushBack(nil)
	}
	mux.Handle("/_/service", handler)

	return handler
}

func indexHandler(rw http.ResponseWriter, req *http.Request) {
	tpl, err := template.ParseFiles(path.Join(frontendFiles, "home.html"))
	if err != nil {
		rw.WriteHeader(http.StatusInternalServerError)
		fmt.Fprint(rw, err)
		return
	}

	rw.Header().Set("Content-type", "text/html")
	tpl.Execute(rw, struct {
		StatusData []template.HTML
	}{
		statusData,
	})
}

// Type Handler stores the breakpad.Supplier and other server state.
type Handler struct {
	supplier          breakpad.Supplier
	frameService      breakpad.AnnotatedFrameService
	moduleInfoService breakpad.ModuleInfoService

	// mu is the mutex that protects the two objects below.
	mu *sync.Mutex
	// mru contains a list of SymbolTable objects most recently fetched from the
	// supplier, with newest at the end.
	mru *list.List
	// symbolCache maps SymbolTable.Identifier() to elements in |mru| for fast
	// cache lookup.
	symbolCache map[string]*list.Element
}

// Init sets the breakpad supplier to use. This should be called before starting
// the server.
func (h *Handler) Init(supplier breakpad.Supplier) {
	h.supplier = supplier
}

// SetAnnotatedFrameService sets the backend implementation that fetches crash
// report frame information. If nil, the CrashKeyParser cannot be used.
func (h *Handler) SetAnnotatedFrameService(s breakpad.AnnotatedFrameService) {
	h.frameService = s
}

// SetModuleInfoService sets the backend for querying for module information.
// If nil, the module_info input type cannot be used.
func (h *Handler) SetModuleInfoService(s breakpad.ModuleInfoService) {
	h.moduleInfoService = s
}

func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	logRequest(req)

	if req.Method != "POST" {
		replyError(req, rw, http.StatusMethodNotAllowed, "Only POSTs allowed")
		return
	}

	input := req.FormValue("input")
	inputRequired := true

	var parser InputParser
	switch req.FormValue("input_type") {
	case "fragment":
		parser = h.handleFragment(rw, req)
	case "apple":
		parser = new(AppleInputParser)
	case "stackwalk":
		parser = NewStackwalkInputParser()
	case "crash_key":
		parser = h.handleCrashKey(rw, req)
		inputRequired = false
	case "module_info":
		parser = h.handleModuleInfo(rw, req)
		inputRequired = false
	case "android":
		parser = h.handleAndroid(rw, req)
	default:
		replyError(req, rw, http.StatusNotImplemented, "Unknown input_type")
	}

	if parser == nil {
		return
	}
	if input == "" && inputRequired {
		replyError(req, rw, http.StatusBadRequest, "Missing input")
		return
	}

	if err := parser.ParseInput(input); err != nil {
		replyError(req, rw, http.StatusBadRequest, err.Error())
		return
	}

	requiredModules := parser.RequiredModules()
	if parser.FilterModules() {
		requiredModules = h.supplier.FilterAvailableModules(requiredModules)
	}

	var tables []breakpad.SymbolTable
	for _, moduleRequest := range requiredModules {
		table, err := h.getTable(moduleRequest)
		if err != nil {
			replyError(req, rw, 404, err.Error())
			return
		}
		tables = append(tables, table)
	}

	output := parser.Symbolize(tables)
	io.WriteString(rw, output)
}

// getTable looks up the requested module in the server cache and returns it
// if present. If it is not, this performs a blocking call to the Supplier and
// caches the result.
func (h *Handler) getTable(request breakpad.SupplierRequest) (breakpad.SymbolTable, error) {
	table := h.loadCachedTable(request)
	if table != nil {
		return table, nil
	}

	// Not cached, so fetch it from the supplier.
	resp := <-h.supplier.TableForModule(request)
	if resp.Error != nil {
		return nil, resp.Error
	}

	// Take the LRU item from the cache and remove it.
	h.mu.Lock()
	defer h.mu.Unlock()
	elm := h.mru.Front()
	if elm.Value != nil {
		delete(h.symbolCache, elm.Value.(breakpad.SymbolTable).Identifier())
	}

	// Insert the new table as the MRU one.
	ident := resp.Table.Identifier()
	elm.Value = resp.Table
	h.symbolCache[ident] = elm

	h.mru.MoveToBack(elm)

	return resp.Table, nil
}

// loadCachedTable looks in the cache for the requested symbol table, marks it
// as recently used if found, and returns it. Returns nil for no cache entry.
func (h *Handler) loadCachedTable(request breakpad.SupplierRequest) breakpad.SymbolTable {
	h.mu.Lock()
	defer h.mu.Unlock()

	if elm, ok := h.symbolCache[request.Identifier]; ok {
		h.mru.MoveToBack(elm)
		return elm.Value.(breakpad.SymbolTable)
	}
	return nil
}

// handleFragment extracts fragment-specific input from the HTTP request and
// returns a FragmentInputParser if successful.
func (h *Handler) handleFragment(rw http.ResponseWriter, req *http.Request) InputParser {
	module := req.FormValue("module")
	ident := req.FormValue("ident")
	if module == "" || ident == "" {
		replyError(req, rw, http.StatusBadRequest, "Missing module or ident")
		return nil
	}

	loadAddress, err := breakpad.ParseAddress(req.FormValue("load_address"))
	if err != nil {
		replyError(req, rw, http.StatusBadRequest, fmt.Sprintf("Load address: %s", err))
		return nil
	}

	return NewFragmentInputParser(module, ident, loadAddress)
}

// handleCrashKey extracts the crash-key-specific input and returns an input
// parser if successful.
func (h *Handler) handleCrashKey(rw http.ResponseWriter, req *http.Request) InputParser {
	reportID := req.FormValue("report_id")
	key := req.FormValue("crash_key")
	if reportID == "" || key == "" {
		replyError(req, rw, http.StatusBadRequest, "Missing report ID or crash key")
		return nil
	}

	return NewCrashKeyInputParser(h.frameService, reportID, key)
}

// handleModuleInfo just looks up the module information for a product and version.
func (h *Handler) handleModuleInfo(rw http.ResponseWriter, req *http.Request) InputParser {
	product := req.FormValue("product_name")
	version := req.FormValue("product_version")
	if product == "" || version == "" {
		replyError(req, rw, http.StatusBadRequest, "Missing product name or version")
		return nil
	}

	return NewModuleInfoInputParser(h.moduleInfoService, product, version)
}

// handleAndroid parses a debug log (logcat) and outputs the stack.  Version number
// of the android chrome build is an optional input.
func (h *Handler) handleAndroid(rw http.ResponseWriter, req *http.Request) InputParser {
	version := req.FormValue("android_chrome_version")
	return NewAndroidInputParser(h.moduleInfoService, version)
}

func replyError(req *http.Request, rw http.ResponseWriter, code int, message string) {
	log.Infof("ERROR reply for %s, code %d (%q)", getUserIp(req), code, message)
	rw.WriteHeader(code)
	io.WriteString(rw, message)
}

// CacheStatus returns a HTML fragment that displays the current status of the
// symbol cache.
func (h *Handler) CacheStatus() string {
	h.mu.Lock()
	defer h.mu.Unlock()

	data := struct {
		NumEntries, CacheSize int
		Cache                 []string
	}{
		NumEntries: len(h.symbolCache),
		CacheSize:  *cacheSize,
		Cache:      make([]string, 0),
	}

	for e := h.mru.Front(); e != nil; e = e.Next() {
		v := "<nil>"
		if e.Value != nil {
			v = e.Value.(breakpad.SymbolTable).String()
		}
		data.Cache = append(data.Cache, v)
	}

	buf := bytes.NewBuffer(nil)
	if err := cacheStatusTemplate.Execute(buf, data); err != nil {
		return fmt.Sprintf("Error: %s", err.Error())
	}
	return buf.String()
}

func getUserIp(req *http.Request) string {
	ip := req.Header.Get("X-Proxied-User-Ip")
	if ip == "" {
		ip = req.Header.Get("X-Forwarded-For")
	}
	if ip == "" {
		ip = req.RemoteAddr
	}
	return ip
}

func logRequest(req *http.Request) {
	log.Infof("REQUEST to symbolize input type %q from %s", req.FormValue("input_type"), getUserIp(req))
}

var cacheStatusTemplate = template.Must(template.New("cache").Parse(
	`

	Capacity: {{.NumEntries}} / {{.CacheSize}}

	{{range .Cache}}

{{.}}

	{{end}}

`))
<|endoftext|>


<commit_before>package main

import (
	"context"
	"encoding/json"
	"flag"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/gin-gonic/gin"
)

var version = os.Getenv("VERSION")
var backend = os.Getenv("BACKEND_URL")

func main() {

	port := ":8080"
	flag.Parse()

	log.Printf("Frontend version: %s\n", version)

	r := gin.Default()
	r.LoadHTMLGlob("templates/*")
	r.GET("/", handleIndex)
	r.GET("/version", handleVersion)
	r.GET("/healthz", handleHealthz)

	srv := &http.Server{
		Addr:    port,
		Handler: r,
	}

	go func() {
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("listen: %s\n", err)
		}
	}()

	// Wait for interrupt signal to gracefully shutdown the server with
	// a timeout of 5 seconds.
	quit := make(chan os.Signal, 1)
	// kill (no param) default send syscall.SIGTERM
	// kill -2 is syscall.SIGINT
	// kill -9 is syscall.SIGKILL but can't be catch, so don't need add it
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
	log.Println("Shutting down server...")

	// The context is used to inform the server it has 5 seconds to finish
	// the request it is currently handling
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := srv.Shutdown(ctx); err != nil {
		log.Fatal("Server forced to shutdown:", err)
	}

	log.Println("Server exiting")

}

func handleIndex(c *gin.Context) {
	// Call backend for info
	resp, err := http.Get(backend)
	if err != nil {
		c.String(http.StatusInternalServerError, "Request to backend server ()%s failed:\n%v", backend, err)
		return
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		c.String(http.StatusInternalServerError, "Unable to read body from    backend request:\n%v", err)
		return
	}
	var p PodMetadata
	err = json.Unmarshal([]byte(body), &p)
	if err != nil {
		c.String(http.StatusInternalServerError, "Unable to parse JSON from backend request:\n%v", err)
		return
	}
	c.HTML(http.StatusOK, "index.html", p)
}

func handleVersion(c *gin.Context) {
	c.String(http.StatusOK, "%s", c.Value("version"))
}

func handleHealthz(c *gin.Context) {
	c.String(http.StatusOK, "", "")
}

// PodMetadata represents info about an InstanceMetadata in GCE
type PodMetadata struct {
	Name       string
	Namespace  string
	HostIP     string
	PodIP      string
	StartTime  string
	RawRequest string
	Counter    string
}
<commit_msg>Change error<commit_after>package main

import (
	"context"
	"encoding/json"
	"flag"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/gin-gonic/gin"
)

var version = os.Getenv("VERSION")
var backend = os.Getenv("BACKEND_URL")

func main() {

	port := ":8080"
	flag.Parse()

	log.Printf("Frontend version: %s\n", version)

	r := gin.Default()
	r.LoadHTMLGlob("templates/*")
	r.GET("/", handleIndex)
	r.GET("/version", handleVersion)
	r.GET("/healthz", handleHealthz)

	srv := &http.Server{
		Addr:    port,
		Handler: r,
	}

	go func() {
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("listen: %s\n", err)
		}
	}()

	// Wait for interrupt signal to gracefully shutdown the server with
	// a timeout of 5 seconds.
	quit := make(chan os.Signal, 1)
	// kill (no param) default send syscall.SIGTERM
	// kill -2 is syscall.SIGINT
	// kill -9 is syscall.SIGKILL but can't be catch, so don't need add it
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
	log.Println("Shutting down server...")

	// The context is used to inform the server it has 5 seconds to finish
	// the request it is currently handling
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := srv.Shutdown(ctx); err != nil {
		log.Fatal("Server forced to shutdown:", err)
	}

	log.Println("Server exiting")

}

func handleIndex(c *gin.Context) {
	// Call backend for info
	resp, err := http.Get(backend)
	if err != nil {
		c.String(http.StatusInternalServerError, "Request to backend server ()%s failed:\n%v", backend, err)
		return
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		c.String(http.StatusInternalServerError, "Unable to read body from    backend request:\n%v", err)
		return
	}
	var p PodMetadata
	err = json.Unmarshal([]byte(body), &p)
	if err != nil {
		c.String(http.StatusInternalServerError, "Unable to parse JSON AMP REVIEfrom backend request:\n%v", err)
		return
	}
	c.HTML(http.StatusOK, "index.html", p)
}

func handleVersion(c *gin.Context) {
	c.String(http.StatusOK, "%s", c.Value("version"))
}

func handleHealthz(c *gin.Context) {
	c.String(http.StatusOK, "", "")
}

// PodMetadata represents info about an InstanceMetadata in GCE
type PodMetadata struct {
	Name       string
	Namespace  string
	HostIP     string
	PodIP      string
	StartTime  string
	RawRequest string
	Counter    string
}
<|endoftext|>


<commit_before>package models

import (
	"context"
	"database/sql"
	"time"

	"github.com/keydotcat/keycatd/util"
)

type Vault struct {
	Id        string    `scaneo:"pk" json:"id"`
	Team      string    `scaneo:"pk" json:"-"`
	Version   uint32    `json:"version"`
	PublicKey []byte    `json:"public_key"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

func createVault(tx *sql.Tx, id, team string, vkp VaultKeyPair) (*Vault, error) {
	v := &Vault{Id: id, Team: team, Version: 1, PublicKey: vkp.PublicKey}
	if err := v.insert(tx); err != nil {
		return nil, err
	}
	for u, k := range vkp.Keys {
		if err := v.addUser(tx, u, k); err != nil {
			return nil, err
		}
	}
	return v, nil
}

func (v *Vault) insert(tx *sql.Tx) error {
	if err := v.validate(); err != nil {
		return err
	}
	now := time.Now().UTC()
	v.CreatedAt = now
	v.UpdatedAt = now
	v.Version = 1
	_, err := v.dbInsert(tx)
	switch {
	case IsDuplicateErr(err):
		return util.NewErrorFrom(ErrAlreadyExists)
	case isErrOrPanic(err):
		return err
	}
	return nil
}

func (v *Vault) update(tx *sql.Tx) error {
	if err := v.validate(); err != nil {
		return err
	}
	v.UpdatedAt = time.Now().UTC()
	res, err := tx.Exec(`UPDATE "vault" SET "version" = "version" + 1, "updated_at" = $1 WHERE "team" = $2 AND "id" = $3`, v.UpdatedAt, v.Team, v.Id)
	if err := treatUpdateErr(res, err); err != nil {
		return err
	}
	r := tx.QueryRow(`SELECT "version" FROM "vault" WHERE "team" = $1 AND "id" = $2`, v.Team, v.Id)
	err = r.Scan(&v.Version)
	if isNotExistsErr(err) {
		return util.NewErrorFrom(ErrDoesntExist)
	}
	if isErrOrPanic(err) {
		return err
	}
	return nil
}

func (v Vault) validate() error {
	errs := util.NewErrorFields().(*util.Error)
	if len(v.Id) == 0 {
		errs.SetFieldError("vault_id", "missing")
	}
	if len(v.Team) == 0 {
		errs.SetFieldError("vault_team", "missing")
	}
	if len(v.PublicKey) != publicKeyPackSize {
		errs.SetFieldError("vault_public_key", "invalid")
	}
	if v.Version == 0 {
		errs.SetFieldError("version", "invalid")
	}
	return errs.SetErrorOrCamo(ErrAlreadyExists)
}

func (v Vault) AddUsers(ctx context.Context, userKeys map[string][]byte) error {
	for _, k := range userKeys {
		if _, err := verifyAndUnpack(v.PublicKey, k); err != nil {
			return err
		}
	}
	return doTx(ctx, func(tx *sql.Tx) error {
		t := &Team{Id: v.Team}
		users, err := t.getUsers(tx)
		if err != nil {
			return err
		}
		for uk := range userKeys {
			found := false
			for _, user := range users {
				if user.Id == uk {
					found = true
					break
				}
			}
			if !found {
				return util.NewErrorFrom(ErrNotInTeam)
			}
		}
		for u, k := range userKeys {
			if err := v.addUser(tx, u, k); err != nil {
				if IsDuplicateErr(err) {
					return util.NewErrorFrom(ErrAlreadyExists)
				}
				return err
			}
		}
		return nil
	})
}

func (v Vault) GetUserIds(ctx context.Context) (uids []string, err error) {
	return uids, doTx(ctx, func(tx *sql.Tx) error {
		uids, err = v.getUserIds(tx)
		return err
	})
}

func (v Vault) getUserIds(tx *sql.Tx) ([]string, error) {
	rows, err := tx.Query(`SELECT "user" FROM "vault_user" WHERE "vault_user"."vault" = $1 AND "vault_user"."team" = $2`, v.Id, v.Team)
	if isErrOrPanic(err) {
		return nil, util.NewErrorFrom(err)
	}
	var users []string
	var uid string
	for rows.Next() {
		err = rows.Scan(&uid)
		if isErrOrPanic(err) {
			return nil, util.NewErrorFrom(err)
		}
		users = append(users, uid)
	}
	if err = rows.Err(); isErrOrPanic(err) {
		return nil, util.NewErrorFrom(err)
	}
	return users, nil
}

func (v Vault) addUser(tx *sql.Tx, username string, key []byte) error {
	if err := v.update(tx); err != nil {
		return err
	}
	vu := &vaultUser{Team: v.Team, Vault: v.Id, User: username, Key: key}
	return vu.insert(tx)
}

func (v Vault) RemoveUser(ctx context.Context, username string) error {
	return doTx(ctx, func(tx *sql.Tx) error {
		t := &Team{Id: v.Team}
		tu, err := t.getUserAffiliation(tx, username)
		if err != nil {
			return err
		}
		if tu.Admin {
			return util.NewErrorFrom(err)
		}
		return v.removeUser(tx, username)
	})
}

func (v Vault) removeUser(tx *sql.Tx, username string) error {
	vu := &vaultUser{Team: v.Team, Vault: v.Id, User: username}
	return treatUpdateErr(vu.dbDelete(tx))
}

func (v *Vault) AddSecret(ctx context.Context, s *Secret) error {
	s.Team = v.Team
	s.Vault = v.Id
	if _, err := verifyAndUnpack(v.PublicKey, s.Data); err != nil {
		return err
	}
	var err error
	for retry := 0; retry < 3; retry++ {
		err = doTx(ctx, func(tx *sql.Tx) error {
			if err := v.update(tx); err != nil {
				return err
			}
			s.VaultVersion = v.Version
			return s.insert(tx)
		})
		if err == ErrAlreadyExists {
			continue
		}
		break
	}
	return err
}

func (v *Vault) AddSecretList(ctx context.Context, sl []*Secret) error {
	for _, s := range sl {
		s.Team = v.Team
		s.Vault = v.Id
		if _, err := verifyAndUnpack(v.PublicKey, s.Data); err != nil {
			return err
		}
	}
	var err error
	for retry := 0; retry < 3; retry++ {
		err = doTx(ctx, func(tx *sql.Tx) error {
			for _, s := range sl {
				if err := v.update(tx); err != nil {
					return err
				}
				s.VaultVersion = v.Version
				if err := s.insert(tx); err != nil {
					return err
				}
			}
			return nil
		})
		if err == ErrAlreadyExists {
			continue
		}
		break
	}
	return err
}

func (v *Vault) UpdateSecret(ctx context.Context, s *Secret) error {
	return doTx(ctx, func(tx *sql.Tx) error {
		os, err := v.getSecret(tx, s.Id)
		if err != nil {
			return err
		}
		if err := v.update(tx); err != nil {
			return err
		}
		s.Team = os.Team
		s.Vault = os.Vault
		s.Version = os.Version + 1
		s.VaultVersion = v.Version
		return s.update(tx)
	})
}

func (v *Vault) DeleteSecret(ctx context.Context, sid string) error {
	return doTx(ctx, func(tx *sql.Tx) error {
		if err := v.update(tx); err != nil {
			return err
		}
		res, err := tx.Exec(`DELETE FROM "secret" WHERE "secret"."id" = $1`, sid)
		return treatUpdateErr(res, err)
	})
}

func (v Vault) GetSecrets(ctx context.Context) ([]*Secret, error) {
	db := GetDB(ctx)
	rows, err := db.Query(`SELECT `+selectSecretFields+` FROM "secret" WHERE "secret"."team" = $1 AND "secret"."vault" = $2`, v.Team, v.Id)
	if isErrOrPanic(err) {
		return nil, util.NewErrorFrom(err)
	}
	secrets, err := scanSecrets(rows)
	if isErrOrPanic(err) {
		return nil, util.NewErrorFrom(err)
	}
	return secrets, nil
}

func (v Vault) GetSecret(ctx context.Context, sid string) (s *Secret, err error) {
	return s, doTx(ctx, func(tx *sql.Tx) error {
		s, err = v.getSecret(tx, sid)
		return err
	})
}

func (v Vault) getSecret(tx *sql.Tx, sid string) (*Secret, error) {
	s := &Secret{Id: sid}
	r := tx.QueryRow(`SELECT `+selectSecretFields+` FROM "secret" WHERE "secret"."team" = $1 AND "secret"."vault" = $2 AND "secret"."id" = $3`, v.Team, v.Id, sid)
	err := s.dbScanRow(r)
	if isNotExistsErr(err) {
		return nil, util.NewErrorFrom(ErrDoesntExist)
	}
	if isErrOrPanic(err) {
		return nil, util.NewErrorFrom(err)
	}
	return s, nil
}
<commit_msg>fix: Only get the latest version of the secret<commit_after>package models

import (
	"context"
	"database/sql"
	"time"

	"github.com/keydotcat/keycatd/util"
)

type Vault struct {
	Id        string    `scaneo:"pk" json:"id"`
	Team      string    `scaneo:"pk" json:"-"`
	Version   uint32    `json:"version"`
	PublicKey []byte    `json:"public_key"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

func createVault(tx *sql.Tx, id, team string, vkp VaultKeyPair) (*Vault, error) {
	v := &Vault{Id: id, Team: team, Version: 1, PublicKey: vkp.PublicKey}
	if err := v.insert(tx); err != nil {
		return nil, err
	}
	for u, k := range vkp.Keys {
		if err := v.addUser(tx, u, k); err != nil {
			return nil, err
		}
	}
	return v, nil
}

func (v *Vault) insert(tx *sql.Tx) error {
	if err := v.validate(); err != nil {
		return err
	}
	now := time.Now().UTC()
	v.CreatedAt = now
	v.UpdatedAt = now
	v.Version = 1
	_, err := v.dbInsert(tx)
	switch {
	case IsDuplicateErr(err):
		return util.NewErrorFrom(ErrAlreadyExists)
	case isErrOrPanic(err):
		return err
	}
	return nil
}

func (v *Vault) update(tx *sql.Tx) error {
	if err := v.validate(); err != nil {
		return err
	}
	v.UpdatedAt = time.Now().UTC()
	res, err := tx.Exec(`UPDATE "vault" SET "version" = "version" + 1, "updated_at" = $1 WHERE "team" = $2 AND "id" = $3`, v.UpdatedAt, v.Team, v.Id)
	if err := treatUpdateErr(res, err); err != nil {
		return err
	}
	r := tx.QueryRow(`SELECT "version" FROM "vault" WHERE "team" = $1 AND "id" = $2`, v.Team, v.Id)
	err = r.Scan(&v.Version)
	if isNotExistsErr(err) {
		return util.NewErrorFrom(ErrDoesntExist)
	}
	if isErrOrPanic(err) {
		return err
	}
	return nil
}

func (v Vault) validate() error {
	errs := util.NewErrorFields().(*util.Error)
	if len(v.Id) == 0 {
		errs.SetFieldError("vault_id", "missing")
	}
	if len(v.Team) == 0 {
		errs.SetFieldError("vault_team", "missing")
	}
	if len(v.PublicKey) != publicKeyPackSize {
		errs.SetFieldError("vault_public_key", "invalid")
	}
	if v.Version == 0 {
		errs.SetFieldError("version", "invalid")
	}
	return errs.SetErrorOrCamo(ErrAlreadyExists)
}

func (v Vault) AddUsers(ctx context.Context, userKeys map[string][]byte) error {
	for _, k := range userKeys {
		if _, err := verifyAndUnpack(v.PublicKey, k); err != nil {
			return err
		}
	}
	return doTx(ctx, func(tx *sql.Tx) error {
		t := &Team{Id: v.Team}
		users, err := t.getUsers(tx)
		if err != nil {
			return err
		}
		for uk := range userKeys {
			found := false
			for _, user := range users {
				if user.Id == uk {
					found = true
					break
				}
			}
			if !found {
				return util.NewErrorFrom(ErrNotInTeam)
			}
		}
		for u, k := range userKeys {
			if err := v.addUser(tx, u, k); err != nil {
				if IsDuplicateErr(err) {
					return util.NewErrorFrom(ErrAlreadyExists)
				}
				return err
			}
		}
		return nil
	})
}

func (v Vault) GetUserIds(ctx context.Context) (uids []string, err error) {
	return uids, doTx(ctx, func(tx *sql.Tx) error {
		uids, err = v.getUserIds(tx)
		return err
	})
}

func (v Vault) getUserIds(tx *sql.Tx) ([]string, error) {
	rows, err := tx.Query(`SELECT "user" FROM "vault_user" WHERE "vault_user"."vault" = $1 AND "vault_user"."team" = $2`, v.Id, v.Team)
	if isErrOrPanic(err) {
		return nil, util.NewErrorFrom(err)
	}
	var users []string
	var uid string
	for rows.Next() {
		err = rows.Scan(&uid)
		if isErrOrPanic(err) {
			return nil, util.NewErrorFrom(err)
		}
		users = append(users, uid)
	}
	if err = rows.Err(); isErrOrPanic(err) {
		return nil, util.NewErrorFrom(err)
	}
	return users, nil
}

func (v Vault) addUser(tx *sql.Tx, username string, key []byte) error {
	if err := v.update(tx); err != nil {
		return err
	}
	vu := &vaultUser{Team: v.Team, Vault: v.Id, User: username, Key: key}
	return vu.insert(tx)
}

func (v Vault) RemoveUser(ctx context.Context, username string) error {
	return doTx(ctx, func(tx *sql.Tx) error {
		t := &Team{Id: v.Team}
		tu, err := t.getUserAffiliation(tx, username)
		if err != nil {
			return err
		}
		if tu.Admin {
			return util.NewErrorFrom(err)
		}
		return v.removeUser(tx, username)
	})
}

func (v Vault) removeUser(tx *sql.Tx, username string) error {
	vu := &vaultUser{Team: v.Team, Vault: v.Id, User: username}
	return treatUpdateErr(vu.dbDelete(tx))
}

func (v *Vault) AddSecret(ctx context.Context, s *Secret) error {
	s.Team = v.Team
	s.Vault = v.Id
	if _, err := verifyAndUnpack(v.PublicKey, s.Data); err != nil {
		return err
	}
	var err error
	for retry := 0; retry < 3; retry++ {
		err = doTx(ctx, func(tx *sql.Tx) error {
			if err := v.update(tx); err != nil {
				return err
			}
			s.VaultVersion = v.Version
			return s.insert(tx)
		})
		if err == ErrAlreadyExists {
			continue
		}
		break
	}
	return err
}

func (v *Vault) AddSecretList(ctx context.Context, sl []*Secret) error {
	for _, s := range sl {
		s.Team = v.Team
		s.Vault = v.Id
		if _, err := verifyAndUnpack(v.PublicKey, s.Data); err != nil {
			return err
		}
	}
	var err error
	for retry := 0; retry < 3; retry++ {
		err = doTx(ctx, func(tx *sql.Tx) error {
			for _, s := range sl {
				if err := v.update(tx); err != nil {
					return err
				}
				s.VaultVersion = v.Version
				if err := s.insert(tx); err != nil {
					return err
				}
			}
			return nil
		})
		if err == ErrAlreadyExists {
			continue
		}
		break
	}
	return err
}

func (v *Vault) UpdateSecret(ctx context.Context, s *Secret) error {
	return doTx(ctx, func(tx *sql.Tx) error {
		os, err := v.getSecret(tx, s.Id)
		if err != nil {
			return err
		}
		if err := v.update(tx); err != nil {
			return err
		}
		s.Team = os.Team
		s.Vault = os.Vault
		s.Version = os.Version + 1
		s.VaultVersion = v.Version
		return s.update(tx)
	})
}

func (v *Vault) DeleteSecret(ctx context.Context, sid string) error {
	return doTx(ctx, func(tx *sql.Tx) error {
		if err := v.update(tx); err != nil {
			return err
		}
		res, err := tx.Exec(`DELETE FROM "secret" WHERE "secret"."id" = $1`, sid)
		return treatUpdateErr(res, err)
	})
}

func (v Vault) GetSecrets(ctx context.Context) ([]*Secret, error) {
	db := GetDB(ctx)
	rows, err := db.Query(`SELECT `+selectSecretFields+` FROM "secret" WHERE "secret"."team" = $1 AND "secret"."vault" = $2`, v.Team, v.Id)
	if isErrOrPanic(err) {
		return nil, util.NewErrorFrom(err)
	}
	secrets, err := scanSecrets(rows)
	if isErrOrPanic(err) {
		return nil, util.NewErrorFrom(err)
	}
	return secrets, nil
}

func (v Vault) GetSecret(ctx context.Context, sid string) (s *Secret, err error) {
	return s, doTx(ctx, func(tx *sql.Tx) error {
		s, err = v.getSecret(tx, sid)
		return err
	})
}

func (v Vault) getSecret(tx *sql.Tx, sid string) (*Secret, error) {
	s := &Secret{Id: sid}
	r := tx.QueryRow(`SELECT `+selectSecretFields+` FROM "secret" WHERE "secret"."team" = $1 AND "secret"."vault" = $2 AND "secret"."id" = $3 ORDER BY "secret"."version" DESC LIMIT 1`, v.Team, v.Id, sid)
	err := s.dbScanRow(r)
	if isNotExistsErr(err) {
		return nil, util.NewErrorFrom(ErrDoesntExist)
	}
	if isErrOrPanic(err) {
		return nil, util.NewErrorFrom(err)
	}
	return s, nil
}
<|endoftext|>


<commit_before>// Package edit provides sam-style editing of rune buffers.
// See sam(1) for an overview: http://swtch.com/plan9port/man/man1/sam.html.
package edit

import (
	"errors"
	"sync"
	"unicode"

	"github.com/eaburns/T/re1"
	"github.com/eaburns/T/runes"
)

// TODO(eaburns): Find a good size.
const blockSize = 1 << 20

// A Buffer is an editable rune buffer.
type Buffer struct {
	runes *runes.Buffer
	lock  sync.Mutex
	eds   []*Editor
}

// NewBuffer returns a new, empty buffer.
func NewBuffer() *Buffer {
	return newBufferRunes(runes.NewBuffer(blockSize))
}

// NewBufferRunes is like NewBuffer, but the buffer uses the given runes.
func newBufferRunes(r *runes.Buffer) *Buffer {
	return &Buffer{runes: r}
}

// Close closes the buffer and all of its editors.
// After Close is called, the buffer is no longer editable.
func (b *Buffer) Close() error {
	b.lock.Lock()
	defer b.lock.Unlock()
	return b.runes.Close()
}

// Size returns the number of runes in the buffer.
// If the rune is out of range it panics.
// If there is an error reading, it panics a RuneReadError containing the error.
//
// The caller must hold b.lock.
func (b *Buffer) size() int64 { return b.runes.Size() }

// Returns the ith rune in the buffer.
//
// The caller must hold b.lock.
func (b *Buffer) rune(i int64) (rune, error) { return b.runes.Rune(i) }

// Change changes the runes in the range.
//
// The caller must hold b.lock.
func (b *Buffer) change(r addr, rs []rune) error {
	if _, err := b.runes.Delete(r.size(), r.from); err != nil {
		return err
	}
	if _, err := b.runes.Insert(rs, r.from); err != nil {
		return err
	}
	for _, e := range b.eds {
		e.update(r, int64(len(rs)))
	}
	return nil
}

// An Editor provides sam-like editing functionality on a buffer of runes.
type Editor struct {
	buf   *Buffer
	marks map[rune]addr
}

// NewEditor returns a new editor that edits the buffer.
func (b *Buffer) NewEditor() *Editor {
	b.lock.Lock()
	defer b.lock.Unlock()

	ed := &Editor{buf: b, marks: make(map[rune]addr, 2)}
	b.eds = append(b.eds, ed)
	return ed
}

// Close closes the editor.
func (ed *Editor) Close() error {
	ed.buf.lock.Lock()
	defer ed.buf.lock.Unlock()

	eds := ed.buf.eds
	for i := range eds {
		if eds[i] == ed {
			ed.buf.eds = append(eds[:i], eds[:i+1]...)
			return nil
		}
	}
	return errors.New("already closed")
}

// Read returns the runes at an address in the buffer
// and sets dot to the address.
func (ed *Editor) Read(a Address) ([]rune, error) {
	ed.buf.lock.Lock()
	defer ed.buf.lock.Unlock()

	r, err := a.addr(ed)
	if err != nil {
		return nil, err
	}
	ed.marks['.'] = r
	rs := make([]rune, r.size())
	if _, err := ed.buf.runes.Read(rs, r.from); err != nil {
		return nil, err
	}
	return rs, nil
}

// Change changes the range to the given runes
// and sets dot to the address of the changed runes.
func (ed *Editor) Change(a Address, rs []rune) error {
	ed.buf.lock.Lock()
	defer ed.buf.lock.Unlock()

	r, err := a.addr(ed)
	if err != nil {
		return err
	}
	if err := ed.buf.change(r, rs); err != nil {
		return err
	}
	ed.marks['.'] = addr{from: r.from, to: r.from + int64(len(rs))}
	return nil
}

// Append inserts the runes after the address
// and sets dot to the address of the appended runes.
func (ed *Editor) Append(ad Address, rs []rune) error {
	return ed.Change(ad.Plus(Rune(0)), rs)
}

// Delete deletes the runes at the address
// and sets dot to the address of the empty string
// where the runes were deleted.
func (ed *Editor) Delete(a Address) error {
	return ed.Change(a, []rune{})
}

// Insert inserts the runes before the address
// and sets dot to the address of the inserted runes.
func (ed *Editor) Insert(ad Address, rs []rune) error {
	return ed.Change(ad.Minus(Rune(0)), rs)
}

// Mark sets a mark to an address.
// The mark must be either a lower-case or upper-case letter or dot: [a-zA-Z.].
// Any other mark is an error.
// If the mark is . then dot is set to the address.
// Otherwise the named mark is set to the address.
func (ed *Editor) Mark(a Address, m rune) error {
	if !isMarkRune(m) && m != '.' {
		return errors.New("bad mark: " + string(m))
	}
	r, err := a.addr(ed)
	if err != nil {
		return err
	}
	ed.marks[m] = r
	return nil
}

// Substitute substitutes text for the first match
// of the regular expression in the addressed range.
// When substituting, a backslash followed by a digit d
// stands for the string that matched the d-th subexpression.
// \n is a literal newline.
// If g is true then all matches in the address range are substituted.
// Dot is set to the modified address.
func (ed *Editor) Substitute(a Address, re *re1.Regexp, sub []rune, g bool) error {
	ed.buf.lock.Lock()
	defer ed.buf.lock.Unlock()

	r, err := a.addr(ed)
	if err != nil {
		return err
	}
	r0 := r

	for {
		d, m, err := ed.sub1(r, re, sub)
		if err != nil {
			return err
		}
		if m == nil {
			break
		}
		r0.to += d
		r.to += d
		r.from = m[0][1] + d
		if !g {
			break
		}
	}
	ed.marks['.'] = r0
	return nil
}

func (ed *Editor) sub1(r addr, re *re1.Regexp, sub []rune) (int64, [][2]int64, error) {
	m, err := ed.subMatch(r, re)
	if err != nil || m == nil {
		return 0, m, err
	}
	repl, err := ed.subText(m, sub)
	if err != nil {
		return 0, nil, err
	}
	if err := ed.buf.change(addr{m[0][0], m[0][1]}, repl); err != nil {
		return 0, nil, err
	}
	return int64(len(repl)) - (m[0][1] - m[0][0]), m, err
}

func (ed *Editor) subText(m [][2]int64, sub []rune) ([]rune, error) {
	var rs []rune
	for i := 0; i < len(sub); i++ {
		d := escDigit(sub[i:])
		if d < 0 {
			rs = append(rs, sub[i])
			continue
		}
		sub, err := ed.subExpr(m, d)
		if err != nil {
			return nil, err
		}
		rs = append(rs, sub...)
		i++
	}
	return rs, nil
}

// EscDigit returns the digit from \[0-9]
// or -1 if the text does not represent an escaped digit.
func escDigit(sub []rune) int {
	if len(sub) >= 2 && sub[0] == '\\' && unicode.IsDigit(sub[1]) {
		return int(sub[1] - '0')
	}
	return -1
}

func (ed *Editor) subExpr(m [][2]int64, n int) ([]rune, error) {
	if n >= len(m) {
		return nil, nil
	}
	rs := make([]rune, m[n][1]-m[n][0])
	if _, err := ed.buf.runes.Read(rs, m[n][0]); err != nil {
		return nil, err
	}
	return rs, nil
}

type runeSlice struct {
	buf *runes.Buffer
	addr
	err error
}

func (rs *runeSlice) Size() int64 { return rs.size() }

func (rs *runeSlice) Rune(i int64) rune {
	switch {
	case i < 0 || i >= rs.size():
		panic("index out of bounds")
	case rs.err != nil:
		return -1
	}
	r, err := rs.buf.Rune(rs.from + i)
	if err != nil {
		rs.err = err
		return -1
	}
	return r
}

func (ed *Editor) subMatch(r addr, re *re1.Regexp) ([][2]int64, error) {
	rs := &runeSlice{buf: ed.buf.runes, addr: r}
	m := re.Match(rs, 0)
	for i := range m {
		m[i][0] += r.from
		m[i][1] += r.from
	}
	return m, rs.err
}

// Edit parses a command and performs its edit on the buffer.
//
// In the following, text surrounded by / represents delimited text.
// The delimiter can be any character, it need not be /.
// Trailing delimiters may be elided, but the opening delimiter must be present.
// In delimited text, \ is an escape; the following character is interpreted literally,
// except \n which represents a literal newline.
// Items in {} are optional.
//
// Commands are:
// 	{addr} a/text/
//	or
//	{addr} a
//	lines of text
//	.	Appends after the addressed text.
//		If an address is not supplied, dot is used.
//		Dot is set to the address.
//	c
//	i	Just like a, but c changes the addressed text
//		and i inserts before the addressed text.
//		Dot is set to the address.
//	{addr} d
//		Deletes the addressed text.
//		If an address is not supplied, dot is used.
//		Dot is set to the address.
//	{addr} m {[a-zA-Z]}
//		Sets the named mark to the address.
//		If an address is not supplied, dot is used.
//		If a mark name is not given, dot is set.
//		Dot is set to the address.
//	{addr} s/regexp/text/
//		Substitute substitutes text for the first match
// 		of the regular expression in the addressed range.
// 		When substituting, a backslash followed by a digit d
// 		stands for the string that matched the d-th subexpression.
//		\n is a literal newline.
// 		If the delimiter after the text is followed by the letter g
//		then all matches in the address range are substituted.
//		If an address is not supplied, dot is used.
//		Dot is set to the modified address.
func (ed *Editor) Edit(cmd []rune) error {
	addr, n, err := Addr(cmd)
	switch {
	case err != nil:
		return err
	case addr == nil:
		addr = Dot
	case len(cmd) == n:
		return errors.New("missing command")
	default:
		cmd = cmd[n:]
	}
	switch c := cmd[0]; c {
	case 'a':
		return ed.Append(addr, parseText(cmd[1:]))
	case 'c':
		return ed.Change(addr, parseText(cmd[1:]))
	case 'i':
		return ed.Insert(addr, parseText(cmd[1:]))
	case 'd':
		return ed.Delete(addr)
	case 'm':
		return ed.Mark(addr, parseMarkRune(cmd[1:]))
	case 's':
		re, err := re1.Compile(cmd[1:], re1.Options{Delimited: true})
		if err != nil {
			return err
		}
		exp := re.Expression()
		cmd = cmd[1+len(exp):]
		sub := parseDelimited(exp[0], true, cmd)
		g := len(sub) < len(cmd)-1 && cmd[len(sub)+1] == 'g'
		return ed.Substitute(addr, re, sub, g)
	default:
		return errors.New("unknown command: " + string(c))
	}
}

func parseText(cmd []rune) []rune {
	switch {
	case len(cmd) > 0 && cmd[0] == '\n':
		return parseLines(cmd)
	case len(cmd) > 0:
		return parseDelimited(cmd[0], false, cmd[1:])
	default:
		return nil
	}
}

func parseLines(cmd []rune) []rune {
	var rs []rune
	for i := 1; i < len(cmd); i++ {
		rs = append(rs, cmd[i])
		if i < len(cmd)-1 && cmd[i] == '\n' && cmd[i+1] == '.' {
			break
		}
	}
	return rs
}

func parseDelimited(d rune, digits bool, cmd []rune) []rune {
	var rs []rune
	for i := 0; i < len(cmd) && cmd[i] != d; i++ {
		if cmd[i] == '\\' && i < len(cmd)-1 {
			if digits && unicode.IsDigit(cmd[i+1]) {
				rs = append(rs, '\\')
			}
			i++
			if cmd[i] == 'n' {
				cmd[i] = '\n'
			}
		}
		rs = append(rs, cmd[i])
	}
	return rs
}

func parseMarkRune(cmd []rune) rune {
	var i int
	for ; i < len(cmd) && unicode.IsSpace(cmd[i]); i++ {
	}
	if i < len(cmd) && isMarkRune(cmd[i]) {
		return cmd[i]
	}
	return '.'
}

// Update updates the Editor's addresses
// given an addr that changed and its new size.
func (ed *Editor) update(r addr, n int64) {
	for m := range ed.marks {
		ed.marks[m] = ed.marks[m].update(r, n)
	}
}
<commit_msg>Add the Print and Where edits.<commit_after>// Package edit provides sam-style editing of rune buffers.
// See sam(1) for an overview: http://swtch.com/plan9port/man/man1/sam.html.
package edit

import (
	"errors"
	"sync"
	"unicode"

	"github.com/eaburns/T/re1"
	"github.com/eaburns/T/runes"
)

// TODO(eaburns): Find a good size.
const blockSize = 1 << 20

// A Buffer is an editable rune buffer.
type Buffer struct {
	runes *runes.Buffer
	lock  sync.Mutex
	eds   []*Editor
}

// NewBuffer returns a new, empty buffer.
func NewBuffer() *Buffer {
	return newBufferRunes(runes.NewBuffer(blockSize))
}

// NewBufferRunes is like NewBuffer, but the buffer uses the given runes.
func newBufferRunes(r *runes.Buffer) *Buffer {
	return &Buffer{runes: r}
}

// Close closes the buffer and all of its editors.
// After Close is called, the buffer is no longer editable.
func (b *Buffer) Close() error {
	b.lock.Lock()
	defer b.lock.Unlock()
	return b.runes.Close()
}

// Size returns the number of runes in the buffer.
// If the rune is out of range it panics.
// If there is an error reading, it panics a RuneReadError containing the error.
//
// The caller must hold b.lock.
func (b *Buffer) size() int64 { return b.runes.Size() }

// Returns the ith rune in the buffer.
//
// The caller must hold b.lock.
func (b *Buffer) rune(i int64) (rune, error) { return b.runes.Rune(i) }

// Change changes the runes in the range.
//
// The caller must hold b.lock.
func (b *Buffer) change(r addr, rs []rune) error {
	if _, err := b.runes.Delete(r.size(), r.from); err != nil {
		return err
	}
	if _, err := b.runes.Insert(rs, r.from); err != nil {
		return err
	}
	for _, e := range b.eds {
		e.update(r, int64(len(rs)))
	}
	return nil
}

// An Editor provides sam-like editing functionality on a buffer of runes.
type Editor struct {
	buf   *Buffer
	marks map[rune]addr
}

// NewEditor returns a new editor that edits the buffer.
func (b *Buffer) NewEditor() *Editor {
	b.lock.Lock()
	defer b.lock.Unlock()

	ed := &Editor{buf: b, marks: make(map[rune]addr, 2)}
	b.eds = append(b.eds, ed)
	return ed
}

// Close closes the editor.
func (ed *Editor) Close() error {
	ed.buf.lock.Lock()
	defer ed.buf.lock.Unlock()

	eds := ed.buf.eds
	for i := range eds {
		if eds[i] == ed {
			ed.buf.eds = append(eds[:i], eds[:i+1]...)
			return nil
		}
	}
	return errors.New("already closed")
}

// Read returns the runes at an address in the buffer
// and sets dot to the address.
func (ed *Editor) Read(a Address) ([]rune, error) {
	ed.buf.lock.Lock()
	defer ed.buf.lock.Unlock()

	r, err := a.addr(ed)
	if err != nil {
		return nil, err
	}
	ed.marks['.'] = r
	rs := make([]rune, r.size())
	if _, err := ed.buf.runes.Read(rs, r.from); err != nil {
		return nil, err
	}
	return rs, nil
}

// Change changes the range to the given runes
// and sets dot to the address of the changed runes.
func (ed *Editor) Change(a Address, rs []rune) error {
	ed.buf.lock.Lock()
	defer ed.buf.lock.Unlock()

	r, err := a.addr(ed)
	if err != nil {
		return err
	}
	if err := ed.buf.change(r, rs); err != nil {
		return err
	}
	ed.marks['.'] = addr{from: r.from, to: r.from + int64(len(rs))}
	return nil
}

// Append inserts the runes after the address
// and sets dot to the address of the appended runes.
func (ed *Editor) Append(ad Address, rs []rune) error {
	return ed.Change(ad.Plus(Rune(0)), rs)
}

// Delete deletes the runes at the address
// and sets dot to the address of the empty string
// where the runes were deleted.
func (ed *Editor) Delete(a Address) error {
	return ed.Change(a, []rune{})
}

// Insert inserts the runes before the address
// and sets dot to the address of the inserted runes.
func (ed *Editor) Insert(ad Address, rs []rune) error {
	return ed.Change(ad.Minus(Rune(0)), rs)
}

// Mark sets a mark to an address.
// The mark must be either a lower-case or upper-case letter or dot: [a-zA-Z.].
// Any other mark is an error.
// If the mark is . then dot is set to the address.
// Otherwise the named mark is set to the address.
func (ed *Editor) Mark(a Address, m rune) error {
	if !isMarkRune(m) && m != '.' {
		return errors.New("bad mark: " + string(m))
	}
	r, err := a.addr(ed)
	if err != nil {
		return err
	}
	ed.marks[m] = r
	return nil
}

// Print returns the runes identified by the address.
// Dot is set to the address.
func (ed *Editor) Print(a Address) ([]rune, error) {
	r, err := a.addr(ed)
	if err != nil {
		return nil, err
	}
	rs := make([]rune, r.size())
	if _, err := ed.buf.runes.Read(rs, r.from); err != nil {
		return nil, err
	}
	ed.marks['.'] = r
	return rs, nil
}

// Where returns the rune offsets of an address.
// The from offset is inclusive and to is exclusive.
// Dot is set to the address.
func (ed *Editor) Where(a Address) (from, to int64, err error) {
	r, err := a.addr(ed)
	if err != nil {
		return 0, 0, err
	}
	ed.marks['.'] = r
	return r.from, r.to, nil
}

// Substitute substitutes text for the first match
// of the regular expression in the addressed range.
// When substituting, a backslash followed by a digit d
// stands for the string that matched the d-th subexpression.
// \n is a literal newline.
// If g is true then all matches in the address range are substituted.
// Dot is set to the modified address.
func (ed *Editor) Substitute(a Address, re *re1.Regexp, sub []rune, g bool) error {
	ed.buf.lock.Lock()
	defer ed.buf.lock.Unlock()

	r, err := a.addr(ed)
	if err != nil {
		return err
	}
	r0 := r

	for {
		d, m, err := ed.sub1(r, re, sub)
		if err != nil {
			return err
		}
		if m == nil {
			break
		}
		r0.to += d
		r.to += d
		r.from = m[0][1] + d
		if !g {
			break
		}
	}
	ed.marks['.'] = r0
	return nil
}

func (ed *Editor) sub1(r addr, re *re1.Regexp, sub []rune) (int64, [][2]int64, error) {
	m, err := ed.subMatch(r, re)
	if err != nil || m == nil {
		return 0, m, err
	}
	repl, err := ed.subText(m, sub)
	if err != nil {
		return 0, nil, err
	}
	if err := ed.buf.change(addr{m[0][0], m[0][1]}, repl); err != nil {
		return 0, nil, err
	}
	return int64(len(repl)) - (m[0][1] - m[0][0]), m, err
}

func (ed *Editor) subText(m [][2]int64, sub []rune) ([]rune, error) {
	var rs []rune
	for i := 0; i < len(sub); i++ {
		d := escDigit(sub[i:])
		if d < 0 {
			rs = append(rs, sub[i])
			continue
		}
		sub, err := ed.subExpr(m, d)
		if err != nil {
			return nil, err
		}
		rs = append(rs, sub...)
		i++
	}
	return rs, nil
}

// EscDigit returns the digit from \[0-9]
// or -1 if the text does not represent an escaped digit.
func escDigit(sub []rune) int {
	if len(sub) >= 2 && sub[0] == '\\' && unicode.IsDigit(sub[1]) {
		return int(sub[1] - '0')
	}
	return -1
}

func (ed *Editor) subExpr(m [][2]int64, n int) ([]rune, error) {
	if n >= len(m) {
		return nil, nil
	}
	rs := make([]rune, m[n][1]-m[n][0])
	if _, err := ed.buf.runes.Read(rs, m[n][0]); err != nil {
		return nil, err
	}
	return rs, nil
}

type runeSlice struct {
	buf *runes.Buffer
	addr
	err error
}

func (rs *runeSlice) Size() int64 { return rs.size() }

func (rs *runeSlice) Rune(i int64) rune {
	switch {
	case i < 0 || i >= rs.size():
		panic("index out of bounds")
	case rs.err != nil:
		return -1
	}
	r, err := rs.buf.Rune(rs.from + i)
	if err != nil {
		rs.err = err
		return -1
	}
	return r
}

func (ed *Editor) subMatch(r addr, re *re1.Regexp) ([][2]int64, error) {
	rs := &runeSlice{buf: ed.buf.runes, addr: r}
	m := re.Match(rs, 0)
	for i := range m {
		m[i][0] += r.from
		m[i][1] += r.from
	}
	return m, rs.err
}

// Edit parses a command and performs its edit on the buffer.
//
// In the following, text surrounded by / represents delimited text.
// The delimiter can be any character, it need not be /.
// Trailing delimiters may be elided, but the opening delimiter must be present.
// In delimited text, \ is an escape; the following character is interpreted literally,
// except \n which represents a literal newline.
// Items in {} are optional.
//
// Commands are:
// 	{addr} a/text/
//	or
//	{addr} a
//	lines of text
//	.	Appends after the addressed text.
//		If an address is not supplied, dot is used.
//		Dot is set to the address.
//	c
//	i	Just like a, but c changes the addressed text
//		and i inserts before the addressed text.
//		Dot is set to the address.
//	{addr} d
//		Deletes the addressed text.
//		If an address is not supplied, dot is used.
//		Dot is set to the address.
//	{addr} m {[a-zA-Z]}
//		Sets the named mark to the address.
//		If an address is not supplied, dot is used.
//		If a mark name is not given, dot is set.
//		Dot is set to the address.
//	{addr} s/regexp/text/
//		Substitute substitutes text for the first match
// 		of the regular expression in the addressed range.
// 		When substituting, a backslash followed by a digit d
// 		stands for the string that matched the d-th subexpression.
//		\n is a literal newline.
// 		If the delimiter after the text is followed by the letter g
//		then all matches in the address range are substituted.
//		If an address is not supplied, dot is used.
//		Dot is set to the modified address.
func (ed *Editor) Edit(cmd []rune) error {
	addr, n, err := Addr(cmd)
	switch {
	case err != nil:
		return err
	case addr == nil:
		addr = Dot
	case len(cmd) == n:
		return errors.New("missing command")
	default:
		cmd = cmd[n:]
	}
	switch c := cmd[0]; c {
	case 'a':
		return ed.Append(addr, parseText(cmd[1:]))
	case 'c':
		return ed.Change(addr, parseText(cmd[1:]))
	case 'i':
		return ed.Insert(addr, parseText(cmd[1:]))
	case 'd':
		return ed.Delete(addr)
	case 'm':
		return ed.Mark(addr, parseMarkRune(cmd[1:]))
	case 's':
		re, err := re1.Compile(cmd[1:], re1.Options{Delimited: true})
		if err != nil {
			return err
		}
		exp := re.Expression()
		cmd = cmd[1+len(exp):]
		sub := parseDelimited(exp[0], true, cmd)
		g := len(sub) < len(cmd)-1 && cmd[len(sub)+1] == 'g'
		return ed.Substitute(addr, re, sub, g)
	default:
		return errors.New("unknown command: " + string(c))
	}
}

func parseText(cmd []rune) []rune {
	switch {
	case len(cmd) > 0 && cmd[0] == '\n':
		return parseLines(cmd)
	case len(cmd) > 0:
		return parseDelimited(cmd[0], false, cmd[1:])
	default:
		return nil
	}
}

func parseLines(cmd []rune) []rune {
	var rs []rune
	for i := 1; i < len(cmd); i++ {
		rs = append(rs, cmd[i])
		if i < len(cmd)-1 && cmd[i] == '\n' && cmd[i+1] == '.' {
			break
		}
	}
	return rs
}

func parseDelimited(d rune, digits bool, cmd []rune) []rune {
	var rs []rune
	for i := 0; i < len(cmd) && cmd[i] != d; i++ {
		if cmd[i] == '\\' && i < len(cmd)-1 {
			if digits && unicode.IsDigit(cmd[i+1]) {
				rs = append(rs, '\\')
			}
			i++
			if cmd[i] == 'n' {
				cmd[i] = '\n'
			}
		}
		rs = append(rs, cmd[i])
	}
	return rs
}

func parseMarkRune(cmd []rune) rune {
	var i int
	for ; i < len(cmd) && unicode.IsSpace(cmd[i]); i++ {
	}
	if i < len(cmd) && isMarkRune(cmd[i]) {
		return cmd[i]
	}
	return '.'
}

// Update updates the Editor's addresses
// given an addr that changed and its new size.
func (ed *Editor) update(r addr, n int64) {
	for m := range ed.marks {
		ed.marks[m] = ed.marks[m].update(r, n)
	}
}
<|endoftext|>


<commit_before>package edn

import "fmt"
import "strings"
import "unicode"
import re "regexp"

// A lot of this is based on: http://cuddle.googlecode.com/hg/talk/lex.html

type tokenType int

const (
	tEOF tokenType = 1 << iota
	tOpenList
	tCloseList
	tOpenMap
	tCloseMapOrSet
	tOpenVector
	tCloseVector
	tQuoteNextForm
	tOpenSet
	tString
	tSymbol
	tKeyword
	tNumber
	tMetadata
	tIgnoreNextForm
	tWhitespace // needed?
	tComment    // needed?

	tError
)

const (
	numberRegex = "[+-]?(0|[1-9]\\d*)"
	stringRegex = "\"(\\\\\"|[^\"\\\\])*?\"" // TODO: do multiline strings work?
)

type token struct {
	kind  tokenType
	value string
}

func (t *token) String() string {
	switch t.kind {
	case tEOF:
		return "EOF"
	case tError:
		return t.value
	default:
		return fmt.Sprintf("%q", t.value)
	}

	panic("unreachable")
}

func isWhitespace(ch rune) bool {
	return unicode.IsSpace(ch) || ch == ','
}

func lexEDN(l *lexer) {
	for {
		ch, _, err := l.read()

		if err != nil {
			return
		}

		switch ch {
		case '(':
			l.emit(tOpenList)
		case '[':
			l.emit(tOpenVector)
		case '{':
			l.emit(tOpenMap)
		case ')':
			l.emit(tCloseList)
		case ']':
			l.emit(tCloseVector)
		case '}':
			l.emit(tCloseMapOrSet)
		case ':': // keyword
			// TODO: Read keyword value
			l.emit(tKeyword)
		case '\'':
			l.emit(tQuoteNextForm)
		case '^':
			l.emit(tMetadata)
		case '#': // ...
			ch, _, _ := l.read()

			switch ch {
			case '_':
				l.emit(tIgnoreNextForm)
			case '{':
				l.emit(tOpenSet)
			case '^':
				l.emit(tMetadata)
			}
		case '"':
			l.readWhileRegexpMatch(stringRegex)
			l.emit(tString)
		case ';':
			for {
				ch, _, _ := l.read()
				if ch == '\n' {
					l.unread()
					break
				}
			}
			l.emit(tComment)
		case '\t', '\n', ',', ' ': // whitespace
			for {
				ch, _, _ := l.read()
				if !isWhitespace(ch) {
					l.unread()
					break
				}
			}
			// l.emit(tWhitespace)
			l.start = l.position // Temporarily ignore whitespace
		default:
			switch {
			case ch == '+' || ch == '-' || unicode.IsNumber(ch):
				l.readWhileRegexpMatch(numberRegex)
				l.emit(tNumber)
			default:
				// TODO: proper error handling
				panic("Unexpected character " + fmt.Sprintf("'%c'", ch))
			}

		}
	}
}

type lexer struct {
	input        string
	reader       *strings.Reader
	start        int
	position     int
	lastRuneSize int
	tokens       chan token
}

func (l *lexer) peek() (ch rune, size int, err error) {
	ch, size, err = l.read()
	l.unread()
	return
}

// FIXME: can only unread one rune
func (l *lexer) unread() {
	l.reader.UnreadRune()
	l.position -= l.lastRuneSize
	l.lastRuneSize = 0
}

func (l *lexer) read() (ch rune, size int, err error) {
	ch, size, err = l.reader.ReadRune()
	l.lastRuneSize = size
	l.position += size
	return
}

func (l *lexer) readWhileRegexpMatch(regexp string) {
	regexp = "^" + regexp
	reg, err := re.Compile(regexp)
	if err != nil {
		panic("Invalid regex: " + regexp)
	}

	indexes := reg.FindStringIndex(l.input[l.position - 1:])

	if indexes == nil {
		panic("No match")
	}

	if indexes[0] != 0 {
		// shouldn't happen due to anchoring
		panic("Unexpected regex match")
	}

	for i := 0; i < indexes[1] - 1; i++ {
		l.read()
	}
}

func (l *lexer) run() {
	lexEDN(l)
	l.emit(tEOF)
	close(l.tokens)
}

func (l *lexer) nextValue() string {
	return l.input[l.start:l.position]
}

func (l *lexer) emit(tt tokenType) {
	value := l.nextValue()
	l.start = l.position
	l.tokens <- token{kind: tt, value: value}
}

func (l *lexer) Next() (t token, more bool) {
	t, more = <-l.tokens
	return
}

func Lex(input string) *lexer {
	l := &lexer{
		input:  input,
		reader: strings.NewReader(input),
		tokens: make(chan token, 100),
	}

	go l.run()
	return l
}
<commit_msg>Rename regexp lexer func<commit_after>package edn

import "fmt"
import "strings"
import "unicode"
import re "regexp"

// A lot of this is based on: http://cuddle.googlecode.com/hg/talk/lex.html

type tokenType int

const (
	tEOF tokenType = 1 << iota
	tOpenList
	tCloseList
	tOpenMap
	tCloseMapOrSet
	tOpenVector
	tCloseVector
	tQuoteNextForm
	tOpenSet
	tString
	tSymbol
	tKeyword
	tNumber
	tMetadata
	tIgnoreNextForm
	tWhitespace // needed?
	tComment    // needed?

	tError
)

const (
	numberRegex = "[+-]?(0|[1-9]\\d*)"
	stringRegex = "\"(\\\\\"|[^\"\\\\])*?\"" // TODO: do multiline strings work?
)

type token struct {
	kind  tokenType
	value string
}

func (t *token) String() string {
	switch t.kind {
	case tEOF:
		return "EOF"
	case tError:
		return t.value
	default:
		return fmt.Sprintf("%q", t.value)
	}

	panic("unreachable")
}

func isWhitespace(ch rune) bool {
	return unicode.IsSpace(ch) || ch == ','
}

func lexEDN(l *lexer) {
	for {
		ch, _, err := l.read()

		if err != nil {
			return
		}

		switch ch {
		case '(':
			l.emit(tOpenList)
		case '[':
			l.emit(tOpenVector)
		case '{':
			l.emit(tOpenMap)
		case ')':
			l.emit(tCloseList)
		case ']':
			l.emit(tCloseVector)
		case '}':
			l.emit(tCloseMapOrSet)
		case ':': // keyword
			// TODO: Read keyword value
			l.emit(tKeyword)
		case '\'':
			l.emit(tQuoteNextForm)
		case '^':
			l.emit(tMetadata)
		case '#': // ...
			ch, _, _ := l.read()

			switch ch {
			case '_':
				l.emit(tIgnoreNextForm)
			case '{':
				l.emit(tOpenSet)
			case '^':
				l.emit(tMetadata)
			}
		case '"':
			l.readTokenMatchingRegexp(stringRegex)
			l.emit(tString)
		case ';':
			for {
				ch, _, _ := l.read()
				if ch == '\n' {
					l.unread()
					break
				}
			}
			l.emit(tComment)
		case '\t', '\n', ',', ' ': // whitespace
			for {
				ch, _, _ := l.read()
				if !isWhitespace(ch) {
					l.unread()
					break
				}
			}
			// l.emit(tWhitespace)
			l.start = l.position // Temporarily ignore whitespace
		default:
			switch {
			case ch == '+' || ch == '-' || unicode.IsNumber(ch):
				l.readTokenMatchingRegexp(numberRegex)
				l.emit(tNumber)
			default:
				// TODO: proper error handling
				panic("Unexpected character " + fmt.Sprintf("'%c'", ch))
			}

		}
	}
}

type lexer struct {
	input        string
	reader       *strings.Reader
	start        int
	position     int
	lastRuneSize int
	tokens       chan token
}

func (l *lexer) peek() (ch rune, size int, err error) {
	ch, size, err = l.read()
	l.unread()
	return
}

// FIXME: can only unread one rune
func (l *lexer) unread() {
	l.reader.UnreadRune()
	l.position -= l.lastRuneSize
	l.lastRuneSize = 0
}

func (l *lexer) read() (ch rune, size int, err error) {
	ch, size, err = l.reader.ReadRune()
	l.lastRuneSize = size
	l.position += size
	return
}

func (l *lexer) readTokenMatchingRegexp(regexp string) {
	regexp = "^" + regexp
	reg, err := re.Compile(regexp)
	if err != nil {
		panic("Invalid regex: " + regexp)
	}

	indexes := reg.FindStringIndex(l.input[l.position-1:])

	if indexes == nil {
		panic("No match")
	}

	if indexes[0] != 0 {
		// shouldn't happen due to anchoring
		panic("Unexpected regex match")
	}

	for i := 0; i < indexes[1]-1; i++ {
		l.read()
	}
}

func (l *lexer) run() {
	lexEDN(l)
	l.emit(tEOF)
	close(l.tokens)
}

func (l *lexer) nextValue() string {
	return l.input[l.start:l.position]
}

func (l *lexer) emit(tt tokenType) {
	value := l.nextValue()
	l.start = l.position
	l.tokens <- token{kind: tt, value: value}
}

func (l *lexer) Next() (t token, more bool) {
	t, more = <-l.tokens
	return
}

func Lex(input string) *lexer {
	l := &lexer{
		input:  input,
		reader: strings.NewReader(input),
		tokens: make(chan token, 100),
	}

	go l.run()
	return l
}
<|endoftext|>


<commit_before>package lib

import (
	auth3 "github.com/BytemarkHosting/auth-client"
	"github.com/BytemarkHosting/bytemark-client/lib/billing"
	"github.com/BytemarkHosting/bytemark-client/lib/brain"
	"github.com/BytemarkHosting/bytemark-client/lib/spp"
)

// Client provides the interface which all API clients should implement.
type Client interface {
	// Getters
	//

	// GetEndpoint returns the API endpoint currently in use.
	GetEndpoint() string

	// GetSessionFactors returns the factors provided when the current auth session was set up
	GetSessionFactors() []string
	// GetSessionToken returns the token for the current auth session
	GetSessionToken() string

	// GetSessionUser returns the user's name for the current auth session.
	GetSessionUser() string

	//
	// Setters
	//

	// SetDebugLevel sets the debug level / verbosity of the API client. 0 (default) is silent.
	SetDebugLevel(int)

	//
	// Auth
	//

	// AuthWithToken attempts to read sessiondata from auth for the given token. Returns nil on success or an error otherwise.
	AuthWithToken(string) error
	// AuthWithCredentials attempts to authenticate with the given credentials. Returns nil on success or an error otherwise.
	AuthWithCredentials(auth3.Credentials) error

	//
	// Requests
	//

	AllowInsecureRequests()
	BuildRequestNoAuth(method string, endpoint Endpoint, path string, parts ...string) (*Request, error)
	BuildRequest(method string, endpoint Endpoint, path string, parts ...string) (*Request, error)

	///////////////////////
	////// SPP STUFF //////
	///////////////////////

	CreateCreditCard(*spp.CreditCard) (string, error)
	CreateCreditCardWithToken(*spp.CreditCard, string) (string, error)

	///////////////////////
	//// BILLING STUFF ////
	///////////////////////

	// CreateAccount(*Account) (*Account, error) // TODO(telyn): figure out if CreateAccount is needed/useful
	GetSPPToken(cc spp.CreditCard, owner *billing.Person) (string, error)
	RegisterNewAccount(acc *Account) (*Account, error)

	////////////////////
	//// BRAIN STUFF ///
	////////////////////
	//
	// Parsers
	//

	ParseVirtualMachineName(string, ...*VirtualMachineName) (*VirtualMachineName, error)
	ParseGroupName(string, ...*GroupName) *GroupName
	ParseAccountName(string, ...string) string

	//
	// DEFINITIONS
	//
	ReadDefinitions() (*Definitions, error)

	//
	// ACCOUNTS
	//

	// GetAccount takes an account name or ID and returns a filled-out Account object
	GetAccount(name string) (account *Account, err error)
	// GetDefaultAccount gets the most-likely default account for the user.
	GetDefaultAccount() (account *Account, err error)
	// GetAccounts gets all the accounts the logged-in user can see.
	GetAccounts() (accounts []*Account, err error)

	//
	// DISCS
	//

	CreateDisc(vm *VirtualMachineName, disc brain.Disc) error
	DeleteDisc(vm *VirtualMachineName, idOrLabel string) error
	GetDisc(vm *VirtualMachineName, idOrLabel string) (*brain.Disc, error)
	ResizeDisc(vm *VirtualMachineName, idOrLabel string, size int) error
	SetDiscIopsLimit(vm *VirtualMachineName, idOrLabel string, iopsLimit int) error

	//
	// GROUPS
	//

	// CreateGroup sends a request to the API server to create a group with the given name.
	CreateGroup(name *GroupName) error
	DeleteGroup(name *GroupName) error
	GetGroup(name *GroupName) (*brain.Group, error)

	//
	// NICS
	//

	AddIP(name *VirtualMachineName, ipcr *brain.IPCreateRequest) (brain.IPs, error)

	//
	// PRIVILEGES
	//
	// username is allowed to be empty
	GetPrivileges(username string) (brain.Privileges, error)
	GetPrivilegesForAccount(account string) (brain.Privileges, error)
	GetPrivilegesForGroup(group GroupName) (brain.Privileges, error)
	GetPrivilegesForVirtualMachine(vm VirtualMachineName) (brain.Privileges, error)
	GrantPrivilege(p brain.Privilege) error
	RevokePrivilege(p brain.Privilege) error

	//
	// BACKUPS
	//

	CreateBackup(server VirtualMachineName, discLabelOrID string) (brain.Backup, error)
	DeleteBackup(server VirtualMachineName, discLabelOrID string, backupLabelOrID string) error
	GetBackups(server VirtualMachineName, discLabelOrID string) (brain.Backups, error)
	RestoreBackup(server VirtualMachineName, discLabelOrID string, backupLabelOrID string) (brain.Backup, error)

	//
	// BACKUP SCHEDULES
	//

	CreateBackupSchedule(server VirtualMachineName, discLabel string, startDate string, intervalSeconds int) (brain.BackupSchedule, error)
	DeleteBackupSchedule(server VirtualMachineName, discLabel string, id int) error

	//
	// USERS
	//

	GetUser(name string) (*brain.User, error)
	AddUserAuthorizedKey(username, key string) error
	DeleteUserAuthorizedKey(username, key string) error

	//
	// VIRTUAL MACHINES
	//

	// CreateVirtualMachine creates a virtual machine with a given specification in the given group.
	// returns nil on success or an error otherwise.
	CreateVirtualMachine(group *GroupName, vm brain.VirtualMachineSpec) (*brain.VirtualMachine, error)

	// DeleteVirtualMachine deletes the named virtual machine.
	// returns nil on success or an error otherwise.
	DeleteVirtualMachine(name *VirtualMachineName, purge bool) error

	// GetVirtualMachine requests an overview of the named VM, regardless of its deletion status.
	GetVirtualMachine(name *VirtualMachineName) (*brain.VirtualMachine, error)

	// MoveVirtualMachine moves a server from one VirtualMachineName to another.
	MoveVirtualMachine(old *VirtualMachineName, new *VirtualMachineName) error

	// ReimageVirtualMachine reimages the named virtual machine. This will wipe everything on the first disk in the vm and install a new OS on top of it.
	// Note that the machine in question must already be powered off. Once complete, according to the API docs, the vm will be powered on but its autoreboot_on will be false.
	ReimageVirtualMachine(name *VirtualMachineName, image *brain.ImageInstall) (err error)

	// ResetVirtualMachine resets the named virtual machine. This is like pressing the reset
	// button on a physical computer. This does not cause a new process to be started, so does not apply any pending hardware changes.
	// returns nil on success or an error otherwise.
	ResetVirtualMachine(name *VirtualMachineName) (err error)

	// RestartVirtualMachine restarts the named virtual machine. This is
	// returns nil on success or an error otherwise.
	RestartVirtualMachine(name *VirtualMachineName) (err error)

	// StartVirtualMachine starts the named virtual machine.
	// returns nil on success or an error otherwise.
	StartVirtualMachine(name *VirtualMachineName) (err error)

	// StopVirtualMachine starts the named virtual machine.
	// returns nil on success or an error otherwise.
	StopVirtualMachine(name *VirtualMachineName) (err error)

	// ShutdownVirtualMachine sends an ACPI shutdown to the VM. This will cause a graceful shutdown of the machine
	// returns nil on success or an error otherwise.
	ShutdownVirtualMachine(name *VirtualMachineName, stayoff bool) (err error)

	// UndeleteVirtualMachine changes the deleted flag on a VM back to false.
	// Return nil on success, an error otherwise.
	UndeleteVirtualMachine(name *VirtualMachineName) error

	// SetVirtualMachineHardwareProfile specifies the hardware profile on a VM. Optionally locks or unlocks h. profile
	// Return nil on success, an error otherwise.
	SetVirtualMachineHardwareProfile(name *VirtualMachineName, profile string, locked ...bool) (err error)

	// SetVirtualMachineHardwareProfileLock locks or unlocks the hardware profile of a VM.
	// Return nil on success, an error otherwise.
	SetVirtualMachineHardwareProfileLock(name *VirtualMachineName, locked bool) (err error)

	// SetVirtualMachineMemory sets the RAM available to a virtual machine in megabytes
	// Return nil on success, an error otherwise.
	SetVirtualMachineMemory(name *VirtualMachineName, memory int) (err error)

	// SetVirtualMachineCores sets the number of CPUs available to a virtual machine
	// Return nil on success, an error otherwise.
	SetVirtualMachineCores(name *VirtualMachineName, cores int) (err error)

	// SetVirtualMachineCDROM sets the URL of a CD to attach to a virtual machine. Set url to "" to remove the CD.
	// Returns nil on success, an error otherwise.
	SetVirtualMachineCDROM(name *VirtualMachineName, url string) (err error)

	//
	// ADMIN
	//

	GetVLANs() ([]*brain.VLAN, error)
	GetVLAN(num int) (*brain.VLAN, error)
	GetIPRanges() ([]*brain.IPRange, error)
	GetIPRange(idOrCIDR string) (*brain.IPRange, error)
	GetHeads() ([]*brain.Head, error)
	GetHead(idOrLabel string) (*brain.Head, error)
	GetTails() ([]*brain.Tail, error)
	GetTail(idOrLabel string) (*brain.Tail, error)
	GetStoragePools() ([]*brain.StoragePool, error)
	GetStoragePool(idOrLabel string) (*brain.StoragePool, error)
	GetMigratingDiscs() ([]brain.Disc, error)
	GetMigratingVMs() ([]*brain.VirtualMachine, error)
	GetStoppedEligibleVMs() ([]*brain.VirtualMachine, error)
	GetRecentVMs() ([]*brain.VirtualMachine, error)
	MigrateDisc(disc int, newStoragePool string) error
	MigrateVirtualMachine(vmName VirtualMachineName, newHead string) error
	ReapVMs() error
	DeleteVLAN(id int) error
	AdminCreateGroup(name GroupName, vlanNum int) error
	CreateIPRange(ipRange string, vlanNum int) error
	CancelDiscMigration(id int) error
	CancelVMMigration(id int) error
	EmptyStoragePool(idOrLabel string) error
	EmptyHead(idOrLabel string) error
	ReifyDisc(id int) error
	ApproveVM(name VirtualMachineName, powerOn bool) error
	RejectVM(name VirtualMachineName, reason string) error
	RegradeDisc(disc int, newGrade string) error
	UpdateVMMigration(name VirtualMachineName, speed *int64, downtime *int) error
	CreateUser(username string, privilege string) error
	UpdateHead(idOrLabel string, options UpdateHead) error
	UpdateTail(idOrLabel string, options UpdateTail) error
	UpdateStoragePool(idOrLabel string, options UpdateStoragePool) error
}
<commit_msg>Add Ensure* functions to Client interface<commit_after>package lib

import (
	auth3 "github.com/BytemarkHosting/auth-client"
	"github.com/BytemarkHosting/bytemark-client/lib/billing"
	"github.com/BytemarkHosting/bytemark-client/lib/brain"
	"github.com/BytemarkHosting/bytemark-client/lib/spp"
)

// Client provides the interface which all API clients should implement.
type Client interface {
	// Getters
	//

	// GetEndpoint returns the API endpoint currently in use.
	GetEndpoint() string

	// GetSessionFactors returns the factors provided when the current auth session was set up
	GetSessionFactors() []string
	// GetSessionToken returns the token for the current auth session
	GetSessionToken() string

	// GetSessionUser returns the user's name for the current auth session.
	GetSessionUser() string

	//
	// Setters
	//

	// SetDebugLevel sets the debug level / verbosity of the API client. 0 (default) is silent.
	SetDebugLevel(int)

	//
	// Ensurers
	//

	EnsureVirtualMachineName(vm *VirtualMachineName) error
	EnsureGroupName(group *GroupName) error
	EnsureAccountName(account *string) error

	//
	// Auth
	//

	// AuthWithToken attempts to read sessiondata from auth for the given token. Returns nil on success or an error otherwise.
	AuthWithToken(string) error
	// AuthWithCredentials attempts to authenticate with the given credentials. Returns nil on success or an error otherwise.
	AuthWithCredentials(auth3.Credentials) error

	//
	// Requests
	//

	AllowInsecureRequests()
	BuildRequestNoAuth(method string, endpoint Endpoint, path string, parts ...string) (*Request, error)
	BuildRequest(method string, endpoint Endpoint, path string, parts ...string) (*Request, error)

	///////////////////////
	////// SPP STUFF //////
	///////////////////////

	CreateCreditCard(*spp.CreditCard) (string, error)
	CreateCreditCardWithToken(*spp.CreditCard, string) (string, error)

	///////////////////////
	//// BILLING STUFF ////
	///////////////////////

	// CreateAccount(*Account) (*Account, error) // TODO(telyn): figure out if CreateAccount is needed/useful
	GetSPPToken(cc spp.CreditCard, owner *billing.Person) (string, error)
	RegisterNewAccount(acc *Account) (*Account, error)

	////////////////////
	//// BRAIN STUFF ///
	////////////////////
	//
	// Parsers
	//

	ParseVirtualMachineName(string, ...*VirtualMachineName) (*VirtualMachineName, error)
	ParseGroupName(string, ...*GroupName) *GroupName
	ParseAccountName(string, ...string) string

	//
	// DEFINITIONS
	//
	ReadDefinitions() (*Definitions, error)

	//
	// ACCOUNTS
	//

	// GetAccount takes an account name or ID and returns a filled-out Account object
	GetAccount(name string) (account *Account, err error)
	// GetDefaultAccount gets the most-likely default account for the user.
	GetDefaultAccount() (account *Account, err error)
	// GetAccounts gets all the accounts the logged-in user can see.
	GetAccounts() (accounts []*Account, err error)

	//
	// DISCS
	//

	CreateDisc(vm *VirtualMachineName, disc brain.Disc) error
	DeleteDisc(vm *VirtualMachineName, idOrLabel string) error
	GetDisc(vm *VirtualMachineName, idOrLabel string) (*brain.Disc, error)
	ResizeDisc(vm *VirtualMachineName, idOrLabel string, size int) error
	SetDiscIopsLimit(vm *VirtualMachineName, idOrLabel string, iopsLimit int) error

	//
	// GROUPS
	//

	// CreateGroup sends a request to the API server to create a group with the given name.
	CreateGroup(name *GroupName) error
	DeleteGroup(name *GroupName) error
	GetGroup(name *GroupName) (*brain.Group, error)

	//
	// NICS
	//

	AddIP(name *VirtualMachineName, ipcr *brain.IPCreateRequest) (brain.IPs, error)

	//
	// PRIVILEGES
	//
	// username is allowed to be empty
	GetPrivileges(username string) (brain.Privileges, error)
	GetPrivilegesForAccount(account string) (brain.Privileges, error)
	GetPrivilegesForGroup(group GroupName) (brain.Privileges, error)
	GetPrivilegesForVirtualMachine(vm VirtualMachineName) (brain.Privileges, error)
	GrantPrivilege(p brain.Privilege) error
	RevokePrivilege(p brain.Privilege) error

	//
	// BACKUPS
	//

	CreateBackup(server VirtualMachineName, discLabelOrID string) (brain.Backup, error)
	DeleteBackup(server VirtualMachineName, discLabelOrID string, backupLabelOrID string) error
	GetBackups(server VirtualMachineName, discLabelOrID string) (brain.Backups, error)
	RestoreBackup(server VirtualMachineName, discLabelOrID string, backupLabelOrID string) (brain.Backup, error)

	//
	// BACKUP SCHEDULES
	//

	CreateBackupSchedule(server VirtualMachineName, discLabel string, startDate string, intervalSeconds int) (brain.BackupSchedule, error)
	DeleteBackupSchedule(server VirtualMachineName, discLabel string, id int) error

	//
	// USERS
	//

	GetUser(name string) (*brain.User, error)
	AddUserAuthorizedKey(username, key string) error
	DeleteUserAuthorizedKey(username, key string) error

	//
	// VIRTUAL MACHINES
	//

	// CreateVirtualMachine creates a virtual machine with a given specification in the given group.
	// returns nil on success or an error otherwise.
	CreateVirtualMachine(group *GroupName, vm brain.VirtualMachineSpec) (*brain.VirtualMachine, error)

	// DeleteVirtualMachine deletes the named virtual machine.
	// returns nil on success or an error otherwise.
	DeleteVirtualMachine(name *VirtualMachineName, purge bool) error

	// GetVirtualMachine requests an overview of the named VM, regardless of its deletion status.
	GetVirtualMachine(name *VirtualMachineName) (*brain.VirtualMachine, error)

	// MoveVirtualMachine moves a server from one VirtualMachineName to another.
	MoveVirtualMachine(old *VirtualMachineName, new *VirtualMachineName) error

	// ReimageVirtualMachine reimages the named virtual machine. This will wipe everything on the first disk in the vm and install a new OS on top of it.
	// Note that the machine in question must already be powered off. Once complete, according to the API docs, the vm will be powered on but its autoreboot_on will be false.
	ReimageVirtualMachine(name *VirtualMachineName, image *brain.ImageInstall) (err error)

	// ResetVirtualMachine resets the named virtual machine. This is like pressing the reset
	// button on a physical computer. This does not cause a new process to be started, so does not apply any pending hardware changes.
	// returns nil on success or an error otherwise.
	ResetVirtualMachine(name *VirtualMachineName) (err error)

	// RestartVirtualMachine restarts the named virtual machine. This is
	// returns nil on success or an error otherwise.
	RestartVirtualMachine(name *VirtualMachineName) (err error)

	// StartVirtualMachine starts the named virtual machine.
	// returns nil on success or an error otherwise.
	StartVirtualMachine(name *VirtualMachineName) (err error)

	// StopVirtualMachine starts the named virtual machine.
	// returns nil on success or an error otherwise.
	StopVirtualMachine(name *VirtualMachineName) (err error)

	// ShutdownVirtualMachine sends an ACPI shutdown to the VM. This will cause a graceful shutdown of the machine
	// returns nil on success or an error otherwise.
	ShutdownVirtualMachine(name *VirtualMachineName, stayoff bool) (err error)

	// UndeleteVirtualMachine changes the deleted flag on a VM back to false.
	// Return nil on success, an error otherwise.
	UndeleteVirtualMachine(name *VirtualMachineName) error

	// SetVirtualMachineHardwareProfile specifies the hardware profile on a VM. Optionally locks or unlocks h. profile
	// Return nil on success, an error otherwise.
	SetVirtualMachineHardwareProfile(name *VirtualMachineName, profile string, locked ...bool) (err error)

	// SetVirtualMachineHardwareProfileLock locks or unlocks the hardware profile of a VM.
	// Return nil on success, an error otherwise.
	SetVirtualMachineHardwareProfileLock(name *VirtualMachineName, locked bool) (err error)

	// SetVirtualMachineMemory sets the RAM available to a virtual machine in megabytes
	// Return nil on success, an error otherwise.
	SetVirtualMachineMemory(name *VirtualMachineName, memory int) (err error)

	// SetVirtualMachineCores sets the number of CPUs available to a virtual machine
	// Return nil on success, an error otherwise.
	SetVirtualMachineCores(name *VirtualMachineName, cores int) (err error)

	// SetVirtualMachineCDROM sets the URL of a CD to attach to a virtual machine. Set url to "" to remove the CD.
	// Returns nil on success, an error otherwise.
	SetVirtualMachineCDROM(name *VirtualMachineName, url string) (err error)

	//
	// ADMIN
	//

	GetVLANs() ([]*brain.VLAN, error)
	GetVLAN(num int) (*brain.VLAN, error)
	GetIPRanges() ([]*brain.IPRange, error)
	GetIPRange(idOrCIDR string) (*brain.IPRange, error)
	GetHeads() ([]*brain.Head, error)
	GetHead(idOrLabel string) (*brain.Head, error)
	GetTails() ([]*brain.Tail, error)
	GetTail(idOrLabel string) (*brain.Tail, error)
	GetStoragePools() ([]*brain.StoragePool, error)
	GetStoragePool(idOrLabel string) (*brain.StoragePool, error)
	GetMigratingDiscs() ([]brain.Disc, error)
	GetMigratingVMs() ([]*brain.VirtualMachine, error)
	GetStoppedEligibleVMs() ([]*brain.VirtualMachine, error)
	GetRecentVMs() ([]*brain.VirtualMachine, error)
	MigrateDisc(disc int, newStoragePool string) error
	MigrateVirtualMachine(vmName VirtualMachineName, newHead string) error
	ReapVMs() error
	DeleteVLAN(id int) error
	AdminCreateGroup(name GroupName, vlanNum int) error
	CreateIPRange(ipRange string, vlanNum int) error
	CancelDiscMigration(id int) error
	CancelVMMigration(id int) error
	EmptyStoragePool(idOrLabel string) error
	EmptyHead(idOrLabel string) error
	ReifyDisc(id int) error
	ApproveVM(name VirtualMachineName, powerOn bool) error
	RejectVM(name VirtualMachineName, reason string) error
	RegradeDisc(disc int, newGrade string) error
	UpdateVMMigration(name VirtualMachineName, speed *int64, downtime *int) error
	CreateUser(username string, privilege string) error
	UpdateHead(idOrLabel string, options UpdateHead) error
	UpdateTail(idOrLabel string, options UpdateTail) error
	UpdateStoragePool(idOrLabel string, options UpdateStoragePool) error
}
<|endoftext|>