<commit_before>package gerrittest

import (
	"context"
	"errors"
	"fmt"
	"net"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/crewjam/errset"
	"github.com/opalmer/dockertest"
)

var (
	// DefaultImage defines the default docker image to use in
	// NewConfig(). This may be overridden with the $GERRITTEST_DOCKER_IMAGE
	// environment variable.
	DefaultImage = "opalmer/gerrittest:2.14.3"
)

const (
	// DefaultImageEnvironmentVar defines the environment variable NewConfig()
	// and the tests should be using to locate the default image override.
	DefaultImageEnvironmentVar = "GERRITTEST_DOCKER_IMAGE"

	// ExportedHTTPPort is the port exported by the docker container
	// where the HTTP service is running.
	ExportedHTTPPort = 8080

	// ExportedSSHPort is the port exported by the docker container
	// where the SSHPort service is running.
	ExportedSSHPort = 29418
)

func newPort(public uint16, private uint16) (*dockertest.Port, error) {
	if private != ExportedSSHPort && private != ExportedHTTPPort {
		return nil, errors.New("Unknown private port")
	}

	// If a random port has been chosen then we need to try and determine
	// one instead of letting Docker choose. If we don't, we'll be unable
	// to properly set $GERRIT_CANONICAL_URL which Gerrit uses for redirects.
	if private == ExportedHTTPPort && public == dockertest.RandomPort {
		listener, err := net.Listen("tcp", "127.0.0.1:0")
		if err != nil {
			return nil, err
		}
		defer listener.Close()
		port, err := strconv.ParseUint(
			strings.Split(listener.Addr().String(), ":")[1], 10, 16)
		if err != nil {
			return nil, err
		}
		public = uint16(port)
	}

	return &dockertest.Port{
		Private:  private,
		Public:   public,
		Protocol: dockertest.ProtocolTCP,
	}, nil
}

func waitPort(ctx context.Context, port *dockertest.Port, errs chan error) {
	addr := fmt.Sprintf("%s:%d", port.Address, port.Public)
	logger := log.WithFields(log.Fields{
		"cmp":   "container",
		"phase": "port-wait",
		"addr":  addr,
	})
	logger.WithField("task", "begin").Debug()
	started := time.Now()
	ticker := time.NewTicker(time.Millisecond * 200)
	defer ticker.Stop()
	for {
		select {
		case <-ctx.Done():
			errs <- ctx.Err()
			return
		case <-ticker.C:
			conn, err := net.Dial("tcp", addr)
			if err != nil {
				continue
			}
			logger.WithFields(log.Fields{
				"task":    "done",
				"elapsed": time.Since(started),
			})
			errs <- conn.Close()
			return
		}
	}
}

func waitHTTP(ctx context.Context, port *dockertest.Port, errs chan error) {
	url := fmt.Sprintf("http://%s:%d/", port.Address, port.Public)
	logger := log.WithFields(log.Fields{
		"cmp":   "container",
		"phase": "wait-port",
		"url":   url,
	})
	logger.WithField("task", "begin").Debug()
	started := time.Now()
	ticker := time.NewTicker(time.Millisecond * 200)
	defer ticker.Stop()
	for {
		select {
		case <-ctx.Done():
			errs <- ctx.Err()
			return
		case <-ticker.C:
			response, err := http.Get(url)
			if err != nil {
				continue
			}
			if response.StatusCode != http.StatusOK {
				continue
			}
			logger.WithFields(log.Fields{
				"task":    "end",
				"elapsed": time.Since(started),
			})
			errs <- nil
			return
		}
	}
}

func getDockerClientInput(http uint16, ssh uint16, image string) (*dockertest.ClientInput, error) {
	httpPort, err := newPort(http, ExportedHTTPPort)
	if err != nil {
		return nil, err
	}
	sshPort, err := newPort(ssh, ExportedSSHPort)
	if err != nil {
		return nil, err
	}

	image = GetDockerImage(image)
	input := dockertest.NewClientInput(image)
	input.Ports.Add(httpPort)
	input.Ports.Add(sshPort)
	input.AddEnvironmentVar(
		"GERRIT_CANONICAL_URL",
		fmt.Sprintf("http://127.0.0.1:%d/", httpPort.Public))
	return input, nil
}

// GetDockerImage returns the docker image to use to run the container. If "" is
// provided as the docker then we'll check $GERRITTEST_DOCKER_IMAGE first before
// returning the value defined in DefaultImage.
func GetDockerImage(image string) string {
	if image != "" {
		return image
	}
	if value, set := os.LookupEnv(DefaultImageEnvironmentVar); set {
		return value
	}
	return DefaultImage
}

// Container stores information about a Gerrit instance running inside of
// a container.
type Container struct {
	ctx    context.Context
	Docker *dockertest.DockerClient `json:"-"`
	HTTP   *dockertest.Port         `json:"http"`
	SSH    *dockertest.Port         `json:"ssh"`
	Image  string                   `json:"image"`
	ID     string                   `json:"id"`
}

// Terminate will terminate and remove the running container.
func (c *Container) Terminate() error {
	return c.Docker.RemoveContainer(c.ctx, c.ID)
}

// NewContainer will create a new container using dockertest and return
// it. If you prefer to use an existing container use one of the LoadContainer*
// functions instead. This function will not return until the container has
// started and is listening on the requested ports.
func NewContainer(parent context.Context, http uint16, ssh uint16, image string) (*Container, error) {
	image = GetDockerImage(image)
	logger := log.WithFields(log.Fields{
		"cmp": "container",
	})
	logger.WithField("image", image).Debug()

	input, err := getDockerClientInput(http, ssh, image)
	if err != nil {
		return nil, err
	}

	client, err := dockertest.NewClient()
	if err != nil {
		return nil, err
	}

	startLog := logger.WithField("phase", "service")
	service := &dockertest.Service{
		Input:  input,
		Client: client,
		Name:   "gerrittest",
		Ping: func(input *dockertest.PingInput) error {
			pingStart := time.Now()
			entry := startLog.WithField("status", "ping")
			entry.WithField("task", "begin").Debug()

			containerSSH, err := input.Container.Port(ExportedSSHPort)
			if err != nil {
				entry.WithError(err).Error()
				return err
			}

			containerHTTP, err := input.Container.Port(ExportedHTTPPort)
			if err != nil {
				entry.WithError(err).Error()
				return err
			}

			// Wait for ports to open
			errs := make(chan error, 2)
			go waitPort(parent, containerSSH, errs)
			go waitHTTP(parent, containerHTTP, errs)
			for i := 0; i < 2; i++ {
				if err := <-errs; err != nil {
					return err
				}
			}

			entry.WithFields(log.Fields{
				"task":    "end",
				"elapsed": time.Since(pingStart),
			}).Debug()
			return nil
		},
	}

	// Call run which will start the container and wait for Ping() in
	// the service above to return.
	start := time.Now()
	startLog.WithField("task", "begin").Debug()
	if err := service.Run(); err != nil {
		errs := errset.ErrSet{}
		errs = append(errs, err)
		errs = append(errs, service.Terminate())
		return nil, errs.ReturnValue()
	}
	startLog.WithFields(log.Fields{
		"task":    "end",
		"elapsed": time.Since(start),
	}).Debug()

	portSSH, err := service.Container.Port(ExportedSSHPort)
	if err != nil {
		return nil, err
	}

	portHTTP, err := service.Container.Port(ExportedHTTPPort)
	if err != nil {
		return nil, err
	}

	return &Container{
		ctx:    parent,
		Docker: client,
		SSH:    portSSH,
		HTTP:   portHTTP,
		Image:  image,
		ID: service.Container.ID(),
	}, nil
}
<commit_msg>ignore err from listener.Close()<commit_after>package gerrittest

import (
	"context"
	"errors"
	"fmt"
	"net"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/crewjam/errset"
	"github.com/opalmer/dockertest"
)

var (
	// DefaultImage defines the default docker image to use in
	// NewConfig(). This may be overridden with the $GERRITTEST_DOCKER_IMAGE
	// environment variable.
	DefaultImage = "opalmer/gerrittest:2.14.3"
)

const (
	// DefaultImageEnvironmentVar defines the environment variable NewConfig()
	// and the tests should be using to locate the default image override.
	DefaultImageEnvironmentVar = "GERRITTEST_DOCKER_IMAGE"

	// ExportedHTTPPort is the port exported by the docker container
	// where the HTTP service is running.
	ExportedHTTPPort = 8080

	// ExportedSSHPort is the port exported by the docker container
	// where the SSHPort service is running.
	ExportedSSHPort = 29418
)

func newPort(public uint16, private uint16) (*dockertest.Port, error) {
	if private != ExportedSSHPort && private != ExportedHTTPPort {
		return nil, errors.New("Unknown private port")
	}

	// If a random port has been chosen then we need to try and determine
	// one instead of letting Docker choose. If we don't, we'll be unable
	// to properly set $GERRIT_CANONICAL_URL which Gerrit uses for redirects.
	if private == ExportedHTTPPort && public == dockertest.RandomPort {
		listener, err := net.Listen("tcp", "127.0.0.1:0")
		if err != nil {
			return nil, err
		}
		defer listener.Close() // nolint: errcheck
		port, err := strconv.ParseUint(
			strings.Split(listener.Addr().String(), ":")[1], 10, 16)
		if err != nil {
			return nil, err
		}
		public = uint16(port)
	}

	return &dockertest.Port{
		Private:  private,
		Public:   public,
		Protocol: dockertest.ProtocolTCP,
	}, nil
}

func waitPort(ctx context.Context, port *dockertest.Port, errs chan error) {
	addr := fmt.Sprintf("%s:%d", port.Address, port.Public)
	logger := log.WithFields(log.Fields{
		"cmp":   "container",
		"phase": "port-wait",
		"addr":  addr,
	})
	logger.WithField("task", "begin").Debug()
	started := time.Now()
	ticker := time.NewTicker(time.Millisecond * 200)
	defer ticker.Stop()
	for {
		select {
		case <-ctx.Done():
			errs <- ctx.Err()
			return
		case <-ticker.C:
			conn, err := net.Dial("tcp", addr)
			if err != nil {
				continue
			}
			logger.WithFields(log.Fields{
				"task":    "done",
				"elapsed": time.Since(started),
			})
			errs <- conn.Close()
			return
		}
	}
}

func waitHTTP(ctx context.Context, port *dockertest.Port, errs chan error) {
	url := fmt.Sprintf("http://%s:%d/", port.Address, port.Public)
	logger := log.WithFields(log.Fields{
		"cmp":   "container",
		"phase": "wait-port",
		"url":   url,
	})
	logger.WithField("task", "begin").Debug()
	started := time.Now()
	ticker := time.NewTicker(time.Millisecond * 200)
	defer ticker.Stop()
	for {
		select {
		case <-ctx.Done():
			errs <- ctx.Err()
			return
		case <-ticker.C:
			response, err := http.Get(url)
			if err != nil {
				continue
			}
			if response.StatusCode != http.StatusOK {
				continue
			}
			logger.WithFields(log.Fields{
				"task":    "end",
				"elapsed": time.Since(started),
			})
			errs <- nil
			return
		}
	}
}

func getDockerClientInput(http uint16, ssh uint16, image string) (*dockertest.ClientInput, error) {
	httpPort, err := newPort(http, ExportedHTTPPort)
	if err != nil {
		return nil, err
	}
	sshPort, err := newPort(ssh, ExportedSSHPort)
	if err != nil {
		return nil, err
	}

	image = GetDockerImage(image)
	input := dockertest.NewClientInput(image)
	input.Ports.Add(httpPort)
	input.Ports.Add(sshPort)
	input.AddEnvironmentVar(
		"GERRIT_CANONICAL_URL",
		fmt.Sprintf("http://127.0.0.1:%d/", httpPort.Public))
	return input, nil
}

// GetDockerImage returns the docker image to use to run the container. If "" is
// provided as the docker then we'll check $GERRITTEST_DOCKER_IMAGE first before
// returning the value defined in DefaultImage.
func GetDockerImage(image string) string {
	if image != "" {
		return image
	}
	if value, set := os.LookupEnv(DefaultImageEnvironmentVar); set {
		return value
	}
	return DefaultImage
}

// Container stores information about a Gerrit instance running inside of
// a container.
type Container struct {
	ctx    context.Context
	Docker *dockertest.DockerClient `json:"-"`
	HTTP   *dockertest.Port         `json:"http"`
	SSH    *dockertest.Port         `json:"ssh"`
	Image  string                   `json:"image"`
	ID     string                   `json:"id"`
}

// Terminate will terminate and remove the running container.
func (c *Container) Terminate() error {
	return c.Docker.RemoveContainer(c.ctx, c.ID)
}

// NewContainer will create a new container using dockertest and return
// it. If you prefer to use an existing container use one of the LoadContainer*
// functions instead. This function will not return until the container has
// started and is listening on the requested ports.
func NewContainer(parent context.Context, http uint16, ssh uint16, image string) (*Container, error) {
	image = GetDockerImage(image)
	logger := log.WithFields(log.Fields{
		"cmp": "container",
	})
	logger.WithField("image", image).Debug()

	input, err := getDockerClientInput(http, ssh, image)
	if err != nil {
		return nil, err
	}

	client, err := dockertest.NewClient()
	if err != nil {
		return nil, err
	}

	startLog := logger.WithField("phase", "service")
	service := &dockertest.Service{
		Input:  input,
		Client: client,
		Name:   "gerrittest",
		Ping: func(input *dockertest.PingInput) error {
			pingStart := time.Now()
			entry := startLog.WithField("status", "ping")
			entry.WithField("task", "begin").Debug()

			containerSSH, err := input.Container.Port(ExportedSSHPort)
			if err != nil {
				entry.WithError(err).Error()
				return err
			}

			containerHTTP, err := input.Container.Port(ExportedHTTPPort)
			if err != nil {
				entry.WithError(err).Error()
				return err
			}

			// Wait for ports to open
			errs := make(chan error, 2)
			go waitPort(parent, containerSSH, errs)
			go waitHTTP(parent, containerHTTP, errs)
			for i := 0; i < 2; i++ {
				if err := <-errs; err != nil {
					return err
				}
			}

			entry.WithFields(log.Fields{
				"task":    "end",
				"elapsed": time.Since(pingStart),
			}).Debug()
			return nil
		},
	}

	// Call run which will start the container and wait for Ping() in
	// the service above to return.
	start := time.Now()
	startLog.WithField("task", "begin").Debug()
	if err := service.Run(); err != nil {
		errs := errset.ErrSet{}
		errs = append(errs, err)
		errs = append(errs, service.Terminate())
		return nil, errs.ReturnValue()
	}
	startLog.WithFields(log.Fields{
		"task":    "end",
		"elapsed": time.Since(start),
	}).Debug()

	portSSH, err := service.Container.Port(ExportedSSHPort)
	if err != nil {
		return nil, err
	}

	portHTTP, err := service.Container.Port(ExportedHTTPPort)
	if err != nil {
		return nil, err
	}

	return &Container{
		ctx:    parent,
		Docker: client,
		SSH:    portSSH,
		HTTP:   portHTTP,
		Image:  image,
		ID:     service.Container.ID(),
	}, nil
}
<|endoftext|>


<commit_before>package container

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"reflect"
	"strings"
)

var (
	ErrServiceAlreadySet   = errors.New("Already Set")
)

// Container is an abstration that represents a 'dependency injection container'.
//
// Use the New func to get a new (dependenc injection) container.
type Container interface {
	Register(string, interface{}) error

	Get(string) (interface{}, error)

	Inject(interface{}) error
}

type internalContainerDependencies struct {
	Logger *log.Logger `inject:"logger"`
}

type internalContainer struct {
	registry map[string]interface{}
	dependencies internalContainerDependencies
}

func (container *internalContainer) Dependencies() interface{} {
	return &container.dependencies
}

// New returns a new 'dependency injection container'.
func New() Container {
	logger := log.New(ioutil.Discard, "dependency injection container> ", log.Lshortfile)

	registry  := make(map[string]interface{})

	container := internalContainer{
		registry:registry,
		dependencies:internalContainerDependencies{
			Logger:logger,
		},
	}

	return &container
}

func (container *internalContainer) Register(dependencyName string, dependency interface{}) error {

	logger := container.dependencies.Logger

	logger.Printf("[BEGIN] Register(%q, <dependency> %T)", dependencyName, dependency)

	if _,ok := container.registry[dependencyName]; ok {
		err := ErrServiceAlreadySet

		logger.Printf("[END]   Register(%q, <dependency> %T) with ERROR: %q", dependencyName, dependency, err)
		return err
	}

	container.registry[dependencyName] = dependency

	logger.Printf("[END]   Register(%q, <dependency> %T)", dependencyName, dependency)

	return nil
}

func (container *internalContainer) Get(dependencyName string) (interface{}, error) {

	logger := container.dependencies.Logger

	logger.Printf("[BEGIN] Get(%q)", dependencyName)

	dependency,ok := container.registry[dependencyName]
	if !ok {
		err := newDependenciesNotFoundComplainer(dependencyName)

		logger.Printf("[END]   Get(%q) with ERROR: %q", dependencyName, err)
		return nil, err
	}

	logger.Printf("[END]   Get(%q)", dependencyName)

	return dependency, nil
}

func (container *internalContainer) Inject(thing interface{}) (errr error) {

	logger := container.dependencies.Logger

	logger.Printf("[BEGIN] Inject(??? %T)", thing)

	// Initialize.
	dependenciesNotFoundComplainer := newDependenciesNotFoundComplainer()
	internalDependenciesNotFoundComplainer, _ := dependenciesNotFoundComplainer.(*internalDependenciesNotFoundComplainer)

	// If the 'thing' passed to this Inject method fits a Depender (interface)
	// (and thus has a Dependencies method) then we "inject" what is returned
	// by the Dependencies method in addition to the 'thing' passed as an argument
	// to this method.
	//
	// The point of this is so that a struct can hide where it stores its
	// dependencies.
	if depender,ok := thing.(Depender); ok {
		if otherThing := depender.Dependencies(); nil != otherThing {
			if err := container.inject(otherThing); nil != err {
				switch complainer := err.(type) {
				case DependenciesNotFoundComplainer:
					logger.Printf("[INSIDE] Inject(??? %T) Intermediate error: %q", thing, complainer)
					internalDependenciesNotFoundComplainer.concatenate(complainer)
					logger.Printf("[INSIDE] Inject(??? %T) Accumulative error: %q", thing, dependenciesNotFoundComplainer)
				default:
					logger.Printf("[END]   Inject(??? %T) with ERROR: %q", thing, err)
					return err
				}
			}
		}
	}

	// Inject the actual thing.
	if err := container.inject(thing); nil != err {
		switch complainer := err.(type) {
		case DependenciesNotFoundComplainer:
			logger.Printf("[INSIDE] Inject(??? %T) Intermediate error: %q", thing, complainer)
			internalDependenciesNotFoundComplainer.concatenate(complainer)
			logger.Printf("[INSIDE] Inject(??? %T) Accumulative error: %q", thing, dependenciesNotFoundComplainer)
		default:
			logger.Printf("[END]   Inject(??? %T) with ERROR: %q", thing, err)
			return err
		}
	}

	// If we had any missing dependencies, then return an error.
	if 0 < internalDependenciesNotFoundComplainer.len() {
		err := dependenciesNotFoundComplainer

		logger.Printf("[END]   Inject(??? %T) with ERROR: %q", thing, err)
		return err
	}

	logger.Printf("[END]   Inject(??? %T)", thing)

	// Return (no errors).
	return nil
}

func (container *internalContainer) inject(thing interface{}) error {

	value := reflect.ValueOf(thing)

	switch value.Kind() {
		case reflect.Array:
			return container.injectArrayOrSlice(thing)

		case reflect.Map:
			return container.injectMap(thing)

		case reflect.Slice:
			return container.injectArrayOrSlice(thing)

		case reflect.Ptr:
			return container.injectPtr(thing)

		default:
			// Nothing here.
	}

	// Return (no errors).
	return nil
}

func (container *internalContainer) injectPtr(thing interface{}) error {

	// Initialize.
	dependenciesNotFoundComplainer := newDependenciesNotFoundComplainer().(*internalDependenciesNotFoundComplainer)

	// Reflection!
	value := reflect.ValueOf(thing)
	x := value.Elem()
	typeOfX := x.Type()

	// Go through each field of the struct, and if it has a dependency-tag
	// indicating that a dependency should be injected, then try to do so.
	numFields := x.NumField()
	for i:=0; i<numFields; i++ {
		field := typeOfX.Field(i)

		fieldTag  := field.Tag

		dependencyName := fieldTag.Get("inject")

		// See if the dependency is registered. If it is, then
		// inject it. Else, make a note of that error.
		//
		// Note that reflection will alwys return a value for the
		// 'struct tag' we ask for. Regardless of whether the
		// programmer added our specific 'struct tag' or not.
		//
		// Even if the programmer doesn't include the specific
		// 'struct tag' we are interested in, reflection will
		// still return a value of "" (i.e., empty string) when
		// we ask it for it.
		//
		// Note that this can be potentially confusing because
		// the programmer could include the specific struct
		// tag we are interested in, but give it "" (i.e., the
		// empty string) as a value, and we couldn't tell the
		// difference between that and when the programmer does
		// not include the 'struct tag'.
		//
		// So, because of this, in the else-clause when we are
		// checking for errors, we ignore the case where the
		// 'dependency name' is "" (i.e., the empty string),
		// and do not consider it an error.
		if dependency,ok := container.registry[dependencyName]; ok {
			err := func(value reflect.Value, dependencyName string) (err error) {

				defer func() {

					if r := recover(); nil != r {
						// See if we received a message of the form:
						//
						//	reflect.Set: value of type ??? is not assignable to type ???
						//
						// If we did then we interpret this as the programmer using the
						// dependency container trying to inject a dependency into a struct
						// field of the wrong type.
						//
						// We return a special error for that.
						if s, ok := r.(string); ok {
							needle := "reflect.Set: value of type "

							if strings.HasPrefix(s, needle) {
								needle = " is not assignable to type "

								if strings.Contains(s, needle) {
									err = newWrongTypeComplainer(dependencyName)
									return
								}
							}
						}

						msg := fmt.Sprintf("%T %v", r)

						err = errors.New( msg )
						return
					}

				}()

				value.Set( reflect.ValueOf(dependency) )

				return nil
			}(x.Field(i), dependencyName)

			if nil != err {
				return err
			}
		} else if "" != dependencyName {
			dependenciesNotFoundComplainer.insert(dependencyName)
		}
	}

	// If we had any missing dependencies, then return an error.
	if 0 < dependenciesNotFoundComplainer.len() {
		return dependenciesNotFoundComplainer
	}

	// Return (no errors).
	return nil
}

func (container *internalContainer) injectArrayOrSlice(thing interface{}) error {

	// Initialize.
	dependenciesNotFoundComplainer := newDependenciesNotFoundComplainer().(*internalDependenciesNotFoundComplainer)

	// Reflection!
	value := reflect.ValueOf(thing)

	// Go through each item in the array or slice, and inject each of them.
	length := value.Len()
	for i:=0; i<length; i++ {
		element := value.Index(i)

		if err := container.Inject(element.Interface()); nil != err {
			switch complainer := err.(type) {
			case DependenciesNotFoundComplainer:
				dependenciesNotFoundComplainer.concatenate(complainer)
			default:
				return err
			}
		}
	}

	// If we had any missing dependencies, then return an error.
	if 0 < dependenciesNotFoundComplainer.len() {
		return dependenciesNotFoundComplainer
	}

	// Return (no errors).
	return nil
}

func (container *internalContainer) injectMap(thing interface{}) error {

	// Initialize.
	dependenciesNotFoundComplainer := newDependenciesNotFoundComplainer().(*internalDependenciesNotFoundComplainer)

	// Reflection!
	value := reflect.ValueOf(thing)

	// Go through each item in the map, and inject each of them.
	keys := value.MapKeys()
	for _,key := range keys {
		element := value.MapIndex(key)

		if err := container.Inject(element.Interface()); nil != err {
			switch complainer := err.(type) {
			case DependenciesNotFoundComplainer:
				dependenciesNotFoundComplainer.concatenate(complainer)
			default:
				return err
			}
		}
	}

	// If we had any missing dependencies, then return an error.
	if 0 < dependenciesNotFoundComplainer.len() {
		return dependenciesNotFoundComplainer
	}

	// Return (no errors).
	return nil
}
<commit_msg>renamed ErrServiceAlreadySet to ErrAlreadyRegistered<commit_after>package container

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"reflect"
	"strings"
)

var (
	ErrAlreadyRegistered = errors.New("Already Set")
)

// Container is an abstration that represents a 'dependency injection container'.
//
// Use the New func to get a new (dependenc injection) container.
type Container interface {
	Register(string, interface{}) error

	Get(string) (interface{}, error)

	Inject(interface{}) error
}

type internalContainerDependencies struct {
	Logger *log.Logger `inject:"logger"`
}

type internalContainer struct {
	registry map[string]interface{}
	dependencies internalContainerDependencies
}

func (container *internalContainer) Dependencies() interface{} {
	return &container.dependencies
}

// New returns a new 'dependency injection container'.
func New() Container {
	logger := log.New(ioutil.Discard, "dependency injection container> ", log.Lshortfile)

	registry  := make(map[string]interface{})

	container := internalContainer{
		registry:registry,
		dependencies:internalContainerDependencies{
			Logger:logger,
		},
	}

	return &container
}

func (container *internalContainer) Register(dependencyName string, dependency interface{}) error {

	logger := container.dependencies.Logger

	logger.Printf("[BEGIN] Register(%q, <dependency> %T)", dependencyName, dependency)

	if _,ok := container.registry[dependencyName]; ok {
		err := ErrAlreadyRegistered

		logger.Printf("[END]   Register(%q, <dependency> %T) with ERROR: %q", dependencyName, dependency, err)
		return err
	}

	container.registry[dependencyName] = dependency

	logger.Printf("[END]   Register(%q, <dependency> %T)", dependencyName, dependency)

	return nil
}

func (container *internalContainer) Get(dependencyName string) (interface{}, error) {

	logger := container.dependencies.Logger

	logger.Printf("[BEGIN] Get(%q)", dependencyName)

	dependency,ok := container.registry[dependencyName]
	if !ok {
		err := newDependenciesNotFoundComplainer(dependencyName)

		logger.Printf("[END]   Get(%q) with ERROR: %q", dependencyName, err)
		return nil, err
	}

	logger.Printf("[END]   Get(%q)", dependencyName)

	return dependency, nil
}

func (container *internalContainer) Inject(thing interface{}) (errr error) {

	logger := container.dependencies.Logger

	logger.Printf("[BEGIN] Inject(??? %T)", thing)

	// Initialize.
	dependenciesNotFoundComplainer := newDependenciesNotFoundComplainer()
	internalDependenciesNotFoundComplainer, _ := dependenciesNotFoundComplainer.(*internalDependenciesNotFoundComplainer)

	// If the 'thing' passed to this Inject method fits a Depender (interface)
	// (and thus has a Dependencies method) then we "inject" what is returned
	// by the Dependencies method in addition to the 'thing' passed as an argument
	// to this method.
	//
	// The point of this is so that a struct can hide where it stores its
	// dependencies.
	if depender,ok := thing.(Depender); ok {
		if otherThing := depender.Dependencies(); nil != otherThing {
			if err := container.inject(otherThing); nil != err {
				switch complainer := err.(type) {
				case DependenciesNotFoundComplainer:
					logger.Printf("[INSIDE] Inject(??? %T) Intermediate error: %q", thing, complainer)
					internalDependenciesNotFoundComplainer.concatenate(complainer)
					logger.Printf("[INSIDE] Inject(??? %T) Accumulative error: %q", thing, dependenciesNotFoundComplainer)
				default:
					logger.Printf("[END]   Inject(??? %T) with ERROR: %q", thing, err)
					return err
				}
			}
		}
	}

	// Inject the actual thing.
	if err := container.inject(thing); nil != err {
		switch complainer := err.(type) {
		case DependenciesNotFoundComplainer:
			logger.Printf("[INSIDE] Inject(??? %T) Intermediate error: %q", thing, complainer)
			internalDependenciesNotFoundComplainer.concatenate(complainer)
			logger.Printf("[INSIDE] Inject(??? %T) Accumulative error: %q", thing, dependenciesNotFoundComplainer)
		default:
			logger.Printf("[END]   Inject(??? %T) with ERROR: %q", thing, err)
			return err
		}
	}

	// If we had any missing dependencies, then return an error.
	if 0 < internalDependenciesNotFoundComplainer.len() {
		err := dependenciesNotFoundComplainer

		logger.Printf("[END]   Inject(??? %T) with ERROR: %q", thing, err)
		return err
	}

	logger.Printf("[END]   Inject(??? %T)", thing)

	// Return (no errors).
	return nil
}

func (container *internalContainer) inject(thing interface{}) error {

	value := reflect.ValueOf(thing)

	switch value.Kind() {
		case reflect.Array:
			return container.injectArrayOrSlice(thing)

		case reflect.Map:
			return container.injectMap(thing)

		case reflect.Slice:
			return container.injectArrayOrSlice(thing)

		case reflect.Ptr:
			return container.injectPtr(thing)

		default:
			// Nothing here.
	}

	// Return (no errors).
	return nil
}

func (container *internalContainer) injectPtr(thing interface{}) error {

	// Initialize.
	dependenciesNotFoundComplainer := newDependenciesNotFoundComplainer().(*internalDependenciesNotFoundComplainer)

	// Reflection!
	value := reflect.ValueOf(thing)
	x := value.Elem()
	typeOfX := x.Type()

	// Go through each field of the struct, and if it has a dependency-tag
	// indicating that a dependency should be injected, then try to do so.
	numFields := x.NumField()
	for i:=0; i<numFields; i++ {
		field := typeOfX.Field(i)

		fieldTag  := field.Tag

		dependencyName := fieldTag.Get("inject")

		// See if the dependency is registered. If it is, then
		// inject it. Else, make a note of that error.
		//
		// Note that reflection will alwys return a value for the
		// 'struct tag' we ask for. Regardless of whether the
		// programmer added our specific 'struct tag' or not.
		//
		// Even if the programmer doesn't include the specific
		// 'struct tag' we are interested in, reflection will
		// still return a value of "" (i.e., empty string) when
		// we ask it for it.
		//
		// Note that this can be potentially confusing because
		// the programmer could include the specific struct
		// tag we are interested in, but give it "" (i.e., the
		// empty string) as a value, and we couldn't tell the
		// difference between that and when the programmer does
		// not include the 'struct tag'.
		//
		// So, because of this, in the else-clause when we are
		// checking for errors, we ignore the case where the
		// 'dependency name' is "" (i.e., the empty string),
		// and do not consider it an error.
		if dependency,ok := container.registry[dependencyName]; ok {
			err := func(value reflect.Value, dependencyName string) (err error) {

				defer func() {

					if r := recover(); nil != r {
						// See if we received a message of the form:
						//
						//	reflect.Set: value of type ??? is not assignable to type ???
						//
						// If we did then we interpret this as the programmer using the
						// dependency container trying to inject a dependency into a struct
						// field of the wrong type.
						//
						// We return a special error for that.
						if s, ok := r.(string); ok {
							needle := "reflect.Set: value of type "

							if strings.HasPrefix(s, needle) {
								needle = " is not assignable to type "

								if strings.Contains(s, needle) {
									err = newWrongTypeComplainer(dependencyName)
									return
								}
							}
						}

						msg := fmt.Sprintf("%T %v", r)

						err = errors.New( msg )
						return
					}

				}()

				value.Set( reflect.ValueOf(dependency) )

				return nil
			}(x.Field(i), dependencyName)

			if nil != err {
				return err
			}
		} else if "" != dependencyName {
			dependenciesNotFoundComplainer.insert(dependencyName)
		}
	}

	// If we had any missing dependencies, then return an error.
	if 0 < dependenciesNotFoundComplainer.len() {
		return dependenciesNotFoundComplainer
	}

	// Return (no errors).
	return nil
}

func (container *internalContainer) injectArrayOrSlice(thing interface{}) error {

	// Initialize.
	dependenciesNotFoundComplainer := newDependenciesNotFoundComplainer().(*internalDependenciesNotFoundComplainer)

	// Reflection!
	value := reflect.ValueOf(thing)

	// Go through each item in the array or slice, and inject each of them.
	length := value.Len()
	for i:=0; i<length; i++ {
		element := value.Index(i)

		if err := container.Inject(element.Interface()); nil != err {
			switch complainer := err.(type) {
			case DependenciesNotFoundComplainer:
				dependenciesNotFoundComplainer.concatenate(complainer)
			default:
				return err
			}
		}
	}

	// If we had any missing dependencies, then return an error.
	if 0 < dependenciesNotFoundComplainer.len() {
		return dependenciesNotFoundComplainer
	}

	// Return (no errors).
	return nil
}

func (container *internalContainer) injectMap(thing interface{}) error {

	// Initialize.
	dependenciesNotFoundComplainer := newDependenciesNotFoundComplainer().(*internalDependenciesNotFoundComplainer)

	// Reflection!
	value := reflect.ValueOf(thing)

	// Go through each item in the map, and inject each of them.
	keys := value.MapKeys()
	for _,key := range keys {
		element := value.MapIndex(key)

		if err := container.Inject(element.Interface()); nil != err {
			switch complainer := err.(type) {
			case DependenciesNotFoundComplainer:
				dependenciesNotFoundComplainer.concatenate(complainer)
			default:
				return err
			}
		}
	}

	// If we had any missing dependencies, then return an error.
	if 0 < dependenciesNotFoundComplainer.len() {
		return dependenciesNotFoundComplainer
	}

	// Return (no errors).
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/michaelsauter/crane/print"
	"os"
	"path"
	"strconv"
	"strings"
	"text/tabwriter"
)

type Container struct {
	Id         string
	Name       string `json:"name" yaml:"name"`
	Dockerfile string `json:"dockerfile" yaml:"dockerfile"`
	Image      string `json:"image" yaml:"image"`
	Run        RunParameters
}

type RunParameters struct {
	Cidfile     string      `json:"cidfile" yaml:"cidfile"`
	CpuShares   int         `json:"cpu-shares" yaml:"cpu-shares"`
	Detach      bool        `json:"detach" yaml:"detach"`
	Dns         []string    `json:"dns" yaml:"dns"`
	Entrypoint  string      `json:"entrypoint" yaml:"entrypoint"`
	Env         []string    `json:"env" yaml:"env"`
	Expose      []string    `json:"expose" yaml:"expose"`
	Host        string      `json:"host" yaml:"host"`
	Interactive bool        `json:"interactive" yaml:"interactive"`
	Link        []string    `json:"link" yaml:"link"`
	LxcConf     []string    `json:"lxc-conf" yaml:"lxc-conf"`
	Memory      string      `json:"memory" yaml:"memory"`
	Privileged  bool        `json:"privileged" yaml:"privileged"`
	Publish     []string    `json:"publish" yaml:"publish"`
	PublishAll  bool        `json:"publish-all" yaml:"publish-all"`
	Rm          bool        `json:"rm" yaml:"rm"`
	Tty         bool        `json:"tty" yaml:"tty"`
	User        string      `json:"user" yaml:"user"`
	Volume      []string    `json:"volume" yaml:"volume"`
	VolumesFrom []string    `json:"volumes-from" yaml:"volumes-from"`
	Workdir     string      `json:"workdir" yaml:"workdir"`
	Command     interface{} `json:"cmd" yaml:"cmd"`
}

func (container *Container) getId() (id string, err error) {
	if len(container.Id) > 0 {
		id = container.Id
	} else {
		// Inspect container, extracting the ID.
		// This will return gibberish if no container is found.
		args := []string{"inspect", "--format={{.ID}}", container.Name}
		output, outErr := commandOutput("docker", args)
		if err == nil {
			id = output
			container.Id = output
		} else {
			err = outErr
		}
	}
	return
}

func (container *Container) exists() bool {
	// `ps -a` returns all existant containers
	id, err := container.getId()
	if err != nil || len(id) == 0 {
		return false
	}
	dockerCmd := []string{"docker", "ps", "--quiet", "--all", "--no-trunc"}
	grepCmd := []string{"grep", "-wF", id}
	output, err := pipedCommandOutput(dockerCmd, grepCmd)
	if err != nil {
		return false
	}
	result := string(output)
	if len(result) > 0 {
		return true
	} else {
		return false
	}
}

func (container *Container) running() bool {
	// `ps` returns all running containers
	id, err := container.getId()
	if err != nil || len(id) == 0 {
		return false
	}
	dockerCmd := []string{"docker", "ps", "--quiet", "--no-trunc"}
	grepCmd := []string{"grep", "-wF", id}
	output, err := pipedCommandOutput(dockerCmd, grepCmd)
	if err != nil {
		return false
	}
	result := string(output)
	if len(result) > 0 {
		return true
	} else {
		return false
	}
}

func (container *Container) imageExists() bool {
	dockerCmd := []string{"docker", "images", "--no-trunc"}
	grepCmd := []string{"grep", "-wF", container.Image}
	output, err := pipedCommandOutput(dockerCmd, grepCmd)
	if err != nil {
		return false
	}
	result := string(output)
	if len(result) > 0 {
		return true
	} else {
		return false
	}
}

func (container *Container) status(w *tabwriter.Writer) {
	args := []string{"inspect", "--format={{.State.Running}}\t{{.ID}}\t{{if .NetworkSettings.IPAddress}}{{.NetworkSettings.IPAddress}}{{else}}-{{end}}\t{{range $k,$v := $.NetworkSettings.Ports}}{{$k}},{{end}}", container.Name}
	output, err := commandOutput("docker", args)
	if err != nil {
		fmt.Fprintf(w, "%s\tError:%v\t%v\n", container.Name, err, output)
		return
	}
	fmt.Fprintf(w, "%s\t%s\n", container.Name, output)
}

// Pull image for container
func (container *Container) pullImage() {
	fmt.Printf("Pulling image %s ... ", container.Image)
	args := []string{"pull", container.Image}
	executeCommand("docker", args)
}

// Build image for container
func (container *Container) buildImage() {
	fmt.Printf("Building image %s ... ", container.Image)
	args := []string{"build", "--rm", "--tag=" + container.Image, os.ExpandEnv(container.Dockerfile)}
	executeCommand("docker", args)
}

func (container Container) provision(force bool) {
	if force || !container.imageExists() {
		if len(container.Dockerfile) > 0 {
			container.buildImage()
		} else {
			container.pullImage()
		}
	} else {
		print.Notice("Image %s does already exist. Use --force to recreate.\n", container.Image)
	}
}

// Run or start container
func (container Container) runOrStart() {
	if container.exists() {
		container.start()
	} else {
		container.run()
	}
}

// Run container
func (container Container) run() {
	if container.exists() {
		print.Notice("Container %s does already exist. Use --force to recreate.\n", container.Name)
		if !container.running() {
			container.start()
		}
	} else {
		fmt.Printf("Running container %s ... ", container.Name)
		// Assemble command arguments
		args := []string{"run"}
		// Cidfile
		if len(container.Run.Cidfile) > 0 {
			args = append(args, "--cidfile", os.ExpandEnv(container.Run.Cidfile))
		}
		// CPU shares
		if container.Run.CpuShares > 0 {
			args = append(args, "--cpu-shares", os.ExpandEnv(strconv.Itoa(container.Run.CpuShares)))
		}
		// Detach
		if container.Run.Detach {
			args = append(args, "--detach")
		}
		// Dns
		for _, dns := range container.Run.Dns {
			args = append(args, "--dns", os.ExpandEnv(dns))
		}
		// Entrypoint
		if len(container.Run.Entrypoint) > 0 {
			args = append(args, "--entrypoint", os.ExpandEnv(container.Run.Entrypoint))
		}
		// Env
		for _, env := range container.Run.Env {
			args = append(args, "--env", os.ExpandEnv(env))
		}
		// Expose
		for _, expose := range container.Run.Expose {
			args = append(args, "--expose", os.ExpandEnv(expose))
		}
		// Host
		if len(container.Run.Host) > 0 {
			args = append(args, "--hostname", os.ExpandEnv(container.Run.Host))
		}
		// Interactive
		if container.Run.Interactive {
			args = append(args, "--interactive")
		}
		// Link
		for _, link := range container.Run.Link {
			args = append(args, "--link", link)
		}
		// LxcConf
		for _, lxcConf := range container.Run.LxcConf {
			args = append(args, "--lxc-conf", os.ExpandEnv(lxcConf))
		}
		// Memory
		if len(container.Run.Memory) > 0 {
			args = append(args, "--memory", os.ExpandEnv(container.Run.Memory))
		}
		// Privileged
		if container.Run.Privileged {
			args = append(args, "--privileged")
		}
		// Publish
		for _, port := range container.Run.Publish {
			args = append(args, "--publish", os.ExpandEnv(port))
		}
		// PublishAll
		if container.Run.PublishAll {
			args = append(args, "--publish-all")
		}
		// Rm
		if container.Run.Rm {
			args = append(args, "--rm")
		}
		// Tty
		if container.Run.Tty {
			args = append(args, "--tty")
		}
		// User
		if len(container.Run.User) > 0 {
			args = append(args, "--user", os.ExpandEnv(container.Run.User))
		}
		// Volumes
		for _, volume := range container.Run.Volume {
			paths := strings.Split(volume, ":")
			if !path.IsAbs(paths[0]) {
				cwd, _ := os.Getwd()
				paths[0] = cwd + "/" + paths[0]
			}
			args = append(args, "--volume", os.ExpandEnv(strings.Join(paths, ":")))
		}
		// VolumesFrom
		for _, volumeFrom := range container.Run.VolumesFrom {
			args = append(args, "--volumes-from", os.ExpandEnv(volumeFrom))
		}
		// Workdir
		if len(container.Run.Workdir) > 0 {
			args = append(args, "--workdir", os.ExpandEnv(container.Run.Workdir))
		}

		// Name
		args = append(args, "--name", container.Name)
		// Image
		args = append(args, container.Image)
		// Command
		if container.Run.Command != nil {
			switch cmd := container.Run.Command.(type) {
			case string:
				if len(cmd) > 0 {
					args = append(args, cmd)
				}
			case []interface{}:
				cmds := make([]string, len(cmd))
				for i, v := range cmd {
					cmds[i] = v.(string)
				}
				args = append(args, cmds...)
			default:
				print.Error("cmd is of unknown type!")
			}
		}
		// Execute command
		executeCommand("docker", args)
	}
}

// Start container
func (container Container) start() {
	if container.exists() {
		if !container.running() {
			fmt.Printf("Starting container %s ... ", container.Name)
			args := []string{"start", container.Name}
			executeCommand("docker", args)
		}
	} else {
		print.Error("Container %s does not exist.\n", container.Name)
	}
}

// Kill container
func (container Container) kill() {
	if container.running() {
		fmt.Printf("Killing container %s ... ", container.Name)
		args := []string{"kill", container.Name}
		executeCommand("docker", args)
	}
}

// Stop container
func (container Container) stop() {
	if container.running() {
		fmt.Printf("Stopping container %s ... ", container.Name)
		args := []string{"stop", container.Name}
		executeCommand("docker", args)
	}
}

// Remove container
func (container Container) rm() {
	if container.exists() {
		if container.running() {
			print.Error("Container %s is running and cannot be removed.\n", container.Name)
		} else {
			fmt.Printf("Removing container %s ... ", container.Name)
			args := []string{"rm", container.Name}
			executeCommand("docker", args)
		}
	}
}
<commit_msg>Add env-file parameter<commit_after>package main

import (
	"fmt"
	"github.com/michaelsauter/crane/print"
	"os"
	"path"
	"strconv"
	"strings"
	"text/tabwriter"
)

type Container struct {
	Id         string
	Name       string `json:"name" yaml:"name"`
	Dockerfile string `json:"dockerfile" yaml:"dockerfile"`
	Image      string `json:"image" yaml:"image"`
	Run        RunParameters
}

type RunParameters struct {
	Cidfile     string      `json:"cidfile" yaml:"cidfile"`
	CpuShares   int         `json:"cpu-shares" yaml:"cpu-shares"`
	Detach      bool        `json:"detach" yaml:"detach"`
	Dns         []string    `json:"dns" yaml:"dns"`
	Entrypoint  string      `json:"entrypoint" yaml:"entrypoint"`
	Env         []string    `json:"env" yaml:"env"`
	EnvFile     string      `json:"env-file" yaml:"env-file"`
	Expose      []string    `json:"expose" yaml:"expose"`
	Host        string      `json:"host" yaml:"host"`
	Interactive bool        `json:"interactive" yaml:"interactive"`
	Link        []string    `json:"link" yaml:"link"`
	LxcConf     []string    `json:"lxc-conf" yaml:"lxc-conf"`
	Memory      string      `json:"memory" yaml:"memory"`
	Privileged  bool        `json:"privileged" yaml:"privileged"`
	Publish     []string    `json:"publish" yaml:"publish"`
	PublishAll  bool        `json:"publish-all" yaml:"publish-all"`
	Rm          bool        `json:"rm" yaml:"rm"`
	Tty         bool        `json:"tty" yaml:"tty"`
	User        string      `json:"user" yaml:"user"`
	Volume      []string    `json:"volume" yaml:"volume"`
	VolumesFrom []string    `json:"volumes-from" yaml:"volumes-from"`
	Workdir     string      `json:"workdir" yaml:"workdir"`
	Command     interface{} `json:"cmd" yaml:"cmd"`
}

func (container *Container) getId() (id string, err error) {
	if len(container.Id) > 0 {
		id = container.Id
	} else {
		// Inspect container, extracting the ID.
		// This will return gibberish if no container is found.
		args := []string{"inspect", "--format={{.ID}}", container.Name}
		output, outErr := commandOutput("docker", args)
		if err == nil {
			id = output
			container.Id = output
		} else {
			err = outErr
		}
	}
	return
}

func (container *Container) exists() bool {
	// `ps -a` returns all existant containers
	id, err := container.getId()
	if err != nil || len(id) == 0 {
		return false
	}
	dockerCmd := []string{"docker", "ps", "--quiet", "--all", "--no-trunc"}
	grepCmd := []string{"grep", "-wF", id}
	output, err := pipedCommandOutput(dockerCmd, grepCmd)
	if err != nil {
		return false
	}
	result := string(output)
	if len(result) > 0 {
		return true
	} else {
		return false
	}
}

func (container *Container) running() bool {
	// `ps` returns all running containers
	id, err := container.getId()
	if err != nil || len(id) == 0 {
		return false
	}
	dockerCmd := []string{"docker", "ps", "--quiet", "--no-trunc"}
	grepCmd := []string{"grep", "-wF", id}
	output, err := pipedCommandOutput(dockerCmd, grepCmd)
	if err != nil {
		return false
	}
	result := string(output)
	if len(result) > 0 {
		return true
	} else {
		return false
	}
}

func (container *Container) imageExists() bool {
	dockerCmd := []string{"docker", "images", "--no-trunc"}
	grepCmd := []string{"grep", "-wF", container.Image}
	output, err := pipedCommandOutput(dockerCmd, grepCmd)
	if err != nil {
		return false
	}
	result := string(output)
	if len(result) > 0 {
		return true
	} else {
		return false
	}
}

func (container *Container) status(w *tabwriter.Writer) {
	args := []string{"inspect", "--format={{.State.Running}}\t{{.ID}}\t{{if .NetworkSettings.IPAddress}}{{.NetworkSettings.IPAddress}}{{else}}-{{end}}\t{{range $k,$v := $.NetworkSettings.Ports}}{{$k}},{{end}}", container.Name}
	output, err := commandOutput("docker", args)
	if err != nil {
		fmt.Fprintf(w, "%s\tError:%v\t%v\n", container.Name, err, output)
		return
	}
	fmt.Fprintf(w, "%s\t%s\n", container.Name, output)
}

// Pull image for container
func (container *Container) pullImage() {
	fmt.Printf("Pulling image %s ... ", container.Image)
	args := []string{"pull", container.Image}
	executeCommand("docker", args)
}

// Build image for container
func (container *Container) buildImage() {
	fmt.Printf("Building image %s ... ", container.Image)
	args := []string{"build", "--rm", "--tag=" + container.Image, os.ExpandEnv(container.Dockerfile)}
	executeCommand("docker", args)
}

func (container Container) provision(force bool) {
	if force || !container.imageExists() {
		if len(container.Dockerfile) > 0 {
			container.buildImage()
		} else {
			container.pullImage()
		}
	} else {
		print.Notice("Image %s does already exist. Use --force to recreate.\n", container.Image)
	}
}

// Run or start container
func (container Container) runOrStart() {
	if container.exists() {
		container.start()
	} else {
		container.run()
	}
}

// Run container
func (container Container) run() {
	if container.exists() {
		print.Notice("Container %s does already exist. Use --force to recreate.\n", container.Name)
		if !container.running() {
			container.start()
		}
	} else {
		fmt.Printf("Running container %s ... ", container.Name)
		// Assemble command arguments
		args := []string{"run"}
		// Cidfile
		if len(container.Run.Cidfile) > 0 {
			args = append(args, "--cidfile", os.ExpandEnv(container.Run.Cidfile))
		}
		// CPU shares
		if container.Run.CpuShares > 0 {
			args = append(args, "--cpu-shares", os.ExpandEnv(strconv.Itoa(container.Run.CpuShares)))
		}
		// Detach
		if container.Run.Detach {
			args = append(args, "--detach")
		}
		// Dns
		for _, dns := range container.Run.Dns {
			args = append(args, "--dns", os.ExpandEnv(dns))
		}
		// Entrypoint
		if len(container.Run.Entrypoint) > 0 {
			args = append(args, "--entrypoint", os.ExpandEnv(container.Run.Entrypoint))
		}
		// Env
		for _, env := range container.Run.Env {
			args = append(args, "--env", os.ExpandEnv(env))
		}
		// Env file
		if len(container.Run.EnvFile) > 0 {
			args = append(args, "--env-file", os.ExpandEnv(container.Run.EnvFile))
		}
		// Expose
		for _, expose := range container.Run.Expose {
			args = append(args, "--expose", os.ExpandEnv(expose))
		}
		// Host
		if len(container.Run.Host) > 0 {
			args = append(args, "--hostname", os.ExpandEnv(container.Run.Host))
		}
		// Interactive
		if container.Run.Interactive {
			args = append(args, "--interactive")
		}
		// Link
		for _, link := range container.Run.Link {
			args = append(args, "--link", link)
		}
		// LxcConf
		for _, lxcConf := range container.Run.LxcConf {
			args = append(args, "--lxc-conf", os.ExpandEnv(lxcConf))
		}
		// Memory
		if len(container.Run.Memory) > 0 {
			args = append(args, "--memory", os.ExpandEnv(container.Run.Memory))
		}
		// Privileged
		if container.Run.Privileged {
			args = append(args, "--privileged")
		}
		// Publish
		for _, port := range container.Run.Publish {
			args = append(args, "--publish", os.ExpandEnv(port))
		}
		// PublishAll
		if container.Run.PublishAll {
			args = append(args, "--publish-all")
		}
		// Rm
		if container.Run.Rm {
			args = append(args, "--rm")
		}
		// Tty
		if container.Run.Tty {
			args = append(args, "--tty")
		}
		// User
		if len(container.Run.User) > 0 {
			args = append(args, "--user", os.ExpandEnv(container.Run.User))
		}
		// Volumes
		for _, volume := range container.Run.Volume {
			paths := strings.Split(volume, ":")
			if !path.IsAbs(paths[0]) {
				cwd, _ := os.Getwd()
				paths[0] = cwd + "/" + paths[0]
			}
			args = append(args, "--volume", os.ExpandEnv(strings.Join(paths, ":")))
		}
		// VolumesFrom
		for _, volumeFrom := range container.Run.VolumesFrom {
			args = append(args, "--volumes-from", os.ExpandEnv(volumeFrom))
		}
		// Workdir
		if len(container.Run.Workdir) > 0 {
			args = append(args, "--workdir", os.ExpandEnv(container.Run.Workdir))
		}

		// Name
		args = append(args, "--name", container.Name)
		// Image
		args = append(args, container.Image)
		// Command
		if container.Run.Command != nil {
			switch cmd := container.Run.Command.(type) {
			case string:
				if len(cmd) > 0 {
					args = append(args, cmd)
				}
			case []interface{}:
				cmds := make([]string, len(cmd))
				for i, v := range cmd {
					cmds[i] = v.(string)
				}
				args = append(args, cmds...)
			default:
				print.Error("cmd is of unknown type!")
			}
		}
		// Execute command
		executeCommand("docker", args)
	}
}

// Start container
func (container Container) start() {
	if container.exists() {
		if !container.running() {
			fmt.Printf("Starting container %s ... ", container.Name)
			args := []string{"start", container.Name}
			executeCommand("docker", args)
		}
	} else {
		print.Error("Container %s does not exist.\n", container.Name)
	}
}

// Kill container
func (container Container) kill() {
	if container.running() {
		fmt.Printf("Killing container %s ... ", container.Name)
		args := []string{"kill", container.Name}
		executeCommand("docker", args)
	}
}

// Stop container
func (container Container) stop() {
	if container.running() {
		fmt.Printf("Stopping container %s ... ", container.Name)
		args := []string{"stop", container.Name}
		executeCommand("docker", args)
	}
}

// Remove container
func (container Container) rm() {
	if container.exists() {
		if container.running() {
			print.Error("Container %s is running and cannot be removed.\n", container.Name)
		} else {
			fmt.Printf("Removing container %s ... ", container.Name)
			args := []string{"rm", container.Name}
			executeCommand("docker", args)
		}
	}
}
<|endoftext|>


<commit_before>package libkbfs

import (
	"fmt"

	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/logger"
	keybase1 "github.com/keybase/client/go/protocol"
	"golang.org/x/net/context"
)

// KBPKIClient uses a config's KeybaseDaemon.
type KBPKIClient struct {
	config Config
	log    logger.Logger
}

var _ KBPKI = (*KBPKIClient)(nil)

// NewKBPKIClient returns a new KBPKIClient with the given Config.
func NewKBPKIClient(config Config) *KBPKIClient {
	return &KBPKIClient{config, config.MakeLogger("")}
}

// GetCurrentToken implements the KBPKI interface for KBPKIClient.
func (k *KBPKIClient) GetCurrentToken(ctx context.Context) (string, error) {
	s, err := k.session(ctx)
	if err != nil {
		// XXX shouldn't ignore this...
		k.log.CWarningf(ctx, "error getting session: %q", err)
		return "", err
	}
	return s.Token, nil
}

// GetCurrentUserInfo implements the KBPKI interface for KBPKIClient.
func (k *KBPKIClient) GetCurrentUserInfo(ctx context.Context) (
	libkb.NormalizedUsername, keybase1.UID, error) {
	s, err := k.session(ctx)
	if err != nil {
		// TODO: something more intelligent; maybe just shut down
		// unless we want anonymous browsing of public data
		return libkb.NormalizedUsername(""), keybase1.UID(""), err
	}
	return s.Name, s.UID, nil
}

// GetCurrentCryptPublicKey implements the KBPKI interface for KBPKIClient.
func (k *KBPKIClient) GetCurrentCryptPublicKey(ctx context.Context) (
	CryptPublicKey, error) {
	s, err := k.session(ctx)
	if err != nil {
		return CryptPublicKey{}, err
	}
	return s.CryptPublicKey, nil
}

// GetCurrentVerifyingKey implements the KBPKI interface for KBPKIClient.
func (k *KBPKIClient) GetCurrentVerifyingKey(ctx context.Context) (
	VerifyingKey, error) {
	s, err := k.session(ctx)
	if err != nil {
		return VerifyingKey{}, err
	}
	return s.VerifyingKey, nil
}

// Resolve implements the KBPKI interface for KBPKIClient.
func (k *KBPKIClient) Resolve(ctx context.Context, assertion string) (
	libkb.NormalizedUsername, keybase1.UID, error) {
	return k.config.KeybaseDaemon().Resolve(ctx, assertion)
}

// Identify implements the KBPKI interface for KBPKIClient.
func (k *KBPKIClient) Identify(ctx context.Context, assertion, reason string) (
	UserInfo, error) {
	return k.config.KeybaseDaemon().Identify(ctx, assertion, reason)
}

// GetNormalizedUsername implements the KBPKI interface for
// KBPKIClient.
func (k *KBPKIClient) GetNormalizedUsername(ctx context.Context, uid keybase1.UID) (
	libkb.NormalizedUsername, error) {
	username, _, err := k.Resolve(ctx, fmt.Sprintf("uid:%s", uid))
	if err != nil {
		return libkb.NormalizedUsername(""), err
	}
	return username, nil
}

// HasVerifyingKey implements the KBPKI interface for KBPKIClient.
func (k *KBPKIClient) HasVerifyingKey(ctx context.Context, uid keybase1.UID,
	verifyingKey VerifyingKey) error {
	for i := 0; i < 2; i++ {
		userInfo, err := k.loadUserPlusKeys(ctx, uid)
		if err != nil {
			return err
		}

		for _, key := range userInfo.VerifyingKeys {
			if verifyingKey.kid.Equal(key.kid) {
				k.log.CDebugf(ctx, "found verifying key %s for user %s",
					verifyingKey.kid, uid)
				return nil
			}
		}

		// If the first attempt couldn't find the key, try again after
		// clearing our local cache.  We might have stale info if the
		// service hasn't learned of the users' new key yet.
		if i == 0 {
			k.config.KeybaseDaemon().FlushUserFromLocalCache(ctx, uid)
		}
	}

	return KeyNotFoundError{verifyingKey.kid}
}

// GetCryptPublicKeys implements the KBPKI interface for KBPKIClient.
func (k *KBPKIClient) GetCryptPublicKeys(ctx context.Context,
	uid keybase1.UID) (keys []CryptPublicKey, err error) {
	userInfo, err := k.loadUserPlusKeys(ctx, uid)
	if err != nil {
		return nil, err
	}
	return userInfo.CryptPublicKeys, nil
}

func (k *KBPKIClient) loadUserPlusKeys(ctx context.Context, uid keybase1.UID) (
	UserInfo, error) {
	return k.config.KeybaseDaemon().LoadUserPlusKeys(ctx, uid)
}

func (k *KBPKIClient) session(ctx context.Context) (SessionInfo, error) {
	const sessionID = 0
	return k.config.KeybaseDaemon().CurrentSession(ctx, sessionID)
}

// FavoriteAdd implements the KBPKI interface for KBPKIClient.
func (k *KBPKIClient) FavoriteAdd(ctx context.Context, folder keybase1.Folder) error {
	return k.config.KeybaseDaemon().FavoriteAdd(ctx, folder)
}

// FavoriteDelete implements the KBPKI interface for KBPKIClient.
func (k *KBPKIClient) FavoriteDelete(ctx context.Context, folder keybase1.Folder) error {
	return k.config.KeybaseDaemon().FavoriteDelete(ctx, folder)
}

// FavoriteList implements the KBPKI interface for KBPKIClient.
func (k *KBPKIClient) FavoriteList(ctx context.Context) ([]keybase1.Folder, error) {
	const sessionID = 0
	return k.config.KeybaseDaemon().FavoriteList(ctx, sessionID)
}

// Notify implements the KBPKI interface for KBPKIClient.
func (k *KBPKIClient) Notify(ctx context.Context, notification *keybase1.FSNotification) error {
	return k.config.KeybaseDaemon().Notify(ctx, notification)
}
<commit_msg>kbpki_client: get rid of HasVerifyingKey for loop<commit_after>package libkbfs

import (
	"fmt"

	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/logger"
	keybase1 "github.com/keybase/client/go/protocol"
	"golang.org/x/net/context"
)

// KBPKIClient uses a config's KeybaseDaemon.
type KBPKIClient struct {
	config Config
	log    logger.Logger
}

var _ KBPKI = (*KBPKIClient)(nil)

// NewKBPKIClient returns a new KBPKIClient with the given Config.
func NewKBPKIClient(config Config) *KBPKIClient {
	return &KBPKIClient{config, config.MakeLogger("")}
}

// GetCurrentToken implements the KBPKI interface for KBPKIClient.
func (k *KBPKIClient) GetCurrentToken(ctx context.Context) (string, error) {
	s, err := k.session(ctx)
	if err != nil {
		// XXX shouldn't ignore this...
		k.log.CWarningf(ctx, "error getting session: %q", err)
		return "", err
	}
	return s.Token, nil
}

// GetCurrentUserInfo implements the KBPKI interface for KBPKIClient.
func (k *KBPKIClient) GetCurrentUserInfo(ctx context.Context) (
	libkb.NormalizedUsername, keybase1.UID, error) {
	s, err := k.session(ctx)
	if err != nil {
		// TODO: something more intelligent; maybe just shut down
		// unless we want anonymous browsing of public data
		return libkb.NormalizedUsername(""), keybase1.UID(""), err
	}
	return s.Name, s.UID, nil
}

// GetCurrentCryptPublicKey implements the KBPKI interface for KBPKIClient.
func (k *KBPKIClient) GetCurrentCryptPublicKey(ctx context.Context) (
	CryptPublicKey, error) {
	s, err := k.session(ctx)
	if err != nil {
		return CryptPublicKey{}, err
	}
	return s.CryptPublicKey, nil
}

// GetCurrentVerifyingKey implements the KBPKI interface for KBPKIClient.
func (k *KBPKIClient) GetCurrentVerifyingKey(ctx context.Context) (
	VerifyingKey, error) {
	s, err := k.session(ctx)
	if err != nil {
		return VerifyingKey{}, err
	}
	return s.VerifyingKey, nil
}

// Resolve implements the KBPKI interface for KBPKIClient.
func (k *KBPKIClient) Resolve(ctx context.Context, assertion string) (
	libkb.NormalizedUsername, keybase1.UID, error) {
	return k.config.KeybaseDaemon().Resolve(ctx, assertion)
}

// Identify implements the KBPKI interface for KBPKIClient.
func (k *KBPKIClient) Identify(ctx context.Context, assertion, reason string) (
	UserInfo, error) {
	return k.config.KeybaseDaemon().Identify(ctx, assertion, reason)
}

// GetNormalizedUsername implements the KBPKI interface for
// KBPKIClient.
func (k *KBPKIClient) GetNormalizedUsername(ctx context.Context, uid keybase1.UID) (
	libkb.NormalizedUsername, error) {
	username, _, err := k.Resolve(ctx, fmt.Sprintf("uid:%s", uid))
	if err != nil {
		return libkb.NormalizedUsername(""), err
	}
	return username, nil
}

func (k *KBPKIClient) hasVerifyingKey(ctx context.Context, uid keybase1.UID,
	verifyingKey VerifyingKey) (bool, error) {
	userInfo, err := k.loadUserPlusKeys(ctx, uid)
	if err != nil {
		return false, err
	}

	for _, key := range userInfo.VerifyingKeys {
		if verifyingKey.kid.Equal(key.kid) {
			k.log.CDebugf(ctx, "found verifying key %s for user %s",
				verifyingKey.kid, uid)
			return true, nil
		}
	}

	return false, nil
}

// HasVerifyingKey implements the KBPKI interface for KBPKIClient.
func (k *KBPKIClient) HasVerifyingKey(ctx context.Context, uid keybase1.UID,
	verifyingKey VerifyingKey) error {
	ok, err := k.hasVerifyingKey(ctx, uid, verifyingKey)
	if err != nil {
		return err
	}
	if ok {
		return nil
	}

	// If the first attempt couldn't find the key, try again after
	// clearing our local cache.  We might have stale info if the
	// service hasn't learned of the users' new key yet.
	k.config.KeybaseDaemon().FlushUserFromLocalCache(ctx, uid)

	ok, err = k.hasVerifyingKey(ctx, uid, verifyingKey)
	if err != nil {
		return err
	}
	if !ok {
		return KeyNotFoundError{verifyingKey.kid}
	}
	return nil
}

// GetCryptPublicKeys implements the KBPKI interface for KBPKIClient.
func (k *KBPKIClient) GetCryptPublicKeys(ctx context.Context,
	uid keybase1.UID) (keys []CryptPublicKey, err error) {
	userInfo, err := k.loadUserPlusKeys(ctx, uid)
	if err != nil {
		return nil, err
	}
	return userInfo.CryptPublicKeys, nil
}

func (k *KBPKIClient) loadUserPlusKeys(ctx context.Context, uid keybase1.UID) (
	UserInfo, error) {
	return k.config.KeybaseDaemon().LoadUserPlusKeys(ctx, uid)
}

func (k *KBPKIClient) session(ctx context.Context) (SessionInfo, error) {
	const sessionID = 0
	return k.config.KeybaseDaemon().CurrentSession(ctx, sessionID)
}

// FavoriteAdd implements the KBPKI interface for KBPKIClient.
func (k *KBPKIClient) FavoriteAdd(ctx context.Context, folder keybase1.Folder) error {
	return k.config.KeybaseDaemon().FavoriteAdd(ctx, folder)
}

// FavoriteDelete implements the KBPKI interface for KBPKIClient.
func (k *KBPKIClient) FavoriteDelete(ctx context.Context, folder keybase1.Folder) error {
	return k.config.KeybaseDaemon().FavoriteDelete(ctx, folder)
}

// FavoriteList implements the KBPKI interface for KBPKIClient.
func (k *KBPKIClient) FavoriteList(ctx context.Context) ([]keybase1.Folder, error) {
	const sessionID = 0
	return k.config.KeybaseDaemon().FavoriteList(ctx, sessionID)
}

// Notify implements the KBPKI interface for KBPKIClient.
func (k *KBPKIClient) Notify(ctx context.Context, notification *keybase1.FSNotification) error {
	return k.config.KeybaseDaemon().Notify(ctx, notification)
}
<|endoftext|>


<commit_before>package dependency

import (
	"errors"
	"fmt"
	"log"
	"regexp"
	"sort"
	"strconv"
	"strings"

	"github.com/hashicorp/consul/api"
	"github.com/hashicorp/go-multierror"
)

// Ripped from https://github.com/hashicorp/consul/blob/master/consul/structs/structs.go#L31
const (
	HealthAny      = "any"
	HealthUnknown  = "unknown"
	HealthPassing  = "passing"
	HealthWarning  = "warning"
	HealthCritical = "critical"
)

// HealthService is a service entry in Consul.
type HealthService struct {
	Node        string
	NodeAddress string
	Address     string
	ID          string
	Name        string
	Tags        ServiceTags
	Port        uint64
}

// HealthServices is the struct that is formed from the dependency inside a
// template.
type HealthServices struct {
	rawKey     string
	Name       string
	Tag        string
	DataCenter string
	Port       uint64
	Status     ServiceStatusFilter
}

// Fetch queries the Consul API defined by the given client and returns a slice
// of HealthService objects.
func (d *HealthServices) Fetch(client *api.Client, options *api.QueryOptions) (interface{}, *api.QueryMeta, error) {
	if d.DataCenter != "" {
		options.Datacenter = d.DataCenter
	}

	log.Printf("[DEBUG] (%s) querying Consul with %+v", d.Display(), options)

	health := client.Health()
	entries, qm, err := health.Service(d.Name, d.Tag, d.Status.passingOnly(), options)
	if err != nil {
		return nil, qm, err
	}

	log.Printf("[DEBUG] (%s) Consul returned %d services", d.Display(), len(entries))

	services := make([]*HealthService, 0, len(entries))

	for _, entry := range entries {
		if !d.Status.Accept(entry.Checks) {
			continue
		}

		tags := deepCopyAndSortTags(entry.Service.Tags)

		// Get the address of the service, falling back to the address of the node.
		var address string
		if entry.Service.Address != "" {
			address = entry.Service.Address
		} else {
			address = entry.Node.Address
		}

		services = append(services, &HealthService{
			Node:        entry.Node.Node,
			NodeAddress: entry.Node.Address,
			Address:     address,
			ID:          entry.Service.ID,
			Name:        entry.Service.Service,
			Tags:        tags,
			Port:        uint64(entry.Service.Port),
		})
	}

	log.Printf("[DEBUG] (%s) %d services after health check status filtering", d.Display(), len(services))

	sort.Stable(HealthServiceList(services))

	return services, qm, nil
}

func (d *HealthServices) HashCode() string {
	return fmt.Sprintf("HealthServices|%s", d.rawKey)
}

func (d *HealthServices) Display() string {
	return fmt.Sprintf(`"service(%s)"`, d.rawKey)
}

// ParseHealthServices processes the incoming strings to build a service dependency.
//
// Supported arguments
//   ParseHealthServices("service_id")
//   ParseHealthServices("service_id", "health_check")
//
// Where service_id is in the format of service(.tag(@datacenter(:port)))
// and health_check is either "any" or "passing".
//
// If no health_check is provided then its the same as "passing".
func ParseHealthServices(s ...string) (*HealthServices, error) {
	var query string
	var status ServiceStatusFilter
	var err error

	switch len(s) {
	case 1:
		query = s[0]
		status, err = NewServiceStatusFilter("")
		if err != nil {
			return nil, err
		}
	case 2:
		query = s[0]
		status, err = NewServiceStatusFilter(s[1])
		if err != nil {
			return nil, err
		}
	default:
		return nil, fmt.Errorf("expected 1 or 2 arguments, got %d", len(s))
	}

	if len(query) == 0 {
		return nil, errors.New("cannot specify empty health service dependency")
	}

	re := regexp.MustCompile(`\A` +
		`((?P<tag>[[:word:]\-.]+)\.)?` +
		`((?P<name>[[:word:]\-/_]+))` +
		`(@(?P<datacenter>[[:word:]\.\-]+))?(:(?P<port>[0-9]+))?` +
		`\z`)
	names := re.SubexpNames()
	match := re.FindAllStringSubmatch(query, -1)

	if len(match) == 0 {
		return nil, errors.New("invalid health service dependency format")
	}

	r := match[0]

	m := map[string]string{}
	for i, n := range r {
		if names[i] != "" {
			m[names[i]] = n
		}
	}

	tag, name, datacenter, port := m["tag"], m["name"], m["datacenter"], m["port"]

	if name == "" {
		return nil, errors.New("name part is required")
	}

	sd := &HealthServices{
		rawKey:     fmt.Sprintf("%s %s", query, status),
		Name:       name,
		Tag:        tag,
		DataCenter: datacenter,
		Status:     status,
	}

	if port != "" {
		port, err := strconv.ParseUint(port, 0, 64)
		if err != nil {
			return nil, err
		} else {
			sd.Port = port
		}
	}

	return sd, nil
}

// ServiceStatusFilter is used to specify a list of service statuses that you want filter by.
type ServiceStatusFilter []string

// String returns the string representation of this status filter
func (f ServiceStatusFilter) String() string {
	return fmt.Sprintf("[%s]", strings.Join(f, ","))
}

// NewServiceStatusFilter creates a status filter from the given string in the
// format `[key[,key[,key...]]]`. Each status is split on the comma character
// and must match one of the valid status names.
//
// If the empty string is given, it is assumed only "passing" statuses are to
// be returned.
//
// If the user specifies "any" with other keys, an error will be returned.
func NewServiceStatusFilter(s string) (ServiceStatusFilter, error) {
	// If no statuses were given, use the default status of "passing".
	if len(s) == 0 {
		return ServiceStatusFilter{HealthPassing}, nil
	}

	var errs *multierror.Error
	var hasAny bool

	raw := strings.Split(s, ",")
	trimmed := make(ServiceStatusFilter, 0, len(raw))
	for _, r := range raw {
		trim := strings.TrimSpace(r)

		// Ignore the empty string.
		if len(trim) == 0 {
			continue
		}

		// Record the case where we have the "any" status - it will be used later.
		if trim == HealthAny {
			hasAny = true
		}

		// Validate that the service is actually a valid name.
		if trim != HealthAny &&
			trim != HealthUnknown &&
			trim != HealthPassing &&
			trim != HealthWarning &&
			trim != HealthCritical {
			errs = multierror.Append(errs, fmt.Errorf("service filter: invalid filter %q", trim))
		}
		trimmed = append(trimmed, trim)
	}

	// If the user specified "any" with additional keys, that is invalid.
	if hasAny && len(trimmed) != 1 {
		errs = multierror.Append(errs, fmt.Errorf("service filter: cannot specify extra keys when using %q", "any"))
	}

	return trimmed, errs.ErrorOrNil()
}

// Accept allows us to check if a slice of health checks pass this filter.
func (f ServiceStatusFilter) Accept(checks []*api.HealthCheck) bool {
	// If the any filter is activated, pass everything.
	if f.any() {
		return true
	}

	// If ONLY the passing filter is activated, ensure all checks are passing and
	// return. If more than one filter is given (like "passing, unknown"), then
	// this conditional will not fire.
	if f.passingOnly() {
		for _, check := range checks {
			if check.Status != HealthPassing {
				return false
			}
		}
		return true
	}

	// Iterate over each status and see if ANY of the checks have that status.
	for _, status := range f {
		for _, check := range checks {
			if status == check.Status {
				return true
			}
		}
	}

	return false
}

// any is a helper method to determine if this is an "any" service status
// filter. If "any" was given, it must be the only item in the list.
func (f ServiceStatusFilter) any() bool {
	return len(f) == 1 && f[0] == HealthAny
}

// onlyPassing returns true if the filter contains only a "passing" service
// status filter.
//
// NOTE: If there is more than one filter, this will return false, even if one
// of the filters is "passing". This is because "passingOnly" is used to alter
// the type of query made to Consul.
func (f ServiceStatusFilter) passingOnly() bool {
	return len(f) == 1 && f[0] == HealthPassing
}

// ServiceTags is a slice of tags assigned to a Service
type ServiceTags []string

// Contains returns true if the tags exists in the ServiceTags slice.
func (t ServiceTags) Contains(s string) bool {
	for _, v := range t {
		if v == s {
			return true
		}
	}
	return false
}

// HealthServiceList is a sortable slice of Service
type HealthServiceList []*HealthService

// Len, Swap, and Less are used to implement the sort.Sort interface.
func (s HealthServiceList) Len() int      { return len(s) }
func (s HealthServiceList) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s HealthServiceList) Less(i, j int) bool {
	if s[i].Node < s[j].Node {
		return true
	} else if s[i].Node == s[j].Node {
		return s[i].ID <= s[j].ID
	}
	return false
}
<commit_msg>Use a switch statement instead<commit_after>package dependency

import (
	"errors"
	"fmt"
	"log"
	"regexp"
	"sort"
	"strconv"
	"strings"

	"github.com/hashicorp/consul/api"
	"github.com/hashicorp/go-multierror"
)

// Ripped from https://github.com/hashicorp/consul/blob/master/consul/structs/structs.go#L31
const (
	HealthAny      = "any"
	HealthUnknown  = "unknown"
	HealthPassing  = "passing"
	HealthWarning  = "warning"
	HealthCritical = "critical"
)

// HealthService is a service entry in Consul.
type HealthService struct {
	Node        string
	NodeAddress string
	Address     string
	ID          string
	Name        string
	Tags        ServiceTags
	Port        uint64
}

// HealthServices is the struct that is formed from the dependency inside a
// template.
type HealthServices struct {
	rawKey     string
	Name       string
	Tag        string
	DataCenter string
	Port       uint64
	Status     ServiceStatusFilter
}

// Fetch queries the Consul API defined by the given client and returns a slice
// of HealthService objects.
func (d *HealthServices) Fetch(client *api.Client, options *api.QueryOptions) (interface{}, *api.QueryMeta, error) {
	if d.DataCenter != "" {
		options.Datacenter = d.DataCenter
	}

	log.Printf("[DEBUG] (%s) querying Consul with %+v", d.Display(), options)

	health := client.Health()
	entries, qm, err := health.Service(d.Name, d.Tag, d.Status.passingOnly(), options)
	if err != nil {
		return nil, qm, err
	}

	log.Printf("[DEBUG] (%s) Consul returned %d services", d.Display(), len(entries))

	services := make([]*HealthService, 0, len(entries))

	for _, entry := range entries {
		if !d.Status.Accept(entry.Checks) {
			continue
		}

		tags := deepCopyAndSortTags(entry.Service.Tags)

		// Get the address of the service, falling back to the address of the node.
		var address string
		if entry.Service.Address != "" {
			address = entry.Service.Address
		} else {
			address = entry.Node.Address
		}

		services = append(services, &HealthService{
			Node:        entry.Node.Node,
			NodeAddress: entry.Node.Address,
			Address:     address,
			ID:          entry.Service.ID,
			Name:        entry.Service.Service,
			Tags:        tags,
			Port:        uint64(entry.Service.Port),
		})
	}

	log.Printf("[DEBUG] (%s) %d services after health check status filtering", d.Display(), len(services))

	sort.Stable(HealthServiceList(services))

	return services, qm, nil
}

func (d *HealthServices) HashCode() string {
	return fmt.Sprintf("HealthServices|%s", d.rawKey)
}

func (d *HealthServices) Display() string {
	return fmt.Sprintf(`"service(%s)"`, d.rawKey)
}

// ParseHealthServices processes the incoming strings to build a service dependency.
//
// Supported arguments
//   ParseHealthServices("service_id")
//   ParseHealthServices("service_id", "health_check")
//
// Where service_id is in the format of service(.tag(@datacenter(:port)))
// and health_check is either "any" or "passing".
//
// If no health_check is provided then its the same as "passing".
func ParseHealthServices(s ...string) (*HealthServices, error) {
	var query string
	var status ServiceStatusFilter
	var err error

	switch len(s) {
	case 1:
		query = s[0]
		status, err = NewServiceStatusFilter("")
		if err != nil {
			return nil, err
		}
	case 2:
		query = s[0]
		status, err = NewServiceStatusFilter(s[1])
		if err != nil {
			return nil, err
		}
	default:
		return nil, fmt.Errorf("expected 1 or 2 arguments, got %d", len(s))
	}

	if len(query) == 0 {
		return nil, errors.New("cannot specify empty health service dependency")
	}

	re := regexp.MustCompile(`\A` +
		`((?P<tag>[[:word:]\-.]+)\.)?` +
		`((?P<name>[[:word:]\-/_]+))` +
		`(@(?P<datacenter>[[:word:]\.\-]+))?(:(?P<port>[0-9]+))?` +
		`\z`)
	names := re.SubexpNames()
	match := re.FindAllStringSubmatch(query, -1)

	if len(match) == 0 {
		return nil, errors.New("invalid health service dependency format")
	}

	r := match[0]

	m := map[string]string{}
	for i, n := range r {
		if names[i] != "" {
			m[names[i]] = n
		}
	}

	tag, name, datacenter, port := m["tag"], m["name"], m["datacenter"], m["port"]

	if name == "" {
		return nil, errors.New("name part is required")
	}

	sd := &HealthServices{
		rawKey:     fmt.Sprintf("%s %s", query, status),
		Name:       name,
		Tag:        tag,
		DataCenter: datacenter,
		Status:     status,
	}

	if port != "" {
		port, err := strconv.ParseUint(port, 0, 64)
		if err != nil {
			return nil, err
		} else {
			sd.Port = port
		}
	}

	return sd, nil
}

// ServiceStatusFilter is used to specify a list of service statuses that you want filter by.
type ServiceStatusFilter []string

// String returns the string representation of this status filter
func (f ServiceStatusFilter) String() string {
	return fmt.Sprintf("[%s]", strings.Join(f, ","))
}

// NewServiceStatusFilter creates a status filter from the given string in the
// format `[key[,key[,key...]]]`. Each status is split on the comma character
// and must match one of the valid status names.
//
// If the empty string is given, it is assumed only "passing" statuses are to
// be returned.
//
// If the user specifies "any" with other keys, an error will be returned.
func NewServiceStatusFilter(s string) (ServiceStatusFilter, error) {
	// If no statuses were given, use the default status of "passing".
	if len(s) == 0 {
		return ServiceStatusFilter{HealthPassing}, nil
	}

	var errs *multierror.Error
	var hasAny bool

	raw := strings.Split(s, ",")
	trimmed := make(ServiceStatusFilter, 0, len(raw))
	for _, r := range raw {
		trim := strings.TrimSpace(r)

		// Ignore the empty string.
		if len(trim) == 0 {
			continue
		}

		// Record the case where we have the "any" status - it will be used later.
		if trim == HealthAny {
			hasAny = true
		}

		// Validate that the service is actually a valid name.
		switch trim {
		case HealthAny, HealthUnknown, HealthPassing, HealthWarning, HealthCritical:
			trimmed = append(trimmed, trim)
		default:
			errs = multierror.Append(errs, fmt.Errorf("service filter: invalid filter %q", trim))
		}
	}

	// If the user specified "any" with additional keys, that is invalid.
	if hasAny && len(trimmed) != 1 {
		errs = multierror.Append(errs, fmt.Errorf("service filter: cannot specify extra keys when using %q", "any"))
	}

	return trimmed, errs.ErrorOrNil()
}

// Accept allows us to check if a slice of health checks pass this filter.
func (f ServiceStatusFilter) Accept(checks []*api.HealthCheck) bool {
	// If the any filter is activated, pass everything.
	if f.any() {
		return true
	}

	// If ONLY the passing filter is activated, ensure all checks are passing and
	// return. If more than one filter is given (like "passing, unknown"), then
	// this conditional will not fire.
	if f.passingOnly() {
		for _, check := range checks {
			if check.Status != HealthPassing {
				return false
			}
		}
		return true
	}

	// Iterate over each status and see if ANY of the checks have that status.
	for _, status := range f {
		for _, check := range checks {
			if status == check.Status {
				return true
			}
		}
	}

	return false
}

// any is a helper method to determine if this is an "any" service status
// filter. If "any" was given, it must be the only item in the list.
func (f ServiceStatusFilter) any() bool {
	return len(f) == 1 && f[0] == HealthAny
}

// onlyPassing returns true if the filter contains only a "passing" service
// status filter.
//
// NOTE: If there is more than one filter, this will return false, even if one
// of the filters is "passing". This is because "passingOnly" is used to alter
// the type of query made to Consul.
func (f ServiceStatusFilter) passingOnly() bool {
	return len(f) == 1 && f[0] == HealthPassing
}

// ServiceTags is a slice of tags assigned to a Service
type ServiceTags []string

// Contains returns true if the tags exists in the ServiceTags slice.
func (t ServiceTags) Contains(s string) bool {
	for _, v := range t {
		if v == s {
			return true
		}
	}
	return false
}

// HealthServiceList is a sortable slice of Service
type HealthServiceList []*HealthService

// Len, Swap, and Less are used to implement the sort.Sort interface.
func (s HealthServiceList) Len() int      { return len(s) }
func (s HealthServiceList) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s HealthServiceList) Less(i, j int) bool {
	if s[i].Node < s[j].Node {
		return true
	} else if s[i].Node == s[j].Node {
		return s[i].ID <= s[j].ID
	}
	return false
}
<|endoftext|>


<commit_before>package core

import (
	"context"
	"encoding/hex"
	"encoding/json"
	"expvar"
	"net/http"
	"net/url"
	"strings"
	"time"

	"chain/core/fetch"
	"chain/core/leader"
	"chain/core/mockhsm"
	"chain/core/txdb"
	"chain/crypto/ed25519"
	"chain/crypto/ed25519/chainkd"
	"chain/database/pg"
	"chain/database/sql"
	"chain/errors"
	"chain/log"
	"chain/net/http/httpjson"
	"chain/net/rpc"
	"chain/protocol"
	"chain/protocol/bc"
	"chain/protocol/state"
)

var (
	errAlreadyConfigured = errors.New("core is already configured; must reset first")
	errUnconfigured      = errors.New("core is not configured")
	errBadGenerator      = errors.New("generator returned an unsuccessful response")
	errBadBlockPub       = errors.New("supplied block pub key is invalid")
	errNoClientTokens    = errors.New("cannot enable client auth without client access tokens")
	errBadSignerURL      = errors.New("block signer URL is invalid")
	errBadSignerPubkey   = errors.New("block signer pubkey is invalid")
	errBadQuorum         = errors.New("quorum must be greater than 0 if there are signers")
	// errProdReset is returned when reset is called on a
	// production system.
	errProdReset = errors.New("reset called on production system")
)

// reserved mockhsm key alias
const (
	networkRPCVersion = 1
	autoBlockKeyAlias = "_CHAIN_CORE_AUTO_BLOCK_KEY"
)

func isProduction() bool {
	bt := expvar.Get("buildtag")
	return bt != nil && bt.String() != `"dev"`
}

// Schema returns DDL for the entire Chain Core Postgres schema.
func Schema() string {
	return files["schema.sql"]
}

func (h *Handler) reset(ctx context.Context, req struct {
	Everything bool `json:"everything"`
}) error {
	if isProduction() {
		return errors.Wrap(errProdReset)
	}

	dataToReset := "blockchain"
	if req.Everything {
		dataToReset = "everything"
	}

	closeConnOK(httpjson.ResponseWriter(ctx), httpjson.Request(ctx))
	execSelf(dataToReset)
	panic("unreached")
}

func (h *Handler) info(ctx context.Context) (map[string]interface{}, error) {
	if h.Config == nil {
		// never configured
		return map[string]interface{}{
			"is_configured": false,
		}, nil
	}
	if leader.IsLeading() {
		return h.leaderInfo(ctx)
	} else {
		var resp map[string]interface{}
		err := h.forwardToLeader(ctx, "/info", nil, &resp)
		return resp, err
	}
}

func (h *Handler) leaderInfo(ctx context.Context) (map[string]interface{}, error) {
	localHeight := h.Chain.Height()
	var (
		generatorHeight  *uint64
		generatorFetched *time.Time
	)
	if h.Config.IsGenerator {
		now := time.Now()
		generatorHeight = &localHeight
		generatorFetched = &now
	} else {
		fetchHeight, fetchTime := fetch.GeneratorHeight()

		// Because everything is asynchronous, it's possible for the localHeight to
		// be higher than our cached generator height. In that case, display the
		// local height as the generator height.
		if localHeight > fetchHeight {
			fetchHeight = localHeight
		}

		// fetchTime might be the zero time if we're having trouble connecting
		// to the remote generator. Only set the height & time if we have it.
		// The dashboard will handle nulls correctly.
		if !fetchTime.IsZero() {
			generatorHeight, generatorFetched = &fetchHeight, &fetchTime
		}
	}

	buildCommit := json.RawMessage(expvar.Get("buildcommit").String())
	buildDate := json.RawMessage(expvar.Get("builddate").String())

	return map[string]interface{}{
		"is_configured":                     true,
		"configured_at":                     h.Config.ConfiguredAt,
		"is_signer":                         h.Config.IsSigner,
		"is_generator":                      h.Config.IsGenerator,
		"generator_url":                     h.Config.GeneratorURL,
		"generator_access_token":            obfuscateTokenSecret(h.Config.GeneratorAccessToken),
		"blockchain_id":                     h.Config.BlockchainID,
		"block_height":                      localHeight,
		"generator_block_height":            generatorHeight,
		"generator_block_height_fetched_at": generatorFetched,
		"is_production":                     isProduction(),
		"network_rpc_version":               networkRPCVersion,
		"build_commit":                      &buildCommit,
		"build_date":                        &buildDate,
	}, nil
}

// Configure configures the core by writing to the database.
// If running in a cored process,
// the caller must ensure that the new configuration is properly reloaded,
// for example by restarting the process.
//
// If c.IsSigner is true, Configure generates a new mockhsm keypair
// for signing blocks, and assigns it to c.BlockPub.
//
// If c.IsGenerator is true, Configure creates an initial block,
// saves it, and assigns its hash to c.BlockchainID.
// Otherwise, c.IsGenerator is false, and Configure makes a test request
// to GeneratorURL to detect simple configuration mistakes.
func Configure(ctx context.Context, db pg.DB, c *Config) error {
	var err error
	if !c.IsGenerator {
		err = tryGenerator(
			ctx,
			c.GeneratorURL,
			c.GeneratorAccessToken,
			c.BlockchainID.String(),
		)
		if err != nil {
			return err
		}
	}

	var signingKeys []ed25519.PublicKey
	if c.IsSigner {
		var blockPub ed25519.PublicKey
		if c.BlockPub == "" {
			hsm := mockhsm.New(db)
			corePub, created, err := hsm.GetOrCreate(ctx, autoBlockKeyAlias)
			if err != nil {
				return err
			}
			blockPub = corePub.Pub
			blockPubStr := hex.EncodeToString(blockPub)
			if created {
				log.Messagef(ctx, "Generated new block-signing key %s\n", blockPubStr)
			} else {
				log.Messagef(ctx, "Using block-signing key %s\n", blockPubStr)
			}
			c.BlockPub = blockPubStr
		} else {
			blockPub, err = hex.DecodeString(c.BlockPub)
			if err != nil {
				return err
			}
		}
		signingKeys = append(signingKeys, blockPub)
	}

	if c.IsGenerator {
		for _, signer := range c.Signers {
			_, err = url.Parse(signer.URL)
			if err != nil {
				return errors.Wrap(errBadSignerURL, err.Error())
			}
			signingKey, err := chainkd.NewEd25519PublicKey(signer.Pubkey)
			if err != nil {
				return errors.Wrap(errBadSignerPubkey, err.Error())
			}
			signingKeys = append(signingKeys, signingKey)
		}

		if c.Quorum == 0 && len(signingKeys) > 0 {
			return errors.Wrap(errBadQuorum)
		}

		block, err := protocol.NewInitialBlock(signingKeys, c.Quorum, time.Now())
		if err != nil {
			return err
		}
		store, pool := txdb.New(db.(*sql.DB))
		chain, err := protocol.NewChain(ctx, store, pool, nil)
		if err != nil {
			return err
		}

		err = chain.CommitBlock(ctx, block, state.Empty())
		if err != nil {
			return err
		}

		c.BlockchainID = block.Hash()
		chain.MaxIssuanceWindow = c.MaxIssuanceWindow
	}

	var blockSignerData []byte
	if len(c.Signers) > 0 {
		blockSignerData, err = json.Marshal(c.Signers)
		if err != nil {
			return errors.Wrap(err)
		}
	}

	// TODO(tessr): rename block_xpub column
	const q = `
		INSERT INTO config (is_signer, block_xpub, is_generator,
			blockchain_id, generator_url, generator_access_token,
			remote_block_signers, max_issuance_window_ms, configured_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NOW())
	`
	_, err = db.Exec(
		ctx,
		q,
		c.IsSigner,
		c.BlockPub,
		c.IsGenerator,
		c.BlockchainID,
		c.GeneratorURL,
		c.GeneratorAccessToken,
		blockSignerData,
		bc.DurationMillis(c.MaxIssuanceWindow),
	)
	return err
}

func (h *Handler) configure(ctx context.Context, x *Config) error {
	if h.Config != nil {
		return errAlreadyConfigured
	}

	if x.IsGenerator && x.MaxIssuanceWindow == 0 {
		x.MaxIssuanceWindow = 24 * time.Hour
	}

	err := Configure(ctx, pg.FromContext(ctx), x)
	if err != nil {
		return err
	}

	closeConnOK(httpjson.ResponseWriter(ctx), httpjson.Request(ctx))
	execSelf("")
	panic("unreached")
}

// LoadConfig loads the stored configuration, if any, from the database.
func LoadConfig(ctx context.Context, db pg.DB) (*Config, error) {
	const q = `
			SELECT is_signer, is_generator,
			blockchain_id, generator_url, generator_access_token, block_xpub,
			remote_block_signers, max_issuance_window_ms, configured_at
			FROM config
		`

	c := new(Config)
	var (
		blockSignerData []byte
		miw             int64
	)
	err := db.QueryRow(ctx, q).Scan(
		&c.IsSigner,
		&c.IsGenerator,
		&c.BlockchainID,
		&c.GeneratorURL,
		&c.GeneratorAccessToken,
		&c.BlockPub,
		&blockSignerData,
		&miw,
		&c.ConfiguredAt,
	)
	if err == sql.ErrNoRows {
		return nil, nil
	} else if err != nil {
		return nil, errors.Wrap(err, "fetching Core config")
	}

	if len(blockSignerData) > 0 {
		err = json.Unmarshal(blockSignerData, &c.Signers)
		if err != nil {
			return nil, errors.Wrap(err)
		}
	}

	c.MaxIssuanceWindow = time.Duration(miw) * time.Millisecond
	return c, nil
}

func tryGenerator(ctx context.Context, url, accessToken, blockchainID string) error {
	client := &rpc.Client{
		BaseURL:      url,
		AccessToken:  accessToken,
		BlockchainID: blockchainID,
	}
	var x struct {
		BlockHeight uint64 `json:"block_height"`
	}
	err := client.Call(ctx, "/rpc/block-height", nil, &x)
	if err != nil {
		return errors.Wrap(errBadGenerator, err.Error())
	}

	if x.BlockHeight < 1 {
		return errBadGenerator
	}

	return nil
}

func closeConnOK(w http.ResponseWriter, req *http.Request) {
	w.Header().Add("Connection", "close")
	w.WriteHeader(http.StatusNoContent)

	hijacker, ok := w.(http.Hijacker)
	if !ok {
		log.Messagef(req.Context(), "no hijacker")
		return
	}
	conn, buf, err := hijacker.Hijack()
	if err != nil {
		log.Messagef(req.Context(), "could not hijack connection: %s\n", err)
		return
	}
	err = buf.Flush()
	if err != nil {
		log.Messagef(req.Context(), "could not flush connection buffer: %s\n", err)
	}
	err = conn.Close()
	if err != nil {
		log.Messagef(req.Context(), "could not close connection: %s\n", err)
	}
}

func obfuscateTokenSecret(token string) string {
	toks := strings.SplitN(token, ":", 2)
	var res string
	if len(toks) > 0 {
		res += toks[0]
	}
	if len(toks) > 1 {
		res += ":********"
	}
	return res
}
<commit_msg>core: add health to /info response<commit_after>package core

import (
	"context"
	"encoding/hex"
	"encoding/json"
	"expvar"
	"net/http"
	"net/url"
	"strings"
	"time"

	"chain/core/fetch"
	"chain/core/leader"
	"chain/core/mockhsm"
	"chain/core/txdb"
	"chain/crypto/ed25519"
	"chain/crypto/ed25519/chainkd"
	"chain/database/pg"
	"chain/database/sql"
	"chain/errors"
	"chain/log"
	"chain/net/http/httpjson"
	"chain/net/rpc"
	"chain/protocol"
	"chain/protocol/bc"
	"chain/protocol/state"
)

var (
	errAlreadyConfigured = errors.New("core is already configured; must reset first")
	errUnconfigured      = errors.New("core is not configured")
	errBadGenerator      = errors.New("generator returned an unsuccessful response")
	errBadBlockPub       = errors.New("supplied block pub key is invalid")
	errNoClientTokens    = errors.New("cannot enable client auth without client access tokens")
	errBadSignerURL      = errors.New("block signer URL is invalid")
	errBadSignerPubkey   = errors.New("block signer pubkey is invalid")
	errBadQuorum         = errors.New("quorum must be greater than 0 if there are signers")
	// errProdReset is returned when reset is called on a
	// production system.
	errProdReset = errors.New("reset called on production system")
)

// reserved mockhsm key alias
const (
	networkRPCVersion = 1
	autoBlockKeyAlias = "_CHAIN_CORE_AUTO_BLOCK_KEY"
)

func isProduction() bool {
	bt := expvar.Get("buildtag")
	return bt != nil && bt.String() != `"dev"`
}

// Schema returns DDL for the entire Chain Core Postgres schema.
func Schema() string {
	return files["schema.sql"]
}

func (h *Handler) reset(ctx context.Context, req struct {
	Everything bool `json:"everything"`
}) error {
	if isProduction() {
		return errors.Wrap(errProdReset)
	}

	dataToReset := "blockchain"
	if req.Everything {
		dataToReset = "everything"
	}

	closeConnOK(httpjson.ResponseWriter(ctx), httpjson.Request(ctx))
	execSelf(dataToReset)
	panic("unreached")
}

func (h *Handler) info(ctx context.Context) (map[string]interface{}, error) {
	if h.Config == nil {
		// never configured
		return map[string]interface{}{
			"is_configured": false,
		}, nil
	}
	if leader.IsLeading() {
		return h.leaderInfo(ctx)
	} else {
		var resp map[string]interface{}
		err := h.forwardToLeader(ctx, "/info", nil, &resp)
		return resp, err
	}
}

func (h *Handler) leaderInfo(ctx context.Context) (map[string]interface{}, error) {
	localHeight := h.Chain.Height()
	var (
		generatorHeight  *uint64
		generatorFetched *time.Time
	)
	if h.Config.IsGenerator {
		now := time.Now()
		generatorHeight = &localHeight
		generatorFetched = &now
	} else {
		fetchHeight, fetchTime := fetch.GeneratorHeight()

		// Because everything is asynchronous, it's possible for the localHeight to
		// be higher than our cached generator height. In that case, display the
		// local height as the generator height.
		if localHeight > fetchHeight {
			fetchHeight = localHeight
		}

		// fetchTime might be the zero time if we're having trouble connecting
		// to the remote generator. Only set the height & time if we have it.
		// The dashboard will handle nulls correctly.
		if !fetchTime.IsZero() {
			generatorHeight, generatorFetched = &fetchHeight, &fetchTime
		}
	}

	buildCommit := json.RawMessage(expvar.Get("buildcommit").String())
	buildDate := json.RawMessage(expvar.Get("builddate").String())

	return map[string]interface{}{
		"is_configured":                     true,
		"configured_at":                     h.Config.ConfiguredAt,
		"is_signer":                         h.Config.IsSigner,
		"is_generator":                      h.Config.IsGenerator,
		"generator_url":                     h.Config.GeneratorURL,
		"generator_access_token":            obfuscateTokenSecret(h.Config.GeneratorAccessToken),
		"blockchain_id":                     h.Config.BlockchainID,
		"block_height":                      localHeight,
		"generator_block_height":            generatorHeight,
		"generator_block_height_fetched_at": generatorFetched,
		"is_production":                     isProduction(),
		"network_rpc_version":               networkRPCVersion,
		"build_commit":                      &buildCommit,
		"build_date":                        &buildDate,
		"health":                            h.health(),
	}, nil
}

// Configure configures the core by writing to the database.
// If running in a cored process,
// the caller must ensure that the new configuration is properly reloaded,
// for example by restarting the process.
//
// If c.IsSigner is true, Configure generates a new mockhsm keypair
// for signing blocks, and assigns it to c.BlockPub.
//
// If c.IsGenerator is true, Configure creates an initial block,
// saves it, and assigns its hash to c.BlockchainID.
// Otherwise, c.IsGenerator is false, and Configure makes a test request
// to GeneratorURL to detect simple configuration mistakes.
func Configure(ctx context.Context, db pg.DB, c *Config) error {
	var err error
	if !c.IsGenerator {
		err = tryGenerator(
			ctx,
			c.GeneratorURL,
			c.GeneratorAccessToken,
			c.BlockchainID.String(),
		)
		if err != nil {
			return err
		}
	}

	var signingKeys []ed25519.PublicKey
	if c.IsSigner {
		var blockPub ed25519.PublicKey
		if c.BlockPub == "" {
			hsm := mockhsm.New(db)
			corePub, created, err := hsm.GetOrCreate(ctx, autoBlockKeyAlias)
			if err != nil {
				return err
			}
			blockPub = corePub.Pub
			blockPubStr := hex.EncodeToString(blockPub)
			if created {
				log.Messagef(ctx, "Generated new block-signing key %s\n", blockPubStr)
			} else {
				log.Messagef(ctx, "Using block-signing key %s\n", blockPubStr)
			}
			c.BlockPub = blockPubStr
		} else {
			blockPub, err = hex.DecodeString(c.BlockPub)
			if err != nil {
				return err
			}
		}
		signingKeys = append(signingKeys, blockPub)
	}

	if c.IsGenerator {
		for _, signer := range c.Signers {
			_, err = url.Parse(signer.URL)
			if err != nil {
				return errors.Wrap(errBadSignerURL, err.Error())
			}
			signingKey, err := chainkd.NewEd25519PublicKey(signer.Pubkey)
			if err != nil {
				return errors.Wrap(errBadSignerPubkey, err.Error())
			}
			signingKeys = append(signingKeys, signingKey)
		}

		if c.Quorum == 0 && len(signingKeys) > 0 {
			return errors.Wrap(errBadQuorum)
		}

		block, err := protocol.NewInitialBlock(signingKeys, c.Quorum, time.Now())
		if err != nil {
			return err
		}
		store, pool := txdb.New(db.(*sql.DB))
		chain, err := protocol.NewChain(ctx, store, pool, nil)
		if err != nil {
			return err
		}

		err = chain.CommitBlock(ctx, block, state.Empty())
		if err != nil {
			return err
		}

		c.BlockchainID = block.Hash()
		chain.MaxIssuanceWindow = c.MaxIssuanceWindow
	}

	var blockSignerData []byte
	if len(c.Signers) > 0 {
		blockSignerData, err = json.Marshal(c.Signers)
		if err != nil {
			return errors.Wrap(err)
		}
	}

	// TODO(tessr): rename block_xpub column
	const q = `
		INSERT INTO config (is_signer, block_xpub, is_generator,
			blockchain_id, generator_url, generator_access_token,
			remote_block_signers, max_issuance_window_ms, configured_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NOW())
	`
	_, err = db.Exec(
		ctx,
		q,
		c.IsSigner,
		c.BlockPub,
		c.IsGenerator,
		c.BlockchainID,
		c.GeneratorURL,
		c.GeneratorAccessToken,
		blockSignerData,
		bc.DurationMillis(c.MaxIssuanceWindow),
	)
	return err
}

func (h *Handler) configure(ctx context.Context, x *Config) error {
	if h.Config != nil {
		return errAlreadyConfigured
	}

	if x.IsGenerator && x.MaxIssuanceWindow == 0 {
		x.MaxIssuanceWindow = 24 * time.Hour
	}

	err := Configure(ctx, pg.FromContext(ctx), x)
	if err != nil {
		return err
	}

	closeConnOK(httpjson.ResponseWriter(ctx), httpjson.Request(ctx))
	execSelf("")
	panic("unreached")
}

// LoadConfig loads the stored configuration, if any, from the database.
func LoadConfig(ctx context.Context, db pg.DB) (*Config, error) {
	const q = `
			SELECT is_signer, is_generator,
			blockchain_id, generator_url, generator_access_token, block_xpub,
			remote_block_signers, max_issuance_window_ms, configured_at
			FROM config
		`

	c := new(Config)
	var (
		blockSignerData []byte
		miw             int64
	)
	err := db.QueryRow(ctx, q).Scan(
		&c.IsSigner,
		&c.IsGenerator,
		&c.BlockchainID,
		&c.GeneratorURL,
		&c.GeneratorAccessToken,
		&c.BlockPub,
		&blockSignerData,
		&miw,
		&c.ConfiguredAt,
	)
	if err == sql.ErrNoRows {
		return nil, nil
	} else if err != nil {
		return nil, errors.Wrap(err, "fetching Core config")
	}

	if len(blockSignerData) > 0 {
		err = json.Unmarshal(blockSignerData, &c.Signers)
		if err != nil {
			return nil, errors.Wrap(err)
		}
	}

	c.MaxIssuanceWindow = time.Duration(miw) * time.Millisecond
	return c, nil
}

func tryGenerator(ctx context.Context, url, accessToken, blockchainID string) error {
	client := &rpc.Client{
		BaseURL:      url,
		AccessToken:  accessToken,
		BlockchainID: blockchainID,
	}
	var x struct {
		BlockHeight uint64 `json:"block_height"`
	}
	err := client.Call(ctx, "/rpc/block-height", nil, &x)
	if err != nil {
		return errors.Wrap(errBadGenerator, err.Error())
	}

	if x.BlockHeight < 1 {
		return errBadGenerator
	}

	return nil
}

func closeConnOK(w http.ResponseWriter, req *http.Request) {
	w.Header().Add("Connection", "close")
	w.WriteHeader(http.StatusNoContent)

	hijacker, ok := w.(http.Hijacker)
	if !ok {
		log.Messagef(req.Context(), "no hijacker")
		return
	}
	conn, buf, err := hijacker.Hijack()
	if err != nil {
		log.Messagef(req.Context(), "could not hijack connection: %s\n", err)
		return
	}
	err = buf.Flush()
	if err != nil {
		log.Messagef(req.Context(), "could not flush connection buffer: %s\n", err)
	}
	err = conn.Close()
	if err != nil {
		log.Messagef(req.Context(), "could not close connection: %s\n", err)
	}
}

func obfuscateTokenSecret(token string) string {
	toks := strings.SplitN(token, ":", 2)
	var res string
	if len(toks) > 0 {
		res += toks[0]
	}
	if len(toks) > 1 {
		res += ":********"
	}
	return res
}
<|endoftext|>