<commit_before>package spec

import (
	"container/set"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"regexp"
	"strings"
)

var (
	reName          = regexp.MustCompile(`[Nn]ame:\s+([^\n]+)`)
	reVersion       = regexp.MustCompile(`[Vv]ersion:\s+([^\n]+)`)
	reRelease       = regexp.MustCompile(`Release:\s+([^\n]+)`)
	reSummary       = regexp.MustCompile(`Summary:\s+([^\n]+)`)
	reRequires      = regexp.MustCompile(`[^d]Requires:\s+([^\n]+)`)
	reBuildRequires = regexp.MustCompile(`BuildRequires:\s+([^\n]+)`)
	reSubpackage    = regexp.MustCompile(`%package\s+([^\n]+)`)
	reSource        = regexp.MustCompile(`Source(\d{1,3}):\s+([^\n]+)`)
	rePatch         = regexp.MustCompile(`Patch(\d{1,3}):\s+([^\n]+)`)
)

var (
	ErrTooFewSubs  = errors.New("Too few submatches.")
	ErrTooManySubs = errors.New("Too many submatches.")
	ErrNoSubs      = errors.New("No submatches found.")
)

type SpecFile struct {
	raw    []byte
	macros MacroSet
}

func (s *SpecFile) findSubmatch(re *regexp.Regexp, nmatches int) ([]byte, error) {
	matches := re.FindSubmatch(s.raw)
	if matches != nil {
		if len(matches) > nmatches {
			return nil, ErrTooManySubs
		} else if len(matches) < nmatches {
			return nil, ErrTooFewSubs
		}
	} else {
		return nil, ErrNoSubs
	}
	return matches[nmatches-1], nil
}

func (s *SpecFile) findStringSubmatches(re *regexp.Regexp, nmatches int) ([]string, error) {
	var err error
	var matches = make([]string, 0)

	mm := re.FindAllStringSubmatch(string(s.raw), -1)
	if mm != nil {
		for _, match := range mm {
			if len(match) == nmatches {
				matches = append(matches, match[nmatches-1])
			} else if len(match) > nmatches {
				err = errors.New("too many submatches")
			} else if len(match) < nmatches {
				err = errors.New("too few submatches")
			}
		}
	} else {
		err = errors.New("no matches found")
	}

	return matches, err
}

/*
This function performs substitutions on byte arrays where a macro is placed,
in the spec file.

For example, if you see "%{name}" in a string somewhere, this function will
return you a new byte array with all occurrences of "%{name}" replaced with the
value returned by calling `*SpecFile.Name()`.

The first argument to this function is the byte array you would like to apply
the subtitutions to. The second argument, `subs`, is a string-string mapping
where the keys are the macro to search for, and the value is what to replace
the macro with.
*/
func (s *SpecFile) macroSub(src []byte, macros map[string]RPMMacro) ([]byte, error) {
	dest := src
	for _, macro := range macros {
		re, err := regexp.Compile(fmt.Sprintf("%%\\{%s\\}", macro.Name))
		if err != nil {
			return nil, err
		}

		dest = re.ReplaceAll(dest, []byte(macro.Value))
	}
	return dest, nil
}

func (s *SpecFile) BuildRequires() []string {
	breqs, err := s.findStringSubmatches(reBuildRequires, 2)
	if err != nil {
		return nil
	}

	buildreqs := set.New()
	for _, i := range breqs {
		b, err := s.macroSub([]byte(i), s.macros)
		if err != nil {
			panic(err)
		}

		for _, i := range strings.Split(string(b), ",") {
			_ = buildreqs.Add(strings.Trim(i, " "))
		}
	}

	return buildreqs.Members()
}

func (s *SpecFile) Requires() []string {
	reqs, err := s.findStringSubmatches(reRequires, 2)
	if err != nil {
		return nil
	}

	requires := set.New()
	for _, r := range reqs {
		b, err := s.macroSub([]byte(r), s.macros)
		if err != nil {
			panic(err)
		}

		// The next little bit of code, splits up the parsed "Require:" line,
		// on commas, and then trims the whitespace off the ends of the
		// resulting substrings.
		//
		// In the even there are no comma-separated requirements, this clause
		// won't "damage" anything.
		for _, i := range strings.Split(string(b), ",") {
			_ = requires.Add(strings.Trim(i, " "))
		}
	}

	return requires.Members()
}

func (s *SpecFile) Patches() map[string]string {
	patches := make(map[string]string, 0)
	matches := rePatch.FindAllStringSubmatch(string(s.raw), -1)
	if matches != nil {
		for _, match := range matches {
			if len(match) == 3 {
				p, err := s.macroSub([]byte(match[2]), s.macros)
				if err != nil {
					panic(err)
				}

				patches[match[1]] = string(p)
			}
		}
		return patches
	}
	return nil
}

func (s *SpecFile) Sources() map[string]string {
	matches := reSource.FindAllStringSubmatch(string(s.raw), -1)
	if matches != nil {
		sources := make(map[string]string, 0)
		for _, match := range matches {
			if len(match) == 3 {
				src, err := s.macroSub([]byte(match[2]), s.macros)
				if err != nil {
					panic(err)
				}

				sources[match[1]] = string(src)
			}
		}
		return sources
	}
	return nil
}

func (s *SpecFile) Subpackages() []string {
	matches := reSubpackage.FindAllStringSubmatch(string(s.raw), -1)
	if matches != nil {
		subpackages := make([]string, 0)
		for _, match := range matches {
			if len(match) == 2 {
				subpackages = append(subpackages, match[1])
			}
		}
		return subpackages
	}
	return nil
}

/*
Returns the package summary from the spec file.

A zero-length string indicates the spec file failed to be parsed, and may
indicated a malformed spec.
*/
func (s *SpecFile) Summary() string {
	if match, err := s.findSubmatch(reSummary, 2); err != nil {
		return ""
	} else {
		return string(match)
	}
}

/*
Returns the release of the package in the spec file. The "release" can also
be referred to as the "build number", and sometimes has an additional "dist"
tag attached to it. If there is a "dist" macro in the release string,
it will be stripped.

A zero-length string indicates the spec file failed to be parsed, and may
indicated a malformed spec.
*/
func (s *SpecFile) Release() string {
	var rel []byte
	var err error
	var match []byte

	if match, err = s.findSubmatch(reRelease, 2); err != nil {
		return ""
	} else {
		subs := make(map[string]RPMMacro)
		subs["\\??dist"] = NewMacro("\\??dist", "", false)

		rel, err = s.macroSub(match, subs)
		if err != nil {
			return ""
		}
	}

	return string(rel)
}

/*
Returns the version of the package in the spec file. A zero-length string
indicates that the spec file failed to be parsed, and may indicate a malformed
spec.
*/
func (s *SpecFile) Version() string {
	if match, err := s.findSubmatch(reVersion, 2); err != nil {
		return ""
	} else {
		return string(match)
	}
}

/*
Returns the name of the package in the spec file. A zero-length string
indicates that the spec file failed to be parsed, and may indicate a malformed
spec.
*/
func (s *SpecFile) Name() string {
	if match, err := s.findSubmatch(reName, 2); err != nil {
		return ""
	} else {
		return string(match)
	}
}

/*
Raw returns the byte slice containing the spec file data that was provided to
one of the Parse* functions.
*/
func (s *SpecFile) Raw() []byte {
	return s.raw
}

func Parse(data []byte) (*SpecFile, error) {
	var spec = SpecFile{raw: data}

	// Find any declared macros, from within the spec file.
	mdefs := parseMacroDefinitions(data)

	// Check a few of the required macros, and if they have not been
	// defined by a "%define" statement, then parse them out from elsewhere
	// in the spec file.
	if _, ok := mdefs["name"]; !ok {
		mdefs["name"] = NewMacro("name", spec.Name(), false)
	}

	if _, ok := mdefs["version"]; !ok {
		mdefs["version"] = NewMacro("version", spec.Version(), false)
	}

	if _, ok := mdefs["release"]; !ok {
		mdefs["release"] = NewMacro("release", spec.Release(), false)
	}

	spec.macros = mdefs
	return &spec, nil
}

func ParseString(data string) (*SpecFile, error) {
	return Parse([]byte(data))
}

func ParseReader(r io.Reader) (*SpecFile, error) {
	data, err := ioutil.ReadAll(r)
	if err != nil {
		return nil, err
	} else if len(data) == 0 {
		return nil, errors.New("error: zero-length data, nothing to parse")
	}

	return Parse(data)
}
<commit_msg>rpm/spec: Removed dep "container/set" and inlined set implementation<commit_after>package spec

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"regexp"
	"strings"
)

var (
	reName          = regexp.MustCompile(`[Nn]ame:\s+([^\n]+)`)
	reVersion       = regexp.MustCompile(`[Vv]ersion:\s+([^\n]+)`)
	reRelease       = regexp.MustCompile(`Release:\s+([^\n]+)`)
	reSummary       = regexp.MustCompile(`Summary:\s+([^\n]+)`)
	reRequires      = regexp.MustCompile(`[^d]Requires:\s+([^\n]+)`)
	reBuildRequires = regexp.MustCompile(`BuildRequires:\s+([^\n]+)`)
	reSubpackage    = regexp.MustCompile(`%package\s+([^\n]+)`)
	reSource        = regexp.MustCompile(`Source(\d{1,3}):\s+([^\n]+)`)
	rePatch         = regexp.MustCompile(`Patch(\d{1,3}):\s+([^\n]+)`)
)

var (
	ErrTooFewSubs  = errors.New("Too few submatches.")
	ErrTooManySubs = errors.New("Too many submatches.")
	ErrNoSubs      = errors.New("No submatches found.")
)

type SpecFile struct {
	raw    []byte
	macros MacroSet
}

func (s *SpecFile) findSubmatch(re *regexp.Regexp, nmatches int) ([]byte, error) {
	matches := re.FindSubmatch(s.raw)
	if matches != nil {
		if len(matches) > nmatches {
			return nil, ErrTooManySubs
		} else if len(matches) < nmatches {
			return nil, ErrTooFewSubs
		}
	} else {
		return nil, ErrNoSubs
	}
	return matches[nmatches-1], nil
}

func (s *SpecFile) findStringSubmatches(re *regexp.Regexp, nmatches int) ([]string, error) {
	var err error
	var matches = make([]string, 0)

	mm := re.FindAllStringSubmatch(string(s.raw), -1)
	if mm != nil {
		for _, match := range mm {
			if len(match) == nmatches {
				matches = append(matches, match[nmatches-1])
			} else if len(match) > nmatches {
				err = errors.New("too many submatches")
			} else if len(match) < nmatches {
				err = errors.New("too few submatches")
			}
		}
	} else {
		err = errors.New("no matches found")
	}

	return matches, err
}

/*
This function performs substitutions on byte arrays where a macro is placed,
in the spec file.

For example, if you see "%{name}" in a string somewhere, this function will
return you a new byte array with all occurrences of "%{name}" replaced with the
value returned by calling `*SpecFile.Name()`.

The first argument to this function is the byte array you would like to apply
the subtitutions to. The second argument, `subs`, is a string-string mapping
where the keys are the macro to search for, and the value is what to replace
the macro with.
*/
func (s *SpecFile) macroSub(src []byte, macros map[string]RPMMacro) ([]byte, error) {
	dest := src
	for _, macro := range macros {
		re, err := regexp.Compile(fmt.Sprintf("%%\\{%s\\}", macro.Name))
		if err != nil {
			return nil, err
		}

		dest = re.ReplaceAll(dest, []byte(macro.Value))
	}
	return dest, nil
}

func (s *SpecFile) BuildRequires() []string {
	breqs, err := s.findStringSubmatches(reBuildRequires, 2)
	if err != nil {
		return nil
	}

	buildreqs := make([]string, 0)
	brs := make(map[string]struct{})
	for _, i := range breqs {
		b, err := s.macroSub([]byte(i), s.macros)
		if err != nil {
			panic(err)
		}

		for _, i := range strings.Split(string(b), ",") {
			i = strings.Trim(i, " ")
			if _, ok := brs[i]; !ok {
				brs[i] = struct{}{}
				buildreqs = append(buildreqs, i)
			}
		}
	}

	return buildreqs
}

func (s *SpecFile) Requires() []string {
	reqs, err := s.findStringSubmatches(reRequires, 2)
	if err != nil {
		return nil
	}

	requires := make([]string, 0)
	rqs := make(map[string]struct{})
	for _, r := range reqs {
		b, err := s.macroSub([]byte(r), s.macros)
		if err != nil {
			panic(err)
		}

		// The next little bit of code, splits up the parsed "Require:" line,
		// on commas, and then trims the whitespace off the ends of the
		// resulting substrings.
		//
		// In the even there are no comma-separated requirements, this clause
		// won't "damage" anything.
		for _, i := range strings.Split(string(b), ",") {
			i = strings.Trim(i, " ")
			if _, ok := rqs[i]; !ok {
				requires = append(requires, strings.Trim(i, " "))
				rqs[i] = struct{}{}
			}
		}
	}

	return requires
}

func (s *SpecFile) Patches() map[string]string {
	patches := make(map[string]string, 0)
	matches := rePatch.FindAllStringSubmatch(string(s.raw), -1)
	if matches != nil {
		for _, match := range matches {
			if len(match) == 3 {
				p, err := s.macroSub([]byte(match[2]), s.macros)
				if err != nil {
					panic(err)
				}

				patches[match[1]] = string(p)
			}
		}
		return patches
	}
	return nil
}

func (s *SpecFile) Sources() map[string]string {
	matches := reSource.FindAllStringSubmatch(string(s.raw), -1)
	if matches != nil {
		sources := make(map[string]string, 0)
		for _, match := range matches {
			if len(match) == 3 {
				src, err := s.macroSub([]byte(match[2]), s.macros)
				if err != nil {
					panic(err)
				}

				sources[match[1]] = string(src)
			}
		}
		return sources
	}
	return nil
}

func (s *SpecFile) Subpackages() []string {
	matches := reSubpackage.FindAllStringSubmatch(string(s.raw), -1)
	if matches != nil {
		subpackages := make([]string, 0)
		for _, match := range matches {
			if len(match) == 2 {
				subpackages = append(subpackages, match[1])
			}
		}
		return subpackages
	}
	return nil
}

/*
Returns the package summary from the spec file.

A zero-length string indicates the spec file failed to be parsed, and may
indicated a malformed spec.
*/
func (s *SpecFile) Summary() string {
	if match, err := s.findSubmatch(reSummary, 2); err != nil {
		return ""
	} else {
		return string(match)
	}
}

/*
Returns the release of the package in the spec file. The "release" can also
be referred to as the "build number", and sometimes has an additional "dist"
tag attached to it. If there is a "dist" macro in the release string,
it will be stripped.

A zero-length string indicates the spec file failed to be parsed, and may
indicated a malformed spec.
*/
func (s *SpecFile) Release() string {
	var rel []byte
	var err error
	var match []byte

	if match, err = s.findSubmatch(reRelease, 2); err != nil {
		return ""
	} else {
		subs := make(map[string]RPMMacro)
		subs["\\??dist"] = NewMacro("\\??dist", "", false)

		rel, err = s.macroSub(match, subs)
		if err != nil {
			return ""
		}
	}

	return string(rel)
}

/*
Returns the version of the package in the spec file. A zero-length string
indicates that the spec file failed to be parsed, and may indicate a malformed
spec.
*/
func (s *SpecFile) Version() string {
	if match, err := s.findSubmatch(reVersion, 2); err != nil {
		return ""
	} else {
		return string(match)
	}
}

/*
Returns the name of the package in the spec file. A zero-length string
indicates that the spec file failed to be parsed, and may indicate a malformed
spec.
*/
func (s *SpecFile) Name() string {
	if match, err := s.findSubmatch(reName, 2); err != nil {
		return ""
	} else {
		return string(match)
	}
}

/*
Raw returns the byte slice containing the spec file data that was provided to
one of the Parse* functions.
*/
func (s *SpecFile) Raw() []byte {
	return s.raw
}

func Parse(data []byte) (*SpecFile, error) {
	var spec = SpecFile{raw: data}

	// Find any declared macros, from within the spec file.
	mdefs := parseMacroDefinitions(data)

	// Check a few of the required macros, and if they have not been
	// defined by a "%define" statement, then parse them out from elsewhere
	// in the spec file.
	if _, ok := mdefs["name"]; !ok {
		mdefs["name"] = NewMacro("name", spec.Name(), false)
	}

	if _, ok := mdefs["version"]; !ok {
		mdefs["version"] = NewMacro("version", spec.Version(), false)
	}

	if _, ok := mdefs["release"]; !ok {
		mdefs["release"] = NewMacro("release", spec.Release(), false)
	}

	spec.macros = mdefs
	return &spec, nil
}

func ParseString(data string) (*SpecFile, error) {
	return Parse([]byte(data))
}

func ParseReader(r io.Reader) (*SpecFile, error) {
	data, err := ioutil.ReadAll(r)
	if err != nil {
		return nil, err
	} else if len(data) == 0 {
		return nil, errors.New("error: zero-length data, nothing to parse")
	}

	return Parse(data)
}
<|endoftext|>


<commit_before>package cluster

import (
	"crypto/tls"
	"encoding/base64"
	"encoding/pem"
	"fmt"
	"net"
	"time"

	"github.com/pkg/errors"

	lxd "github.com/lxc/lxd/client"
	"github.com/lxc/lxd/lxd/cluster/request"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/instance/instancetype"
	"github.com/lxc/lxd/lxd/state"
	storagePools "github.com/lxc/lxd/lxd/storage"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	"github.com/lxc/lxd/shared/version"
)

// ErrCertificateExists indicates that a certificate already exists.
var ErrCertificateExists error = fmt.Errorf("Certificate already in trust store")

// Connect is a convenience around lxd.ConnectLXD that configures the client
// with the correct parameters for node-to-node communication.
//
// If 'notify' switch is true, then the user agent will be set to the special
// to the UserAgentNotifier value, which can be used in some cases to distinguish
// between a regular client request and an internal cluster request.
func Connect(address string, networkCert *shared.CertInfo, serverCert *shared.CertInfo, notify bool) (lxd.InstanceServer, error) {
	// Wait for a connection to the events API first for non-notify connections.
	if !notify {
		connected := false
		for i := 0; i < 20; i++ {
			listenersLock.Lock()
			_, ok := listeners[address]
			listenersLock.Unlock()

			if ok {
				connected = true
				break
			}

			time.Sleep(500 * time.Millisecond)
		}

		if !connected {
			return nil, fmt.Errorf("Missing event connection with target cluster member")
		}
	}

	args := &lxd.ConnectionArgs{
		TLSServerCert: string(networkCert.PublicKey()),
		TLSClientCert: string(serverCert.PublicKey()),
		TLSClientKey:  string(serverCert.PrivateKey()),
		SkipGetServer: true,
		UserAgent:     version.UserAgent,
	}

	if notify {
		args.UserAgent = request.UserAgentNotifier
	}

	url := fmt.Sprintf("https://%s", address)
	return lxd.ConnectLXD(url, args)
}

// ConnectIfInstanceIsRemote figures out the address of the node which is
// running the container with the given name. If it's not the local node will
// connect to it and return the connected client, otherwise it will just return
// nil.
func ConnectIfInstanceIsRemote(cluster *db.Cluster, projectName string, name string, networkCert *shared.CertInfo, serverCert *shared.CertInfo, instanceType instancetype.Type) (lxd.InstanceServer, error) {
	var address string // Node address
	err := cluster.Transaction(func(tx *db.ClusterTx) error {
		var err error
		address, err = tx.GetNodeAddressOfInstance(projectName, name, instanceType)
		return err
	})
	if err != nil {
		return nil, err
	}
	if address == "" {
		// The instance is running right on this node, no need to connect.
		return nil, nil
	}
	return Connect(address, networkCert, serverCert, false)
}

// ConnectIfVolumeIsRemote figures out the address of the cluster member on which the volume with the given name is
// defined. If it's not the local cluster member it will connect to it and return the connected client, otherwise
// it just returns nil. If there is more than one cluster member with a matching volume name, an error is returned.
func ConnectIfVolumeIsRemote(s *state.State, poolName string, projectName string, volumeName string, volumeType int, networkCert *shared.CertInfo, serverCert *shared.CertInfo) (lxd.InstanceServer, error) {
	localNodeID := s.Cluster.GetNodeID()
	var err error
	var nodes []db.NodeInfo
	var poolID int64
	err = s.Cluster.Transaction(func(tx *db.ClusterTx) error {
		poolID, err = tx.GetStoragePoolID(poolName)
		if err != nil {
			return err
		}

		nodes, err = tx.GetStorageVolumeNodes(poolID, projectName, volumeName, volumeType)
		if err != nil {
			return err
		}

		return nil
	})
	if err != nil && err != db.ErrNoClusterMember {
		return nil, err
	}

	// If volume uses a remote storage driver and so has no explicit cluster member, then we need to check
	// whether it is exclusively attached to remote instance, and if so then we need to forward the request to
	// the node whereit is currently used. This avoids conflicting with another member when using it locally.
	if err == db.ErrNoClusterMember {
		// GetLocalStoragePoolVolume returns a volume with an empty Location field for remote drivers.
		_, vol, err := s.Cluster.GetLocalStoragePoolVolume(projectName, volumeName, volumeType, poolID)
		if err != nil {
			return nil, err
		}

		remoteInstance, err := storagePools.VolumeUsedByExclusiveRemoteInstancesWithProfiles(s, poolName, projectName, vol)
		if err != nil {
			return nil, errors.Wrapf(err, "Failed checking if volume %q is available", volumeName)
		}

		if remoteInstance != nil {
			var instNode db.NodeInfo
			err := s.Cluster.Transaction(func(tx *db.ClusterTx) error {
				instNode, err = tx.GetNodeByName(remoteInstance.Node)
				return err
			})
			if err != nil {
				return nil, errors.Wrapf(err, "Failed getting cluster member info for %q", remoteInstance.Node)
			}

			// Replace node list with instance's cluster member node (which might be local member).
			nodes = []db.NodeInfo{instNode}
		} else {
			// Volume isn't exclusively attached to an instance. Use local cluster member.
			return nil, nil
		}
	}

	nodeCount := len(nodes)
	if nodeCount > 1 {
		return nil, fmt.Errorf("More than one cluster member has a volume named %q. Please target a specific member", volumeName)
	} else if nodeCount < 1 {
		// Should never get here.
		return nil, fmt.Errorf("Volume %q has empty cluster member list", volumeName)
	}

	node := nodes[0]
	if node.ID == localNodeID {
		// Use local cluster member if volume belongs to this local node.
		return nil, nil
	}

	// Connect to remote cluster member.
	return Connect(node.Address, networkCert, serverCert, false)
}

// SetupTrust is a convenience around InstanceServer.CreateCertificate that adds the given server certificate to
// the trusted pool of the cluster at the given address, using the given password. The certificate is added as
// type CertificateTypeServer to allow intra-member communication. If a certificate with the same fingerprint
// already exists with a different name or type, then no error is returned.
func SetupTrust(serverCert *shared.CertInfo, serverName string, targetAddress string, targetCert string, targetPassword string) error {
	// Connect to the target cluster node.
	args := &lxd.ConnectionArgs{
		TLSServerCert: targetCert,
		UserAgent:     version.UserAgent,
	}

	target, err := lxd.ConnectLXD(fmt.Sprintf("https://%s", targetAddress), args)
	if err != nil {
		return errors.Wrapf(err, "Failed to connect to target cluster node %q", targetAddress)
	}

	cert, err := generateTrustCertificate(serverCert, serverName)
	if err != nil {
		return errors.Wrapf(err, "Failed generating trust certificate")
	}

	post := api.CertificatesPost{
		CertificatePut: cert.CertificatePut,
		Certificate:    cert.Certificate,
		Password:       targetPassword,
	}

	err = target.CreateCertificate(post)
	if err != nil && err.Error() != ErrCertificateExists.Error() {
		return errors.Wrap(err, "Failed to add server cert to cluster")
	}

	return nil
}

// UpdateTrust ensures that the supplied certificate is stored in the target trust store with the correct name
// and type to ensure correct cluster operation. Should be called after SetupTrust. If a certificate with the same
// fingerprint is already in the trust store, but is of the wrong type or name then the existing certificate is
// updated to the correct type and name. If the existing certificate is the correct type but the wrong name then an
// error is returned. And if the existing certificate is the correct type and name then nothing more is done.
func UpdateTrust(serverCert *shared.CertInfo, serverName string, targetAddress string, targetCert string) error {
	// Connect to the target cluster node.
	args := &lxd.ConnectionArgs{
		TLSClientCert: string(serverCert.PublicKey()),
		TLSClientKey:  string(serverCert.PrivateKey()),
		TLSServerCert: targetCert,
		UserAgent:     version.UserAgent,
	}

	target, err := lxd.ConnectLXD(fmt.Sprintf("https://%s", targetAddress), args)
	if err != nil {
		return errors.Wrapf(err, "Failed to connect to target cluster node %q", targetAddress)
	}

	cert, err := generateTrustCertificate(serverCert, serverName)
	if err != nil {
		return errors.Wrapf(err, "Failed generating trust certificate")
	}

	existingCert, _, err := target.GetCertificate(cert.Fingerprint)
	if err != nil {
		return errors.Wrapf(err, "Failed getting existing certificate")
	}

	if existingCert.Name != serverName && existingCert.Type == api.CertificateTypeServer {
		// Don't alter an existing server certificate that has our fingerprint but not our name.
		// Something is wrong as this shouldn't happen.
		return fmt.Errorf("Existing server certificate with different name %q already in trust store", existingCert.Name)
	} else if existingCert.Name != serverName && existingCert.Type != api.CertificateTypeServer {
		// Ensure that if a client certificate already exists that matches our fingerprint, that it
		// has the correct name and type for cluster operation, to allow us to associate member
		// server names to certificate names.
		err = target.UpdateCertificate(cert.Fingerprint, cert.CertificatePut, "")
		if err != nil {
			return errors.Wrap(err, "Failed updating certificate name and type in trust store")
		}
	}

	return nil
}

// generateTrustCertificate converts the specified serverCert and serverName into an api.Certificate suitable for
// use as a trusted cluster server certificate.
func generateTrustCertificate(serverCert *shared.CertInfo, serverName string) (*api.Certificate, error) {
	block, _ := pem.Decode(serverCert.PublicKey())
	if block == nil {
		return nil, fmt.Errorf("Failed to decode certificate")
	}

	fingerprint, err := shared.CertFingerprintStr(string(serverCert.PublicKey()))
	if err != nil {
		return nil, errors.Wrap(err, "Failed to calculate fingerprint")
	}

	certificate := base64.StdEncoding.EncodeToString(block.Bytes)
	cert := api.Certificate{
		CertificatePut: api.CertificatePut{
			Name: serverName,
			Type: api.CertificateTypeServer, // Server type for intra-member communication.
		},
		Certificate: certificate,
		Fingerprint: fingerprint,
	}

	return &cert, nil
}

// HasConnectivity probes the member with the given address for connectivity.
func HasConnectivity(cert *shared.CertInfo, address string) bool {
	config, err := tlsClientConfig(cert)
	if err != nil {
		return false
	}

	var conn net.Conn
	dialer := &net.Dialer{Timeout: time.Second}
	conn, err = tls.DialWithDialer(dialer, "tcp", address, config)
	if err == nil {
		conn.Close()
		return true
	}
	return false
}
<commit_msg>lxd/cluster/connect: Updates HasConnectivity to accept networkCert and serverCert<commit_after>package cluster

import (
	"crypto/tls"
	"encoding/base64"
	"encoding/pem"
	"fmt"
	"net"
	"time"

	"github.com/pkg/errors"

	lxd "github.com/lxc/lxd/client"
	"github.com/lxc/lxd/lxd/cluster/request"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/instance/instancetype"
	"github.com/lxc/lxd/lxd/state"
	storagePools "github.com/lxc/lxd/lxd/storage"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	"github.com/lxc/lxd/shared/version"
)

// ErrCertificateExists indicates that a certificate already exists.
var ErrCertificateExists error = fmt.Errorf("Certificate already in trust store")

// Connect is a convenience around lxd.ConnectLXD that configures the client
// with the correct parameters for node-to-node communication.
//
// If 'notify' switch is true, then the user agent will be set to the special
// to the UserAgentNotifier value, which can be used in some cases to distinguish
// between a regular client request and an internal cluster request.
func Connect(address string, networkCert *shared.CertInfo, serverCert *shared.CertInfo, notify bool) (lxd.InstanceServer, error) {
	// Wait for a connection to the events API first for non-notify connections.
	if !notify {
		connected := false
		for i := 0; i < 20; i++ {
			listenersLock.Lock()
			_, ok := listeners[address]
			listenersLock.Unlock()

			if ok {
				connected = true
				break
			}

			time.Sleep(500 * time.Millisecond)
		}

		if !connected {
			return nil, fmt.Errorf("Missing event connection with target cluster member")
		}
	}

	args := &lxd.ConnectionArgs{
		TLSServerCert: string(networkCert.PublicKey()),
		TLSClientCert: string(serverCert.PublicKey()),
		TLSClientKey:  string(serverCert.PrivateKey()),
		SkipGetServer: true,
		UserAgent:     version.UserAgent,
	}

	if notify {
		args.UserAgent = request.UserAgentNotifier
	}

	url := fmt.Sprintf("https://%s", address)
	return lxd.ConnectLXD(url, args)
}

// ConnectIfInstanceIsRemote figures out the address of the node which is
// running the container with the given name. If it's not the local node will
// connect to it and return the connected client, otherwise it will just return
// nil.
func ConnectIfInstanceIsRemote(cluster *db.Cluster, projectName string, name string, networkCert *shared.CertInfo, serverCert *shared.CertInfo, instanceType instancetype.Type) (lxd.InstanceServer, error) {
	var address string // Node address
	err := cluster.Transaction(func(tx *db.ClusterTx) error {
		var err error
		address, err = tx.GetNodeAddressOfInstance(projectName, name, instanceType)
		return err
	})
	if err != nil {
		return nil, err
	}
	if address == "" {
		// The instance is running right on this node, no need to connect.
		return nil, nil
	}
	return Connect(address, networkCert, serverCert, false)
}

// ConnectIfVolumeIsRemote figures out the address of the cluster member on which the volume with the given name is
// defined. If it's not the local cluster member it will connect to it and return the connected client, otherwise
// it just returns nil. If there is more than one cluster member with a matching volume name, an error is returned.
func ConnectIfVolumeIsRemote(s *state.State, poolName string, projectName string, volumeName string, volumeType int, networkCert *shared.CertInfo, serverCert *shared.CertInfo) (lxd.InstanceServer, error) {
	localNodeID := s.Cluster.GetNodeID()
	var err error
	var nodes []db.NodeInfo
	var poolID int64
	err = s.Cluster.Transaction(func(tx *db.ClusterTx) error {
		poolID, err = tx.GetStoragePoolID(poolName)
		if err != nil {
			return err
		}

		nodes, err = tx.GetStorageVolumeNodes(poolID, projectName, volumeName, volumeType)
		if err != nil {
			return err
		}

		return nil
	})
	if err != nil && err != db.ErrNoClusterMember {
		return nil, err
	}

	// If volume uses a remote storage driver and so has no explicit cluster member, then we need to check
	// whether it is exclusively attached to remote instance, and if so then we need to forward the request to
	// the node whereit is currently used. This avoids conflicting with another member when using it locally.
	if err == db.ErrNoClusterMember {
		// GetLocalStoragePoolVolume returns a volume with an empty Location field for remote drivers.
		_, vol, err := s.Cluster.GetLocalStoragePoolVolume(projectName, volumeName, volumeType, poolID)
		if err != nil {
			return nil, err
		}

		remoteInstance, err := storagePools.VolumeUsedByExclusiveRemoteInstancesWithProfiles(s, poolName, projectName, vol)
		if err != nil {
			return nil, errors.Wrapf(err, "Failed checking if volume %q is available", volumeName)
		}

		if remoteInstance != nil {
			var instNode db.NodeInfo
			err := s.Cluster.Transaction(func(tx *db.ClusterTx) error {
				instNode, err = tx.GetNodeByName(remoteInstance.Node)
				return err
			})
			if err != nil {
				return nil, errors.Wrapf(err, "Failed getting cluster member info for %q", remoteInstance.Node)
			}

			// Replace node list with instance's cluster member node (which might be local member).
			nodes = []db.NodeInfo{instNode}
		} else {
			// Volume isn't exclusively attached to an instance. Use local cluster member.
			return nil, nil
		}
	}

	nodeCount := len(nodes)
	if nodeCount > 1 {
		return nil, fmt.Errorf("More than one cluster member has a volume named %q. Please target a specific member", volumeName)
	} else if nodeCount < 1 {
		// Should never get here.
		return nil, fmt.Errorf("Volume %q has empty cluster member list", volumeName)
	}

	node := nodes[0]
	if node.ID == localNodeID {
		// Use local cluster member if volume belongs to this local node.
		return nil, nil
	}

	// Connect to remote cluster member.
	return Connect(node.Address, networkCert, serverCert, false)
}

// SetupTrust is a convenience around InstanceServer.CreateCertificate that adds the given server certificate to
// the trusted pool of the cluster at the given address, using the given password. The certificate is added as
// type CertificateTypeServer to allow intra-member communication. If a certificate with the same fingerprint
// already exists with a different name or type, then no error is returned.
func SetupTrust(serverCert *shared.CertInfo, serverName string, targetAddress string, targetCert string, targetPassword string) error {
	// Connect to the target cluster node.
	args := &lxd.ConnectionArgs{
		TLSServerCert: targetCert,
		UserAgent:     version.UserAgent,
	}

	target, err := lxd.ConnectLXD(fmt.Sprintf("https://%s", targetAddress), args)
	if err != nil {
		return errors.Wrapf(err, "Failed to connect to target cluster node %q", targetAddress)
	}

	cert, err := generateTrustCertificate(serverCert, serverName)
	if err != nil {
		return errors.Wrapf(err, "Failed generating trust certificate")
	}

	post := api.CertificatesPost{
		CertificatePut: cert.CertificatePut,
		Certificate:    cert.Certificate,
		Password:       targetPassword,
	}

	err = target.CreateCertificate(post)
	if err != nil && err.Error() != ErrCertificateExists.Error() {
		return errors.Wrap(err, "Failed to add server cert to cluster")
	}

	return nil
}

// UpdateTrust ensures that the supplied certificate is stored in the target trust store with the correct name
// and type to ensure correct cluster operation. Should be called after SetupTrust. If a certificate with the same
// fingerprint is already in the trust store, but is of the wrong type or name then the existing certificate is
// updated to the correct type and name. If the existing certificate is the correct type but the wrong name then an
// error is returned. And if the existing certificate is the correct type and name then nothing more is done.
func UpdateTrust(serverCert *shared.CertInfo, serverName string, targetAddress string, targetCert string) error {
	// Connect to the target cluster node.
	args := &lxd.ConnectionArgs{
		TLSClientCert: string(serverCert.PublicKey()),
		TLSClientKey:  string(serverCert.PrivateKey()),
		TLSServerCert: targetCert,
		UserAgent:     version.UserAgent,
	}

	target, err := lxd.ConnectLXD(fmt.Sprintf("https://%s", targetAddress), args)
	if err != nil {
		return errors.Wrapf(err, "Failed to connect to target cluster node %q", targetAddress)
	}

	cert, err := generateTrustCertificate(serverCert, serverName)
	if err != nil {
		return errors.Wrapf(err, "Failed generating trust certificate")
	}

	existingCert, _, err := target.GetCertificate(cert.Fingerprint)
	if err != nil {
		return errors.Wrapf(err, "Failed getting existing certificate")
	}

	if existingCert.Name != serverName && existingCert.Type == api.CertificateTypeServer {
		// Don't alter an existing server certificate that has our fingerprint but not our name.
		// Something is wrong as this shouldn't happen.
		return fmt.Errorf("Existing server certificate with different name %q already in trust store", existingCert.Name)
	} else if existingCert.Name != serverName && existingCert.Type != api.CertificateTypeServer {
		// Ensure that if a client certificate already exists that matches our fingerprint, that it
		// has the correct name and type for cluster operation, to allow us to associate member
		// server names to certificate names.
		err = target.UpdateCertificate(cert.Fingerprint, cert.CertificatePut, "")
		if err != nil {
			return errors.Wrap(err, "Failed updating certificate name and type in trust store")
		}
	}

	return nil
}

// generateTrustCertificate converts the specified serverCert and serverName into an api.Certificate suitable for
// use as a trusted cluster server certificate.
func generateTrustCertificate(serverCert *shared.CertInfo, serverName string) (*api.Certificate, error) {
	block, _ := pem.Decode(serverCert.PublicKey())
	if block == nil {
		return nil, fmt.Errorf("Failed to decode certificate")
	}

	fingerprint, err := shared.CertFingerprintStr(string(serverCert.PublicKey()))
	if err != nil {
		return nil, errors.Wrap(err, "Failed to calculate fingerprint")
	}

	certificate := base64.StdEncoding.EncodeToString(block.Bytes)
	cert := api.Certificate{
		CertificatePut: api.CertificatePut{
			Name: serverName,
			Type: api.CertificateTypeServer, // Server type for intra-member communication.
		},
		Certificate: certificate,
		Fingerprint: fingerprint,
	}

	return &cert, nil
}

// HasConnectivity probes the member with the given address for connectivity.
func HasConnectivity(networkCert *shared.CertInfo, serverCert *shared.CertInfo, address string) bool {
	config, err := tlsClientConfig(networkCert, serverCert)
	if err != nil {
		return false
	}

	var conn net.Conn
	dialer := &net.Dialer{Timeout: time.Second}
	conn, err = tls.DialWithDialer(dialer, "tcp", address, config)
	if err == nil {
		conn.Close()
		return true
	}
	return false
}
<|endoftext|>


<commit_before>package util

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"io/ioutil"
	"path/filepath"

	"github.com/lxc/lxd/shared"
	"github.com/pkg/errors"

	"golang.org/x/crypto/scrypt"
)

// PasswordCheck validates the provided password against the encoded secret
func PasswordCheck(secret string, password string) error {
	// No password set
	if secret == "" {
		return fmt.Errorf("No password is set")
	}

	// Compare the password
	buff, err := hex.DecodeString(secret)
	if err != nil {
		return err
	}

	salt := buff[0:32]
	hash, err := scrypt.Key([]byte(password), salt, 1<<14, 8, 1, 64)
	if err != nil {
		return err
	}

	if !bytes.Equal(hash, buff[32:]) {
		return fmt.Errorf("Bad password provided")
	}

	return nil
}

// LoadCert reads the LXD server certificate from the given var dir.
//
// If a cluster certificate is found it will be loaded instead.
func LoadCert(dir string) (*shared.CertInfo, error) {
	prefix := "server"
	if shared.PathExists(filepath.Join(dir, "cluster.crt")) {
		prefix = "cluster"
	}

	cert, err := shared.KeyPairAndCA(dir, prefix, shared.CertServer, true)
	if err != nil {
		return nil, errors.Wrap(err, "failed to load TLS certificate")
	}

	return cert, nil
}

// WriteCert writes the given material to the appropriate certificate files in
// the given LXD var directory.
func WriteCert(dir, prefix string, cert, key, ca []byte) error {
	err := ioutil.WriteFile(filepath.Join(dir, prefix+".crt"), cert, 0644)
	if err != nil {
		return err
	}

	err = ioutil.WriteFile(filepath.Join(dir, prefix+".key"), key, 0600)
	if err != nil {
		return err
	}

	if ca != nil {
		err = ioutil.WriteFile(filepath.Join(dir, prefix+".ca"), ca, 0644)
		if err != nil {
			return err
		}
	}

	return nil
}
<commit_msg>lxd/util/encryption: Comment on LoadCert<commit_after>package util

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"io/ioutil"
	"path/filepath"

	"github.com/lxc/lxd/shared"
	"github.com/pkg/errors"

	"golang.org/x/crypto/scrypt"
)

// PasswordCheck validates the provided password against the encoded secret
func PasswordCheck(secret string, password string) error {
	// No password set
	if secret == "" {
		return fmt.Errorf("No password is set")
	}

	// Compare the password
	buff, err := hex.DecodeString(secret)
	if err != nil {
		return err
	}

	salt := buff[0:32]
	hash, err := scrypt.Key([]byte(password), salt, 1<<14, 8, 1, 64)
	if err != nil {
		return err
	}

	if !bytes.Equal(hash, buff[32:]) {
		return fmt.Errorf("Bad password provided")
	}

	return nil
}

// LoadCert reads the LXD server certificate from the given var dir.
//
// If a cluster certificate is found it will be loaded instead.
// If neither a server or cluster certfificate exists, a new server certificate will be generated.
func LoadCert(dir string) (*shared.CertInfo, error) {
	prefix := "server"
	if shared.PathExists(filepath.Join(dir, "cluster.crt")) {
		prefix = "cluster"
	}

	cert, err := shared.KeyPairAndCA(dir, prefix, shared.CertServer, true)
	if err != nil {
		return nil, errors.Wrap(err, "failed to load TLS certificate")
	}

	return cert, nil
}

// WriteCert writes the given material to the appropriate certificate files in
// the given LXD var directory.
func WriteCert(dir, prefix string, cert, key, ca []byte) error {
	err := ioutil.WriteFile(filepath.Join(dir, prefix+".crt"), cert, 0644)
	if err != nil {
		return err
	}

	err = ioutil.WriteFile(filepath.Join(dir, prefix+".key"), key, 0600)
	if err != nil {
		return err
	}

	if ca != nil {
		err = ioutil.WriteFile(filepath.Join(dir, prefix+".ca"), ca, 0644)
		if err != nil {
			return err
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	. "fmt"
	"github.com/yak-people/chirp-lang"
	"io/ioutil"
	"os"
)

var cFlag = flag.String("c", "", "Immediate command to execute.")

func saveArgvStarting(fr *chirp.Frame, i int) {
	argv := []chirp.T{}
	for _, a := range os.Args[i:] {
		argv = append(argv, chirp.MkString(a))
	}
	fr.SetVar("argv", chirp.MkList(argv))
}

func main() {
	flag.Parse()
	fr := chirp.New()

	if cFlag != nil && *cFlag != "" {
		saveArgvStarting(fr, 1)
		Printf("T<<< %#v\n", *cFlag)
		z := fr.Eval(chirp.MkString(*cFlag))
		Printf("T>>> %#v\n", z)
		return
	}

	if len(os.Args) > 1 {
		fname := os.Args[1]
		contents, err := ioutil.ReadFile(fname)
		if err != nil {
			Fprintf(os.Stderr, "Cannot read file %s: %v", fname, err)
			os.Exit(2)
			return
		}
		saveArgvStarting(fr, 2)
		Printf("T<<< fname = %#v\n", fname)
		z := fr.Eval(chirp.MkString(string(contents)))
		Printf("T>>> %#v\n", z)
		return
	}

	// No os.Args --
	panic("REPL not yet")
}
<commit_msg>Remove Printf used for debugging.<commit_after>package main

import (
	"flag"
	. "fmt"
	"github.com/yak-people/chirp-lang"
	"io/ioutil"
	"os"
)

var cFlag = flag.String("c", "", "Immediate command to execute.")

func saveArgvStarting(fr *chirp.Frame, i int) {
	argv := []chirp.T{}
	for _, a := range os.Args[i:] {
		argv = append(argv, chirp.MkString(a))
	}
	fr.SetVar("argv", chirp.MkList(argv))
}

func main() {
	flag.Parse()
	fr := chirp.New()

	if cFlag != nil && *cFlag != "" {
		saveArgvStarting(fr, 1)
		fr.Eval(chirp.MkString(*cFlag))
		return
	}

	if len(os.Args) > 1 {
		fname := os.Args[1]
		contents, err := ioutil.ReadFile(fname)
		if err != nil {
			Fprintf(os.Stderr, "Cannot read file %s: %v", fname, err)
			os.Exit(2)
			return
		}
		saveArgvStarting(fr, 2)
		fr.Eval(chirp.MkString(string(contents)))
		return
	}

	// No os.Args --
	panic("REPL not yet")
}
<|endoftext|>


<commit_before>// Package cienv provides utility for environment variable in CI services.
package cienv

import (
	"errors"
	"os"
	"regexp"
	"strconv"
	"strings"
)

// BuildInfo represents build information about GitHub or GitLab project.
type BuildInfo struct {
	Owner string
	Repo  string
	SHA   string

	// Optional.
	PullRequest int // MergeRequest for GitLab.

	// Optional.
	Branch string
}

// GetBuildInfo returns BuildInfo from environment variables.
//
// Supporrted CI services' documents:
// - Travis CI: https://docs.travis-ci.com/user/environment-variables/
// - Circle CI: https://circleci.com/docs/environment-variables/
// - Drone.io: http://docs.drone.io/environment-reference/
// - GitLab CI: https://docs.gitlab.com/ee/ci/variables/#predefined-variables-environment-variables
//   - GitLab CI doesn't export ID of Merge Request. https://gitlab.com/gitlab-org/gitlab-ce/issues/15280
func GetBuildInfo() (prInfo *BuildInfo, isPR bool, err error) {
	owner, repo := getOwnerAndRepoFromSlug([]string{
		"TRAVIS_REPO_SLUG",
		"DRONE_REPO", // drone<=0.4
	})
	if owner == "" {
		owner = getOneEnvValue([]string{
			"CI_REPO_OWNER", // common
			"CIRCLE_PROJECT_USERNAME",
			"DRONE_REPO_OWNER",
			"CI_PROJECT_NAMESPACE", // GitLab CI
		})
	}
	if owner == "" {
		return nil, false, errors.New("cannot get repo owner from environment variable. Set CI_REPO_OWNER?")
	}

	if repo == "" {
		repo = getOneEnvValue([]string{
			"CI_REPO_NAME", // common
			"CIRCLE_PROJECT_REPONAME",
			"DRONE_REPO_NAME",
			"CI_PROJECT_NAME", // GitLab CI
		})
	}

	if owner == "" {
		return nil, false, errors.New("cannot get repo name from environment variable. Set CI_REPO_NAME?")
	}

	sha := getOneEnvValue([]string{
		"CI_COMMIT", // common
		"TRAVIS_PULL_REQUEST_SHA",
		"TRAVIS_COMMIT",
		"CIRCLE_SHA1",
		"DRONE_COMMIT",
		"CI_COMMIT_SHA", // GitLab CI
	})
	if sha == "" {
		return nil, false, errors.New("cannot get commit SHA from environment variable. Set CI_COMMIT?")
	}

	branch := getOneEnvValue([]string{
		"CI_BRANCH", // common
		"TRAVIS_PULL_REQUEST_BRANCH",
		"CIRCLE_BRANCH",
		"DRONE_COMMIT_BRANCH",
	})

	pr := getPullRequestNum()

	return &BuildInfo{
		Owner:       owner,
		Repo:        repo,
		PullRequest: pr,
		SHA:         sha,
		Branch:      branch,
	}, pr != 0, nil
}

func getPullRequestNum() int {
	envs := []string{
		// Common.
		"CI_PULL_REQUEST",
		// Travis CI.
		"TRAVIS_PULL_REQUEST",
		// Circle CI.
		"CIRCLE_PULL_REQUEST", // CircleCI 2.0
		"CIRCLE_PR_NUMBER",    // For Pull Request by a fork repository
		// drone.io.
		"DRONE_PULL_REQUEST",
	}
	// regexp.MustCompile() in func intentionally because this func is called
	// once for one run.
	re := regexp.MustCompile(`[1-9]\d*$`)
	for _, env := range envs {
		prm := re.FindString(os.Getenv(env))
		pr, _ := strconv.Atoi(prm)
		if pr != 0 {
			return pr
		}
	}
	return 0
}

func getOneEnvValue(envs []string) string {
	for _, env := range envs {
		if v := os.Getenv(env); v != "" {
			return v
		}
	}
	return ""
}

func getOwnerAndRepoFromSlug(slugEnvs []string) (string, string) {
	repoSlug := getOneEnvValue(slugEnvs)
	ownerAndRepo := strings.SplitN(repoSlug, "/", 2)
	if len(ownerAndRepo) < 2 {
		return "", ""
	}
	return ownerAndRepo[0], ownerAndRepo[1]
}
<commit_msg>Fix condition to validate repo CI envronment variable<commit_after>// Package cienv provides utility for environment variable in CI services.
package cienv

import (
	"errors"
	"os"
	"regexp"
	"strconv"
	"strings"
)

// BuildInfo represents build information about GitHub or GitLab project.
type BuildInfo struct {
	Owner string
	Repo  string
	SHA   string

	// Optional.
	PullRequest int // MergeRequest for GitLab.

	// Optional.
	Branch string
}

// GetBuildInfo returns BuildInfo from environment variables.
//
// Supporrted CI services' documents:
// - Travis CI: https://docs.travis-ci.com/user/environment-variables/
// - Circle CI: https://circleci.com/docs/environment-variables/
// - Drone.io: http://docs.drone.io/environment-reference/
// - GitLab CI: https://docs.gitlab.com/ee/ci/variables/#predefined-variables-environment-variables
//   - GitLab CI doesn't export ID of Merge Request. https://gitlab.com/gitlab-org/gitlab-ce/issues/15280
func GetBuildInfo() (prInfo *BuildInfo, isPR bool, err error) {
	owner, repo := getOwnerAndRepoFromSlug([]string{
		"TRAVIS_REPO_SLUG",
		"DRONE_REPO", // drone<=0.4
	})
	if owner == "" {
		owner = getOneEnvValue([]string{
			"CI_REPO_OWNER", // common
			"CIRCLE_PROJECT_USERNAME",
			"DRONE_REPO_OWNER",
			"CI_PROJECT_NAMESPACE", // GitLab CI
		})
	}
	if owner == "" {
		return nil, false, errors.New("cannot get repo owner from environment variable. Set CI_REPO_OWNER?")
	}

	if repo == "" {
		repo = getOneEnvValue([]string{
			"CI_REPO_NAME", // common
			"CIRCLE_PROJECT_REPONAME",
			"DRONE_REPO_NAME",
			"CI_PROJECT_NAME", // GitLab CI
		})
	}

	if repo == "" {
		return nil, false, errors.New("cannot get repo name from environment variable. Set CI_REPO_NAME?")
	}

	sha := getOneEnvValue([]string{
		"CI_COMMIT", // common
		"TRAVIS_PULL_REQUEST_SHA",
		"TRAVIS_COMMIT",
		"CIRCLE_SHA1",
		"DRONE_COMMIT",
		"CI_COMMIT_SHA", // GitLab CI
	})
	if sha == "" {
		return nil, false, errors.New("cannot get commit SHA from environment variable. Set CI_COMMIT?")
	}

	branch := getOneEnvValue([]string{
		"CI_BRANCH", // common
		"TRAVIS_PULL_REQUEST_BRANCH",
		"CIRCLE_BRANCH",
		"DRONE_COMMIT_BRANCH",
	})

	pr := getPullRequestNum()

	return &BuildInfo{
		Owner:       owner,
		Repo:        repo,
		PullRequest: pr,
		SHA:         sha,
		Branch:      branch,
	}, pr != 0, nil
}

func getPullRequestNum() int {
	envs := []string{
		// Common.
		"CI_PULL_REQUEST",
		// Travis CI.
		"TRAVIS_PULL_REQUEST",
		// Circle CI.
		"CIRCLE_PULL_REQUEST", // CircleCI 2.0
		"CIRCLE_PR_NUMBER",    // For Pull Request by a fork repository
		// drone.io.
		"DRONE_PULL_REQUEST",
	}
	// regexp.MustCompile() in func intentionally because this func is called
	// once for one run.
	re := regexp.MustCompile(`[1-9]\d*$`)
	for _, env := range envs {
		prm := re.FindString(os.Getenv(env))
		pr, _ := strconv.Atoi(prm)
		if pr != 0 {
			return pr
		}
	}
	return 0
}

func getOneEnvValue(envs []string) string {
	for _, env := range envs {
		if v := os.Getenv(env); v != "" {
			return v
		}
	}
	return ""
}

func getOwnerAndRepoFromSlug(slugEnvs []string) (string, string) {
	repoSlug := getOneEnvValue(slugEnvs)
	ownerAndRepo := strings.SplitN(repoSlug, "/", 2)
	if len(ownerAndRepo) < 2 {
		return "", ""
	}
	return ownerAndRepo[0], ownerAndRepo[1]
}
<|endoftext|>


<commit_before>package yang

import (
	"strings"

	"github.com/openconfig/goyang/pkg/yang"
	"github.com/oshothebig/pbast"
)

var builtinMap = map[yang.TypeKind]pbast.Type{
	yang.Yint8:   pbast.Int32,
	yang.Yint16:  pbast.Int32,
	yang.Yint32:  pbast.Int32,
	yang.Yint64:  pbast.Int64,
	yang.Yuint8:  pbast.UInt32,
	yang.Yuint16: pbast.UInt32,
	yang.Yuint32: pbast.UInt32,
	yang.Yuint64: pbast.UInt64,
	yang.Ystring: pbast.String,
	yang.Ybool:   pbast.Bool,
	yang.Ybinary: pbast.Bytes,
}

type transformer struct {
	topScope  []*pbast.Message
	decimal64 *pbast.Message
}

// e must be YANG module
func Transform(e *yang.Entry) *pbast.File {
	if _, ok := e.Node.(*yang.Module); !ok {
		return nil
	}

	t := &transformer{}

	return t.module(entry{e})
}

func (t *transformer) declare(m *pbast.Message) {
	if m == nil {
		return
	}
	t.topScope = append(t.topScope, m)
}

func (t *transformer) module(e entry) *pbast.File {
	namespace := e.Namespace().Name
	f := pbast.NewFile(pbast.NewPackageWithElements(guessElements(namespace)))

	f.Comment = t.moduleComment(e)

	// Child nodes are enclosed with Root message
	f.AddMessage(t.buildMessage("Root", e))

	// RPCs
	s := t.rpcs(e)
	f.AddService(s)

	// Notifications
	n := t.notifications(e)
	f.AddService(n)

	for _, m := range t.topScope {
		f.AddMessage(m)
	}
	f.AddMessage(t.decimal64)

	return f
}

func (t *transformer) moduleComment(e entry) pbast.Comment {
	description := t.description(e)
	namespace := t.namespace(e)
	revisions := t.revisions(e)
	reference := t.reference(e)

	var comment []string
	comment = append(comment, description...)
	comment = append(comment, namespace...)
	comment = append(comment, revisions...)
	comment = append(comment, reference...)

	return comment
}

func (t *transformer) genericComments(e entry) pbast.Comment {
	description := t.description(e)
	reference := t.reference(e)

	comments := append(description, reference...)
	return comments
}

func (t *transformer) description(e entry) pbast.Comment {
	description := e.Description
	if e.Description == "" {
		return nil
	}

	lines := strings.Split(strings.TrimRight(description, "\n "), "\n")

	ret := make([]string, 0, len(lines)+1)
	ret = append(ret, "Description:")
	ret = append(ret, lines...)
	return ret
}

func (t *transformer) revisions(e entry) pbast.Comment {
	var lines []string
	if v := e.Extra["revision"]; len(v) > 0 {
		for _, rev := range v[0].([]*yang.Revision) {
			lines = append(lines, "Revision: "+rev.Name)
		}
	}

	return lines
}

func (t *transformer) namespace(e entry) pbast.Comment {
	namespace := e.Namespace().Name
	if namespace == "" {
		return nil
	}

	return []string{"Namespace: " + namespace}
}

func (t *transformer) reference(e entry) pbast.Comment {
	v := e.Extra["reference"]
	if len(v) == 0 {
		return nil
	}

	ref := v[0].(*yang.Value)
	if ref == nil {
		return nil
	}
	if ref.Name == "" {
		return nil
	}

	lines := strings.Split(strings.TrimRight(ref.Name, "\n "), "\n")

	ret := make([]string, 0, len(lines)+1)
	ret = append(ret, "Reference:")
	ret = append(ret, lines...)
	return ret
}

func (t *transformer) rpcs(e entry) *pbast.Service {
	rpcs := e.rpcs()
	if len(rpcs) == 0 {
		return nil
	}

	s := pbast.NewService(CamelCase(e.Name))
	s.Comment = t.genericComments(e)
	for _, rpc := range rpcs {
		r := t.rpc(rpc)
		s.AddRPC(r)
	}

	return s
}

func (t *transformer) rpc(e entry) *pbast.RPC {
	method := CamelCase(e.Name)
	in := method + "Request"
	out := method + "Response"

	rpc := pbast.NewRPC(
		method,
		pbast.NewReturnType(in),
		pbast.NewReturnType(out),
	)
	rpc.Comment = t.genericComments(e)

	t.declare(t.buildMessage(in, entry{e.RPC.Input}))
	t.declare(t.buildMessage(out, entry{e.RPC.Output}))

	return rpc
}

func (t *transformer) notifications(e entry) *pbast.Service {
	notifications := e.notifications()
	if len(notifications) == 0 {
		return nil
	}

	s := pbast.NewService(CamelCase(e.Name + "Notification"))
	s.Comment = t.genericComments(e)
	for _, notification := range notifications {
		n := t.notification(notification)
		s.AddRPC(n)
	}

	return s
}

func (t *transformer) notification(e entry) *pbast.RPC {
	method := CamelCase(e.Name)
	in := method + "NotificationRequest"
	out := method + "NotificationResponse"

	rpc := pbast.NewRPC(method, pbast.NewReturnType(in), pbast.NewReturnType(out))

	// notification statement doesn't have an input parameter equivalent,
	// then empty message is used for input as RPC
	t.declare(pbast.NewMessage(in))
	t.declare(t.buildMessage(out, e))

	return rpc
}

func (t *transformer) buildMessage(name string, e entry) *pbast.Message {
	if e.Entry == nil {
		return nil
	}

	msg := pbast.NewMessage(name)
	msg.Comment = t.genericComments(e)
	for index, child := range e.children() {
		fieldNum := index + 1
		switch {
		// leaf-list case
		case child.Type != nil && child.ListAttr != nil:
			field, nested := t.leaf(child, fieldNum, true)
			msg.AddType(nested).AddField(field)
		// leaf case
		case child.Type != nil:
			field, nested := t.leaf(child, fieldNum, false)
			msg.AddType(nested).AddField(field)
		// list case
		case child.ListAttr != nil:
			inner, field := t.directory(child, fieldNum, true)
			msg.AddMessage(inner).AddField(field)
		// others might be container case
		default:
			inner, field := t.directory(child, fieldNum, false)
			msg.AddMessage(inner).AddField(field)
		}
	}

	return msg
}

func (t *transformer) leaf(e entry, index int, repeated bool) (field *pbast.MessageField, nested pbast.Type) {
	typ := builtinMap[e.Type.Kind]
	// no direct builtin type mapping
	// custom message is built
	if typ == nil {
		name := CamelCase(e.Name)
		switch e.Type.Kind {
		// define at the top level
		case yang.Ydecimal64:
			t.decimal64 = decimal64Message
			typ = decimal64Message
		// define as a nested type
		case yang.Ybits:
			typ = t.customBits(name, e.Type.Bit)
		// define as a nested type
		case yang.Yenum:
			typ = t.customEnum(name, e.Type.Enum)
		// not implemented
		case yang.Yunion, yang.Yempty, yang.Yleafref,
			yang.Yidentityref, yang.YinstanceIdentifier:
			return nil, nil
		}
	}

	name := underscoreCase(e.Name)
	if repeated {
		field = pbast.NewRepeatedMessageField(typ, name, index)
	} else {
		field = pbast.NewMessageField(typ, name, index)
	}

	if e.Type.Kind == yang.Ydecimal64 {
		return field, nil
	}

	return field, typ
}

func (t *transformer) customBits(name string, bits *yang.EnumType) *pbast.Message {
	msg := pbast.NewMessage(name)
	for i, n := range bits.Names() {
		v := 1 << uint(bits.Values()[i])
		msg.AddField(pbast.NewMessageField(pbast.Bool, n, v))
	}

	return msg
}

func (t *transformer) customEnum(name string, e *yang.EnumType) *pbast.Enum {
	enum := pbast.NewEnum(name)
	for i, n := range e.Names() {
		v := int(e.Values()[i])
		enum.AddField(pbast.NewEnumField(constantName(n), v))
	}

	return enum
}

func (t *transformer) directory(e entry, index int, repeated bool) (*pbast.Message, *pbast.MessageField) {
	fieldName := underscoreCase(e.Name)
	typeName := CamelCase(e.Name)

	inner := t.buildMessage(typeName, e)
	var field *pbast.MessageField
	if repeated {
		field = pbast.NewRepeatedMessageField(inner, fieldName, index)
	} else {
		field = pbast.NewMessageField(inner, fieldName, index)
	}

	return inner, field
}
<commit_msg>Remove duplicated comment<commit_after>package yang

import (
	"strings"

	"github.com/openconfig/goyang/pkg/yang"
	"github.com/oshothebig/pbast"
)

var builtinMap = map[yang.TypeKind]pbast.Type{
	yang.Yint8:   pbast.Int32,
	yang.Yint16:  pbast.Int32,
	yang.Yint32:  pbast.Int32,
	yang.Yint64:  pbast.Int64,
	yang.Yuint8:  pbast.UInt32,
	yang.Yuint16: pbast.UInt32,
	yang.Yuint32: pbast.UInt32,
	yang.Yuint64: pbast.UInt64,
	yang.Ystring: pbast.String,
	yang.Ybool:   pbast.Bool,
	yang.Ybinary: pbast.Bytes,
}

type transformer struct {
	topScope  []*pbast.Message
	decimal64 *pbast.Message
}

// e must be YANG module
func Transform(e *yang.Entry) *pbast.File {
	if _, ok := e.Node.(*yang.Module); !ok {
		return nil
	}

	t := &transformer{}

	return t.module(entry{e})
}

func (t *transformer) declare(m *pbast.Message) {
	if m == nil {
		return
	}
	t.topScope = append(t.topScope, m)
}

func (t *transformer) module(e entry) *pbast.File {
	namespace := e.Namespace().Name
	f := pbast.NewFile(pbast.NewPackageWithElements(guessElements(namespace)))

	f.Comment = t.moduleComment(e)

	// Child nodes are enclosed with Root message
	f.AddMessage(t.buildMessage("Root", e))

	// RPCs
	s := t.rpcs(e)
	f.AddService(s)

	// Notifications
	n := t.notifications(e)
	f.AddService(n)

	for _, m := range t.topScope {
		f.AddMessage(m)
	}
	f.AddMessage(t.decimal64)

	return f
}

func (t *transformer) moduleComment(e entry) pbast.Comment {
	description := t.description(e)
	namespace := t.namespace(e)
	revisions := t.revisions(e)
	reference := t.reference(e)

	var comment []string
	comment = append(comment, description...)
	comment = append(comment, namespace...)
	comment = append(comment, revisions...)
	comment = append(comment, reference...)

	return comment
}

func (t *transformer) genericComments(e entry) pbast.Comment {
	description := t.description(e)
	reference := t.reference(e)

	comments := append(description, reference...)
	return comments
}

func (t *transformer) description(e entry) pbast.Comment {
	description := e.Description
	if e.Description == "" {
		return nil
	}

	lines := strings.Split(strings.TrimRight(description, "\n "), "\n")

	ret := make([]string, 0, len(lines)+1)
	ret = append(ret, "Description:")
	ret = append(ret, lines...)
	return ret
}

func (t *transformer) revisions(e entry) pbast.Comment {
	var lines []string
	if v := e.Extra["revision"]; len(v) > 0 {
		for _, rev := range v[0].([]*yang.Revision) {
			lines = append(lines, "Revision: "+rev.Name)
		}
	}

	return lines
}

func (t *transformer) namespace(e entry) pbast.Comment {
	namespace := e.Namespace().Name
	if namespace == "" {
		return nil
	}

	return []string{"Namespace: " + namespace}
}

func (t *transformer) reference(e entry) pbast.Comment {
	v := e.Extra["reference"]
	if len(v) == 0 {
		return nil
	}

	ref := v[0].(*yang.Value)
	if ref == nil {
		return nil
	}
	if ref.Name == "" {
		return nil
	}

	lines := strings.Split(strings.TrimRight(ref.Name, "\n "), "\n")

	ret := make([]string, 0, len(lines)+1)
	ret = append(ret, "Reference:")
	ret = append(ret, lines...)
	return ret
}

func (t *transformer) rpcs(e entry) *pbast.Service {
	rpcs := e.rpcs()
	if len(rpcs) == 0 {
		return nil
	}

	s := pbast.NewService(CamelCase(e.Name))
	for _, rpc := range rpcs {
		r := t.rpc(rpc)
		s.AddRPC(r)
	}

	return s
}

func (t *transformer) rpc(e entry) *pbast.RPC {
	method := CamelCase(e.Name)
	in := method + "Request"
	out := method + "Response"

	rpc := pbast.NewRPC(
		method,
		pbast.NewReturnType(in),
		pbast.NewReturnType(out),
	)
	rpc.Comment = t.genericComments(e)

	t.declare(t.buildMessage(in, entry{e.RPC.Input}))
	t.declare(t.buildMessage(out, entry{e.RPC.Output}))

	return rpc
}

func (t *transformer) notifications(e entry) *pbast.Service {
	notifications := e.notifications()
	if len(notifications) == 0 {
		return nil
	}

	s := pbast.NewService(CamelCase(e.Name + "Notification"))
	for _, notification := range notifications {
		n := t.notification(notification)
		s.AddRPC(n)
	}

	return s
}

func (t *transformer) notification(e entry) *pbast.RPC {
	method := CamelCase(e.Name)
	in := method + "NotificationRequest"
	out := method + "NotificationResponse"

	rpc := pbast.NewRPC(method, pbast.NewReturnType(in), pbast.NewReturnType(out))

	// notification statement doesn't have an input parameter equivalent,
	// then empty message is used for input as RPC
	t.declare(pbast.NewMessage(in))
	t.declare(t.buildMessage(out, e))

	return rpc
}

func (t *transformer) buildMessage(name string, e entry) *pbast.Message {
	if e.Entry == nil {
		return nil
	}

	msg := pbast.NewMessage(name)
	msg.Comment = t.genericComments(e)
	for index, child := range e.children() {
		fieldNum := index + 1
		switch {
		// leaf-list case
		case child.Type != nil && child.ListAttr != nil:
			field, nested := t.leaf(child, fieldNum, true)
			msg.AddType(nested).AddField(field)
		// leaf case
		case child.Type != nil:
			field, nested := t.leaf(child, fieldNum, false)
			msg.AddType(nested).AddField(field)
		// list case
		case child.ListAttr != nil:
			inner, field := t.directory(child, fieldNum, true)
			msg.AddMessage(inner).AddField(field)
		// others might be container case
		default:
			inner, field := t.directory(child, fieldNum, false)
			msg.AddMessage(inner).AddField(field)
		}
	}

	return msg
}

func (t *transformer) leaf(e entry, index int, repeated bool) (field *pbast.MessageField, nested pbast.Type) {
	typ := builtinMap[e.Type.Kind]
	// no direct builtin type mapping
	// custom message is built
	if typ == nil {
		name := CamelCase(e.Name)
		switch e.Type.Kind {
		// define at the top level
		case yang.Ydecimal64:
			t.decimal64 = decimal64Message
			typ = decimal64Message
		// define as a nested type
		case yang.Ybits:
			typ = t.customBits(name, e.Type.Bit)
		// define as a nested type
		case yang.Yenum:
			typ = t.customEnum(name, e.Type.Enum)
		// not implemented
		case yang.Yunion, yang.Yempty, yang.Yleafref,
			yang.Yidentityref, yang.YinstanceIdentifier:
			return nil, nil
		}
	}

	name := underscoreCase(e.Name)
	if repeated {
		field = pbast.NewRepeatedMessageField(typ, name, index)
	} else {
		field = pbast.NewMessageField(typ, name, index)
	}

	if e.Type.Kind == yang.Ydecimal64 {
		return field, nil
	}

	return field, typ
}

func (t *transformer) customBits(name string, bits *yang.EnumType) *pbast.Message {
	msg := pbast.NewMessage(name)
	for i, n := range bits.Names() {
		v := 1 << uint(bits.Values()[i])
		msg.AddField(pbast.NewMessageField(pbast.Bool, n, v))
	}

	return msg
}

func (t *transformer) customEnum(name string, e *yang.EnumType) *pbast.Enum {
	enum := pbast.NewEnum(name)
	for i, n := range e.Names() {
		v := int(e.Values()[i])
		enum.AddField(pbast.NewEnumField(constantName(n), v))
	}

	return enum
}

func (t *transformer) directory(e entry, index int, repeated bool) (*pbast.Message, *pbast.MessageField) {
	fieldName := underscoreCase(e.Name)
	typeName := CamelCase(e.Name)

	inner := t.buildMessage(typeName, e)
	var field *pbast.MessageField
	if repeated {
		field = pbast.NewRepeatedMessageField(inner, fieldName, index)
	} else {
		field = pbast.NewMessageField(inner, fieldName, index)
	}

	return inner, field
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"strings"
	"time"

	"github.com/cheggaaa/pb"
	"github.com/docker/docker/pkg/term"
	cfg "github.com/flynn/flynn/cli/config"
	"github.com/flynn/flynn/controller/client"
	ct "github.com/flynn/flynn/controller/types"
	"github.com/flynn/flynn/pkg/backup"
	"github.com/flynn/flynn/pkg/shutdown"
	"github.com/flynn/go-docopt"
)

func init() {
	register("cluster", runCluster, `
usage: flynn cluster
       flynn cluster add [-f] [-d] [--git-url <giturl>] [--no-git] [--docker-push-url <url>] [--docker] [-p <tlspin>] <cluster-name> <domain> <key>
       flynn cluster remove <cluster-name>
       flynn cluster default [<cluster-name>]
       flynn cluster migrate-domain <domain>
       flynn cluster backup [--file <file>]

Manage Flynn clusters.

Commands:
    With no arguments, shows a list of configured clusters.

    add
        Adds <cluster-name> to the ~/.flynnrc configuration file.

        options:
            -f, --force               force add cluster
            -d, --default             set as default cluster
            --git-url=<giturl>        git URL
            --no-git                  skip git configuration
            --docker-push-url=<url>   Docker push URL
            --docker                  configure Docker to push to the cluster
            -p, --tls-pin=<tlspin>    SHA256 of the cluster's TLS cert

    remove
        Removes <cluster-name> from the ~/.flynnrc configuration file.

    default
        With no arguments, prints the default cluster. With <cluster-name>, sets
        the default cluster.

    migrate-domain
        Migrates the cluster's base domain from the current one to <domain>.

        New certificates will be generated for the controller/dashboard and new
        routes will be added with the pattern <app-name>.<domain> for each app.

    backup
        Takes a backup of the cluster.

        The backup may be restored while creating a new cluster with
        'flynn-host bootstrap --from-backup'.

        options:
            --file=<backup-file>  file to write backup to (defaults to stdout)

Examples:

	$ flynn cluster add -p KGCENkp53YF5OvOKkZIry71+czFRkSw2ZdMszZ/0ljs= default dev.localflynn.com e09dc5301d72be755a3d666f617c4600
	Cluster "default" added.

	$ flynn cluster migrate-domain new.example.com
	Migrate cluster domain from "example.com" to "new.example.com"? (yes/no): yes
	Migrating cluster domain (this can take up to 2m0s)...
	Changed cluster domain from "example.com" to "new.example.com"
`)
}

func runCluster(args *docopt.Args) error {
	if err := readConfig(); err != nil {
		return err
	}

	if args.Bool["add"] {
		return runClusterAdd(args)
	} else if args.Bool["remove"] {
		return runClusterRemove(args)
	} else if args.Bool["default"] {
		return runClusterDefault(args)
	} else if args.Bool["migrate-domain"] {
		return runClusterMigrateDomain(args)
	} else if args.Bool["backup"] {
		return runClusterBackup(args)
	}

	w := tabWriter()
	defer w.Flush()

	listRec(w, "NAME", "CONTROLLER URL", "GIT URL", "DOCKER URL")
	for _, s := range config.Clusters {
		gitURL := s.GitURL
		if gitURL == "" {
			gitURL = "(none)"
		}
		dockerURL := s.DockerPushURL
		if dockerURL == "" {
			dockerURL = "(none)"
		}
		data := []interface{}{s.Name, s.ControllerURL, gitURL, dockerURL}
		if s.Name == config.Default {
			data = append(data, "(default)")
		}
		listRec(w, data...)
	}
	return nil
}

func runClusterAdd(args *docopt.Args) error {
	s := &cfg.Cluster{
		Name:          args.String["<cluster-name>"],
		Key:           args.String["<key>"],
		GitURL:        args.String["--git-url"],
		DockerPushURL: args.String["--docker-push-url"],
		TLSPin:        args.String["--tls-pin"],
	}
	domain := args.String["<domain>"]
	if strings.HasPrefix(domain, "https://") {
		s.ControllerURL = domain
	} else {
		s.ControllerURL = "https://controller." + domain
	}
	if s.GitURL == "" && !args.Bool["--no-git"] {
		s.GitURL = "https://git." + domain
	}
	if s.DockerPushURL == "" && args.Bool["--docker"] {
		s.DockerPushURL = "https://docker." + domain
	}

	if err := config.Add(s, args.Bool["--force"]); err != nil {
		return err
	}

	setDefault := args.Bool["--default"] || len(config.Clusters) == 1

	if setDefault && !config.SetDefault(s.Name) {
		return errors.New(fmt.Sprintf("Cluster %q does not exist and cannot be set as default.", s.Name))
	}

	var caPath string
	if s.GitURL != "" || s.DockerPushURL != "" {
		client, err := s.Client()
		if err != nil {
			return err
		}
		caPath, err = writeCACert(client, s.Name)
		if err != nil {
			return fmt.Errorf("Error writing CA certificate: %s", err)
		}
	}

	if s.GitURL != "" {
		if _, err := exec.LookPath("git"); err != nil {
			if serr, ok := err.(*exec.Error); ok && serr.Err == exec.ErrNotFound {
				return errors.New("Executable 'git' was not found. Use --no-git to skip git configuration")
			}
			return err
		}
		if err := cfg.WriteGlobalGitConfig(s.GitURL, caPath); err != nil {
			return err
		}
	}

	if s.DockerPushURL != "" {
		host, err := s.DockerPushHost()
		if err != nil {
			return err
		}
		if err := dockerLogin(host, s.Key); err != nil {
			if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound {
				err = errors.New("Executable 'docker' was not found.")
			} else if err == ErrDockerTLSError {
				printDockerTLSWarning(host, caPath)
				err = errors.New("Error configuring docker, follow the instructions above then try again")
			}
			return err
		}
	}

	if err := config.SaveTo(configPath()); err != nil {
		return err
	}

	if setDefault {
		log.Printf("Cluster %q added and set as default.", s.Name)
	} else {
		log.Printf("Cluster %q added.", s.Name)
	}
	return nil
}

func writeCACert(c controller.Client, name string) (string, error) {
	data, err := c.GetCACert()
	if err != nil {
		return "", err
	}
	dest, err := cfg.CACertFile(name)
	if err != nil {
		return "", err
	}
	defer dest.Close()
	_, err = dest.Write(data)
	return dest.Name(), err
}

func runClusterRemove(args *docopt.Args) error {
	name := args.String["<cluster-name>"]

	if c := config.Remove(name); c != nil {
		msg := fmt.Sprintf("Cluster %q removed.", name)

		// Select next available cluster as default
		if config.Default == name && len(config.Clusters) > 0 {
			config.SetDefault(config.Clusters[0].Name)
			msg = fmt.Sprintf("Cluster %q removed and %q is now the default cluster.", name, config.Default)
		}

		if err := config.SaveTo(configPath()); err != nil {
			return err
		}

		cfg.RemoveGlobalGitConfig(c.GitURL)

		if host, err := c.DockerPushHost(); err == nil {
			dockerLogout(host)
		}

		log.Print(msg)
	}

	return nil
}

func runClusterDefault(args *docopt.Args) error {
	name := args.String["<cluster-name>"]

	if name == "" {
		w := tabWriter()
		defer w.Flush()
		listRec(w, "NAME", "URL")
		for _, s := range config.Clusters {
			if s.Name == config.Default {
				listRec(w, s.Name, s.ControllerURL, "(default)")
				break
			}
		}
		return nil
	}

	if !config.SetDefault(name) {
		return fmt.Errorf("Cluster %q does not exist and cannot be set as default.", name)
	}
	if err := config.SaveTo(configPath()); err != nil {
		return err
	}

	log.Printf("%q is now the default cluster.", name)
	return nil
}

func runClusterMigrateDomain(args *docopt.Args) error {
	cluster, err := getCluster()
	if err != nil {
		shutdown.Fatal(err)
	}
	client, err := cluster.Client()
	if err != nil {
		shutdown.Fatal(err)
	}

	dm := &ct.DomainMigration{
		Domain: args.String["<domain>"],
	}

	release, err := client.GetAppRelease("controller")
	if err != nil {
		return err
	}
	dm.OldDomain = release.Env["DEFAULT_ROUTE_DOMAIN"]

	if !promptYesNo(fmt.Sprintf("Migrate cluster domain from %q to %q?", dm.OldDomain, dm.Domain)) {
		fmt.Println("Aborted")
		return nil
	}

	maxDuration := 2 * time.Minute
	fmt.Printf("Migrating cluster domain (this can take up to %s)...\n", maxDuration)

	events := make(chan *ct.Event)
	stream, err := client.StreamEvents(ct.StreamEventsOptions{
		ObjectTypes: []ct.EventType{ct.EventTypeDomainMigration},
	}, events)
	if err != nil {
		return nil
	}
	defer stream.Close()

	if err := client.PutDomain(dm); err != nil {
		return err
	}

	timeout := time.After(maxDuration)
	for {
		select {
		case event, ok := <-events:
			if !ok {
				return stream.Err()
			}
			var e *ct.DomainMigrationEvent
			if err := json.Unmarshal(event.Data, &e); err != nil {
				return err
			}
			if e.Error != "" {
				fmt.Println(e.Error)
			}
			if e.DomainMigration.FinishedAt != nil {
				dm = e.DomainMigration
				fmt.Printf("Changed cluster domain from %q to %q\n", dm.OldDomain, dm.Domain)

				// update flynnrc
				cluster.TLSPin = dm.TLSCert.Pin
				cluster.ControllerURL = fmt.Sprintf("https://controller.%s", dm.Domain)
				cluster.GitURL = fmt.Sprintf("https://git.%s", dm.Domain)
				cluster.DockerPushURL = fmt.Sprintf("https://docker.%s", dm.Domain)
				if err := config.SaveTo(configPath()); err != nil {
					return fmt.Errorf("Error saving config: %s", err)
				}

				// update git config
				caFile, err := cfg.CACertFile(cluster.Name)
				if err != nil {
					return err
				}
				defer caFile.Close()
				if _, err := caFile.Write([]byte(dm.TLSCert.CACert)); err != nil {
					return err
				}
				if err := cfg.WriteGlobalGitConfig(cluster.GitURL, caFile.Name()); err != nil {
					return err
				}
				cfg.RemoveGlobalGitConfig(fmt.Sprintf("https://git.%s", dm.OldDomain))

				// try to run "docker login" for the new domain, but just print a warning
				// if it fails so the user can fix it later
				if host, err := cluster.DockerPushHost(); err == nil {
					if err := dockerLogin(host, cluster.Key); err == ErrDockerTLSError {
						printDockerTLSWarning(host, caFile.Name())
					}
				}
				dockerLogout(dm.OldDomain)

				fmt.Println("Updated local CLI configuration")
				return nil
			}
		case <-timeout:
			return errors.New("timed out waiting for domain migration to complete")
		}
	}
}

func runClusterBackup(args *docopt.Args) error {
	client, err := getClusterClient()
	if err != nil {
		return err
	}

	var bar *pb.ProgressBar
	var progress backup.ProgressBar
	if term.IsTerminal(os.Stderr.Fd()) {
		bar = pb.New(0)
		bar.SetUnits(pb.U_BYTES)
		bar.ShowBar = false
		bar.ShowSpeed = true
		bar.Output = os.Stderr
		bar.Start()
		progress = bar
	}

	var dest io.Writer = os.Stdout
	if filename := args.String["--file"]; filename != "" {
		f, err := os.Create(filename)
		if err != nil {
			return err
		}
		defer f.Close()
		dest = f
	}

	fmt.Fprintln(os.Stderr, "Creating cluster backup...")

	if err := backup.Run(client, dest, progress); err != nil {
		return err
	}
	if bar != nil {
		bar.Finish()
	}
	fmt.Fprintln(os.Stderr, "Backup complete.")

	return nil
}
<commit_msg>cli: Add cluster log-sink command<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net/url"
	"os"
	"os/exec"
	"strings"
	"time"

	"github.com/cheggaaa/pb"
	"github.com/docker/docker/pkg/term"
	cfg "github.com/flynn/flynn/cli/config"
	"github.com/flynn/flynn/controller/client"
	ct "github.com/flynn/flynn/controller/types"
	"github.com/flynn/flynn/pkg/backup"
	"github.com/flynn/flynn/pkg/shutdown"
	"github.com/flynn/go-docopt"
)

func init() {
	register("cluster", runCluster, `
usage: flynn cluster
       flynn cluster add [-f] [-d] [--git-url <giturl>] [--no-git] [--docker-push-url <url>] [--docker] [-p <tlspin>] <cluster-name> <domain> <key>
       flynn cluster remove <cluster-name>
       flynn cluster default [<cluster-name>]
       flynn cluster migrate-domain <domain>
       flynn cluster backup [--file <file>]
       flynn cluster log-sink
       flynn cluster log-sink add syslog [--use-ids] <url> [<prefix>]
       flynn cluster log-sink remove <id>

Manage Flynn clusters.

Commands:
    With no arguments, shows a list of configured clusters.

    add
        Adds <cluster-name> to the ~/.flynnrc configuration file.

        options:
            -f, --force               force add cluster
            -d, --default             set as default cluster
            --git-url=<giturl>        git URL
            --no-git                  skip git configuration
            --docker-push-url=<url>   Docker push URL
            --docker                  configure Docker to push to the cluster
            -p, --tls-pin=<tlspin>    SHA256 of the cluster's TLS cert

    remove
        Removes <cluster-name> from the ~/.flynnrc configuration file.

    default
        With no arguments, prints the default cluster. With <cluster-name>, sets
        the default cluster.

    migrate-domain
        Migrates the cluster's base domain from the current one to <domain>.

        New certificates will be generated for the controller/dashboard and new
        routes will be added with the pattern <app-name>.<domain> for each app.

    backup
        Takes a backup of the cluster.

        The backup may be restored while creating a new cluster with
        'flynn-host bootstrap --from-backup'.

        options:
            --file=<backup-file>  file to write backup to (defaults to stdout)

    log-sink
        With no arguments, prints a list of registerd log-sinks for this cluster

    log-sink add syslog
        Creates a new syslog log sink with specified <url> and optionally <prefix> template.
        Supported schemes are syslog and syslog+tls

        options:
            --use-ids  Use app IDs instead of app names in the syslog APP-NAME field

        examples:
			$ flynn cluster log-sink add syslog syslog+tls://rsyslog.host:514/

    log-sink remove
        Removes a log sink with <id>

Examples:

	$ flynn cluster add -p KGCENkp53YF5OvOKkZIry71+czFRkSw2ZdMszZ/0ljs= default dev.localflynn.com e09dc5301d72be755a3d666f617c4600
	Cluster "default" added.

	$ flynn cluster migrate-domain new.example.com
	Migrate cluster domain from "example.com" to "new.example.com"? (yes/no): yes
	Migrating cluster domain (this can take up to 2m0s)...
	Changed cluster domain from "example.com" to "new.example.com"
`)
}

func runCluster(args *docopt.Args) error {
	if err := readConfig(); err != nil {
		return err
	}

	if args.Bool["log-sink"] {
		return runLogSink(args)
	} else if args.Bool["add"] {
		return runClusterAdd(args)
	} else if args.Bool["remove"] {
		return runClusterRemove(args)
	} else if args.Bool["default"] {
		return runClusterDefault(args)
	} else if args.Bool["migrate-domain"] {
		return runClusterMigrateDomain(args)
	} else if args.Bool["backup"] {
		return runClusterBackup(args)
	}

	w := tabWriter()
	defer w.Flush()

	listRec(w, "NAME", "CONTROLLER URL", "GIT URL", "DOCKER URL")
	for _, s := range config.Clusters {
		gitURL := s.GitURL
		if gitURL == "" {
			gitURL = "(none)"
		}
		dockerURL := s.DockerPushURL
		if dockerURL == "" {
			dockerURL = "(none)"
		}
		data := []interface{}{s.Name, s.ControllerURL, gitURL, dockerURL}
		if s.Name == config.Default {
			data = append(data, "(default)")
		}
		listRec(w, data...)
	}
	return nil
}

func runClusterAdd(args *docopt.Args) error {
	s := &cfg.Cluster{
		Name:          args.String["<cluster-name>"],
		Key:           args.String["<key>"],
		GitURL:        args.String["--git-url"],
		DockerPushURL: args.String["--docker-push-url"],
		TLSPin:        args.String["--tls-pin"],
	}
	domain := args.String["<domain>"]
	if strings.HasPrefix(domain, "https://") {
		s.ControllerURL = domain
	} else {
		s.ControllerURL = "https://controller." + domain
	}
	if s.GitURL == "" && !args.Bool["--no-git"] {
		s.GitURL = "https://git." + domain
	}
	if s.DockerPushURL == "" && args.Bool["--docker"] {
		s.DockerPushURL = "https://docker." + domain
	}

	if err := config.Add(s, args.Bool["--force"]); err != nil {
		return err
	}

	setDefault := args.Bool["--default"] || len(config.Clusters) == 1

	if setDefault && !config.SetDefault(s.Name) {
		return errors.New(fmt.Sprintf("Cluster %q does not exist and cannot be set as default.", s.Name))
	}

	var caPath string
	if s.GitURL != "" || s.DockerPushURL != "" {
		client, err := s.Client()
		if err != nil {
			return err
		}
		caPath, err = writeCACert(client, s.Name)
		if err != nil {
			return fmt.Errorf("Error writing CA certificate: %s", err)
		}
	}

	if s.GitURL != "" {
		if _, err := exec.LookPath("git"); err != nil {
			if serr, ok := err.(*exec.Error); ok && serr.Err == exec.ErrNotFound {
				return errors.New("Executable 'git' was not found. Use --no-git to skip git configuration")
			}
			return err
		}
		if err := cfg.WriteGlobalGitConfig(s.GitURL, caPath); err != nil {
			return err
		}
	}

	if s.DockerPushURL != "" {
		host, err := s.DockerPushHost()
		if err != nil {
			return err
		}
		if err := dockerLogin(host, s.Key); err != nil {
			if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound {
				err = errors.New("Executable 'docker' was not found.")
			} else if err == ErrDockerTLSError {
				printDockerTLSWarning(host, caPath)
				err = errors.New("Error configuring docker, follow the instructions above then try again")
			}
			return err
		}
	}

	if err := config.SaveTo(configPath()); err != nil {
		return err
	}

	if setDefault {
		log.Printf("Cluster %q added and set as default.", s.Name)
	} else {
		log.Printf("Cluster %q added.", s.Name)
	}
	return nil
}

func writeCACert(c controller.Client, name string) (string, error) {
	data, err := c.GetCACert()
	if err != nil {
		return "", err
	}
	dest, err := cfg.CACertFile(name)
	if err != nil {
		return "", err
	}
	defer dest.Close()
	_, err = dest.Write(data)
	return dest.Name(), err
}

func runClusterRemove(args *docopt.Args) error {
	name := args.String["<cluster-name>"]

	if c := config.Remove(name); c != nil {
		msg := fmt.Sprintf("Cluster %q removed.", name)

		// Select next available cluster as default
		if config.Default == name && len(config.Clusters) > 0 {
			config.SetDefault(config.Clusters[0].Name)
			msg = fmt.Sprintf("Cluster %q removed and %q is now the default cluster.", name, config.Default)
		}

		if err := config.SaveTo(configPath()); err != nil {
			return err
		}

		cfg.RemoveGlobalGitConfig(c.GitURL)

		if host, err := c.DockerPushHost(); err == nil {
			dockerLogout(host)
		}

		log.Print(msg)
	}

	return nil
}

func runClusterDefault(args *docopt.Args) error {
	name := args.String["<cluster-name>"]

	if name == "" {
		w := tabWriter()
		defer w.Flush()
		listRec(w, "NAME", "URL")
		for _, s := range config.Clusters {
			if s.Name == config.Default {
				listRec(w, s.Name, s.ControllerURL, "(default)")
				break
			}
		}
		return nil
	}

	if !config.SetDefault(name) {
		return fmt.Errorf("Cluster %q does not exist and cannot be set as default.", name)
	}
	if err := config.SaveTo(configPath()); err != nil {
		return err
	}

	log.Printf("%q is now the default cluster.", name)
	return nil
}

func runClusterMigrateDomain(args *docopt.Args) error {
	cluster, err := getCluster()
	if err != nil {
		shutdown.Fatal(err)
	}
	client, err := cluster.Client()
	if err != nil {
		shutdown.Fatal(err)
	}

	dm := &ct.DomainMigration{
		Domain: args.String["<domain>"],
	}

	release, err := client.GetAppRelease("controller")
	if err != nil {
		return err
	}
	dm.OldDomain = release.Env["DEFAULT_ROUTE_DOMAIN"]

	if !promptYesNo(fmt.Sprintf("Migrate cluster domain from %q to %q?", dm.OldDomain, dm.Domain)) {
		fmt.Println("Aborted")
		return nil
	}

	maxDuration := 2 * time.Minute
	fmt.Printf("Migrating cluster domain (this can take up to %s)...\n", maxDuration)

	events := make(chan *ct.Event)
	stream, err := client.StreamEvents(ct.StreamEventsOptions{
		ObjectTypes: []ct.EventType{ct.EventTypeDomainMigration},
	}, events)
	if err != nil {
		return nil
	}
	defer stream.Close()

	if err := client.PutDomain(dm); err != nil {
		return err
	}

	timeout := time.After(maxDuration)
	for {
		select {
		case event, ok := <-events:
			if !ok {
				return stream.Err()
			}
			var e *ct.DomainMigrationEvent
			if err := json.Unmarshal(event.Data, &e); err != nil {
				return err
			}
			if e.Error != "" {
				fmt.Println(e.Error)
			}
			if e.DomainMigration.FinishedAt != nil {
				dm = e.DomainMigration
				fmt.Printf("Changed cluster domain from %q to %q\n", dm.OldDomain, dm.Domain)

				// update flynnrc
				cluster.TLSPin = dm.TLSCert.Pin
				cluster.ControllerURL = fmt.Sprintf("https://controller.%s", dm.Domain)
				cluster.GitURL = fmt.Sprintf("https://git.%s", dm.Domain)
				cluster.DockerPushURL = fmt.Sprintf("https://docker.%s", dm.Domain)
				if err := config.SaveTo(configPath()); err != nil {
					return fmt.Errorf("Error saving config: %s", err)
				}

				// update git config
				caFile, err := cfg.CACertFile(cluster.Name)
				if err != nil {
					return err
				}
				defer caFile.Close()
				if _, err := caFile.Write([]byte(dm.TLSCert.CACert)); err != nil {
					return err
				}
				if err := cfg.WriteGlobalGitConfig(cluster.GitURL, caFile.Name()); err != nil {
					return err
				}
				cfg.RemoveGlobalGitConfig(fmt.Sprintf("https://git.%s", dm.OldDomain))

				// try to run "docker login" for the new domain, but just print a warning
				// if it fails so the user can fix it later
				if host, err := cluster.DockerPushHost(); err == nil {
					if err := dockerLogin(host, cluster.Key); err == ErrDockerTLSError {
						printDockerTLSWarning(host, caFile.Name())
					}
				}
				dockerLogout(dm.OldDomain)

				fmt.Println("Updated local CLI configuration")
				return nil
			}
		case <-timeout:
			return errors.New("timed out waiting for domain migration to complete")
		}
	}
}

func runClusterBackup(args *docopt.Args) error {
	client, err := getClusterClient()
	if err != nil {
		return err
	}

	var bar *pb.ProgressBar
	var progress backup.ProgressBar
	if term.IsTerminal(os.Stderr.Fd()) {
		bar = pb.New(0)
		bar.SetUnits(pb.U_BYTES)
		bar.ShowBar = false
		bar.ShowSpeed = true
		bar.Output = os.Stderr
		bar.Start()
		progress = bar
	}

	var dest io.Writer = os.Stdout
	if filename := args.String["--file"]; filename != "" {
		f, err := os.Create(filename)
		if err != nil {
			return err
		}
		defer f.Close()
		dest = f
	}

	fmt.Fprintln(os.Stderr, "Creating cluster backup...")

	if err := backup.Run(client, dest, progress); err != nil {
		return err
	}
	if bar != nil {
		bar.Finish()
	}
	fmt.Fprintln(os.Stderr, "Backup complete.")

	return nil
}

func runLogSink(args *docopt.Args) error {
	client, err := getClusterClient()
	if err != nil {
		return err
	}

	if args.Bool["add"] {
		switch {
		case args.Bool["syslog"]:
			return runLogSinkAddSyslog(args, client)
		default:
			return fmt.Errorf("Sink kind not supported")
		}
	}
	if args.Bool["remove"] {
		return runLogSinkRemove(args, client)
	}

	sinks, err := client.ListSinks()
	if err != nil {
		return err
	}

	w := tabWriter()
	defer w.Flush()

	listRec(w, "ID", "KIND", "CONFIG")
	for _, j := range sinks {
		listRec(w, j.ID, j.Kind, string(j.Config))
	}

	return nil
}

func runLogSinkAddSyslog(args *docopt.Args, client controller.Client) error {
	u, err := url.Parse(args.String["<url>"])
	if err != nil {
		return fmt.Errorf("Invalid syslog URL: %s", err)
	}
	switch u.Scheme {
	case "syslog", "syslog+tls":
	default:
		return fmt.Errorf("Invalid syslog protocol: %s", u.Scheme)
	}

	config, _ := json.Marshal(ct.SyslogSinkConfig{
		Prefix: args.String["<prefix>"],
		URL:    u.String(),
		UseIDs: args.Bool["--use-ids"],
	})

	sink := &ct.Sink{
		Kind:   ct.SinkKindSyslog,
		Config: config,
	}

	if err := client.CreateSink(sink); err != nil {
		return err
	}

	log.Printf("Created sink %s.", sink.ID)

	return nil
}

func runLogSinkRemove(args *docopt.Args, client controller.Client) error {
	id := args.String["<id>"]

	res, err := client.DeleteSink(id)
	if err != nil {
		return err
	}

	log.Printf("Deleted sink %s.", res.ID)

	return nil
}
<|endoftext|>