<commit_before>package csv

import (
	"bytes"
	"encoding/csv"
	"errors"
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

// Row is one row of CSV data, indexed by column name or position
type Row struct {
	Columns *[]string // The name of the columns, in order
	Data    []string  // the data for the row
}

// At returns the rows data for the column positon i
func (r *Row) At(i int) string {
	return r.Data[i]
}

// Named reutrns the row's data for the first columne named 'n'
func (r *Row) Named(n string) (string, error) {
	for i, cn := range *r.Columns {
		if cn == n {
			return r.At(i), nil
		}
	}

	return "", fmt.Errorf("No column found for %s", n)
}

type decoder struct {
	*csv.Reader                // the csv document for input
	reflect.Type               // the underlying struct to decode
	out          reflect.Value // the slice output
	fms          []cfield      //
	cols         []string      // colum names
}

type decoderFn func(*reflect.Value, *Row) error

// maps a CSV column Name and index to a StructField
type cfield struct {
	colName     string
	colIndex    int
	structField *reflect.StructField
	decode      decoderFn
}

// Unmarshaler is the interface implemented by objects which can unmarshall the CSV row itself.
type Unmarshaler interface {

	// UnmarshalCSV receives a string with the column value matching this field
	// and a reference to the the current row.
	// This allows composing a value from mutliple columns.
	UnmarshalCSV(string, *Row) error
}

// Unmarshal decodes the CSV document into the slice interface.
func Unmarshal(doc []byte, v interface{}) error {
	if err := checkValidInterface(v); err != nil {
		return err
	}

	rv := reflect.ValueOf(v).Elem()
	dec, err := newDecoder(doc, rv)

	if err != nil {
		return err
	}

	dec.unmarshal()
	return nil
}

func (dec *decoder) unmarshal() error {
	for {
		raw, err := dec.Read()

		if err != nil {
			break
		} else {
			row := dec.newRow(raw)
			o := reflect.New(dec.Type).Elem()
			err := dec.set(row, &o)
			if err != nil {
				return err
			}
			dec.out.Set(reflect.Append(dec.out, o))
		}

	}

	return nil

}

func (dec *decoder) newRow(raw []string) *Row {
	return &Row{
		Columns: &dec.cols,
		Data:    raw,
	}
}

func checkValidInterface(v interface{}) error {
	pv := reflect.ValueOf(v)

	if pv.Kind() != reflect.Ptr || pv.IsNil() {
		return errors.New("type is nil or not a pointer")
	}

	rv := reflect.ValueOf(v).Elem()

	if rv.Kind() != reflect.Slice {
		return fmt.Errorf("only slices are allowed: %s", rv.Kind())
	}

	return nil
}

// interface is implemented on a value
const impsVal int = 1

// interface is implemented on a pointer
const impsPtr int = 2

// checks if an object implements the Unmarshaler interface
func impsUnmarshaller(et reflect.Type, i interface{}) (int, error) {
	el := reflect.New(et).Elem()
	it := reflect.TypeOf(i).Elem()

	if el.Type().Implements(it) {
		return impsVal, nil
	}

	if el.Addr().Type().Implements(it) {
		return impsPtr, nil
	}

	return 0, fmt.Errorf("%v el does not implement %s", el, it.Name())
}

// mapFields creates a set of fieldMap instrances where
// the CSV colnames and the exported field names intersect
func (dec *decoder) mapFieldsToCols(t reflect.Type, cols []string) {
	pFields := exportedFields(t)

	cMap := map[string]int{}

	for i, col := range cols {
		cMap[col] = i
	}

	for _, f := range pFields {

		name, ok := fieldHeaderName(*f)
		if ok == false {
			continue
		}

		index, ok := cMap[name]

		if ok == true {
			fm := cfield{
				colName:     name,
				colIndex:    index,
				structField: f,
			}

			if code, err := impsUnmarshaller(f.Type, new(Unmarshaler)); err == nil {
				fm.assignUnmarshaller(code)
			} else {
				fm.assignDecoder()
			}

			dec.fms = append(dec.fms, fm)
		}
	}

}

func exportedFields(t reflect.Type) []*reflect.StructField {
	var out []*reflect.StructField

	v := reflect.New(t).Elem()
	flen := v.NumField()

	for i := 0; i < flen; i++ {

		sf := t.Field(i)

		if skipField(sf) {
			continue
		}

		// Work around issue with CanSet not working on struct fields
		c := string(sf.Name[0])
		if c == strings.ToUpper(c) {
			out = append(out, &sf)
		}
	}

	return out

}

func newDecoder(doc []byte, rv reflect.Value) (*decoder, error) {
	b := bytes.NewReader(doc)
	r := csv.NewReader(b)
	cols, err := r.Read()

	if err != nil {
		return nil, err
	}

	ch := []string(cols)
	el := rv.Type().Elem()

	dec := decoder{
		Reader: r,
		Type:   el,
		out:    rv,
		cols:   cols,
	}

	dec.mapFieldsToCols(el, ch)

	return &dec, nil
}

func assign(fm *cfield, fn decoderFn) {
	fm.decode = func(f *reflect.Value, row *Row) error {
		return fn(f, row)
	}
}

func (fm *cfield) assignUnmarshaller(code int) {
	if code == impsPtr {
		assign(fm, fm.unmarshalPointer)
	} else {
		assign(fm, fm.unmarshalValue)
	}
}

func (fm *cfield) unmarshalPointer(f *reflect.Value, row *Row) error {
	val := row.At(fm.colIndex)
	m := f.Addr().Interface().(Unmarshaler)
	m.UnmarshalCSV(val, row)

	return nil
}

func (fm *cfield) unmarshalValue(f *reflect.Value, row *Row) error {
	val := row.At(fm.colIndex)
	m := f.Interface().(Unmarshaler)
	m.UnmarshalCSV(val, row)
	return nil
}

func (fm *cfield) assignDecoder() {
	switch fm.structField.Type.Kind() {
	case reflect.String:
		assign(fm, fm.decodeString)
	case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int8:
		assign(fm, fm.decodeInt)
	case reflect.Float32:
		assign(fm, fm.decodeFloat(32))
	case reflect.Float64:
		assign(fm, fm.decodeFloat(64))
	case reflect.Bool:
		assign(fm, fm.decodeBool)
	default:
		assign(fm, fm.ignoreValue)
	}
}

// Sets each field value for the el struct for the given row
func (dec *decoder) set(row *Row, el *reflect.Value) error {
	for _, fm := range dec.fms {
		field := fm.structField

		f := el.FieldByName(field.Name)
		if fm.decode != nil {
			err := fm.decode(&f, row)

			if err != nil {
				return err
			}
		} else {
			return fmt.Errorf("no decoder for %v\n", field.Name)
		}
	}

	return nil
}

func (fm *cfield) decodeBool(f *reflect.Value, row *Row) error {
	val := row.At(fm.colIndex)
	var bv bool

	bt := fm.structField.Tag.Get("true")
	bf := fm.structField.Tag.Get("false")

	switch val {
	case bt:
		bv = true
	case bf:
		bv = false
	default:
		bv = true
	}

	f.SetBool(bv)

	return nil
}

func (fm *cfield) decodeInt(f *reflect.Value, row *Row) error {
	val := row.At(fm.colIndex)
	i, e := strconv.Atoi(val)

	if e != nil {
		return e
	}

	f.SetInt(int64(i))
	return nil
}

func (fm *cfield) decodeString(f *reflect.Value, row *Row) error {
	val := row.At(fm.colIndex)
	f.SetString(val)

	return nil
}

func (fm *cfield) decodeFloat(bit int) decoderFn {
	return func(f *reflect.Value, row *Row) error {
		val := row.At(fm.colIndex)
		n, err := strconv.ParseFloat(val, bit)

		if err != nil {
			return err
		}

		f.SetFloat(n)

		return nil
	}
}

// ignoreValue does nothing. This is for unsupported types.
func (fm *cfield) ignoreValue(f *reflect.Value, row *Row) error {
	return nil
}
<commit_msg>don’t embed the csv reader, too many methods<commit_after>package csv

import (
	"bytes"
	"encoding/csv"
	"errors"
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

// Row is one row of CSV data, indexed by column name or position
type Row struct {
	Columns *[]string // The name of the columns, in order
	Data    []string  // the data for the row
}

// At returns the rows data for the column positon i
func (r *Row) At(i int) string {
	return r.Data[i]
}

// Named reutrns the row's data for the first columne named 'n'
func (r *Row) Named(n string) (string, error) {
	for i, cn := range *r.Columns {
		if cn == n {
			return r.At(i), nil
		}
	}

	return "", fmt.Errorf("No column found for %s", n)
}

type decoder struct {
	csv          *csv.Reader   // the csv document for input
	reflect.Type               // the underlying struct to decode
	out          reflect.Value // the slice output
	fms          []cfield      //
	cols         []string      // colum names
}

type decoderFn func(*reflect.Value, *Row) error

// maps a CSV column Name and index to a StructField
type cfield struct {
	colName     string
	colIndex    int
	structField *reflect.StructField
	decode      decoderFn
}

// Unmarshaler is the interface implemented by objects which can unmarshall the CSV row itself.
type Unmarshaler interface {

	// UnmarshalCSV receives a string with the column value matching this field
	// and a reference to the the current row.
	// This allows composing a value from mutliple columns.
	UnmarshalCSV(string, *Row) error
}

// Unmarshal decodes the CSV document into the slice interface.
func Unmarshal(doc []byte, v interface{}) error {
	if err := checkValidInterface(v); err != nil {
		return err
	}

	rv := reflect.ValueOf(v).Elem()
	dec, err := newDecoder(doc, rv)

	if err != nil {
		return err
	}

	dec.unmarshal()
	return nil
}

func (dec *decoder) unmarshal() error {
	for {
		raw, err := dec.csv.Read()

		if err != nil {
			break
		} else {
			row := dec.newRow(raw)
			o := reflect.New(dec.Type).Elem()
			err := dec.set(row, &o)
			if err != nil {
				return err
			}
			dec.out.Set(reflect.Append(dec.out, o))
		}

	}

	return nil

}

func (dec *decoder) newRow(raw []string) *Row {
	return &Row{
		Columns: &dec.cols,
		Data:    raw,
	}
}

func checkValidInterface(v interface{}) error {
	pv := reflect.ValueOf(v)

	if pv.Kind() != reflect.Ptr || pv.IsNil() {
		return errors.New("type is nil or not a pointer")
	}

	rv := reflect.ValueOf(v).Elem()

	if rv.Kind() != reflect.Slice {
		return fmt.Errorf("only slices are allowed: %s", rv.Kind())
	}

	return nil
}

// interface is implemented on a value
const impsVal int = 1

// interface is implemented on a pointer
const impsPtr int = 2

// checks if an object implements the Unmarshaler interface
func impsUnmarshaller(et reflect.Type, i interface{}) (int, error) {
	el := reflect.New(et).Elem()
	it := reflect.TypeOf(i).Elem()

	if el.Type().Implements(it) {
		return impsVal, nil
	}

	if el.Addr().Type().Implements(it) {
		return impsPtr, nil
	}

	return 0, fmt.Errorf("%v el does not implement %s", el, it.Name())
}

// mapFields creates a set of fieldMap instrances where
// the CSV colnames and the exported field names intersect
func (dec *decoder) mapFieldsToCols(t reflect.Type, cols []string) {
	pFields := exportedFields(t)

	cMap := map[string]int{}

	for i, col := range cols {
		cMap[col] = i
	}

	for _, f := range pFields {

		name, ok := fieldHeaderName(*f)
		if ok == false {
			continue
		}

		index, ok := cMap[name]

		if ok == true {
			fm := cfield{
				colName:     name,
				colIndex:    index,
				structField: f,
			}

			if code, err := impsUnmarshaller(f.Type, new(Unmarshaler)); err == nil {
				fm.assignUnmarshaller(code)
			} else {
				fm.assignDecoder()
			}

			dec.fms = append(dec.fms, fm)
		}
	}

}

func exportedFields(t reflect.Type) []*reflect.StructField {
	var out []*reflect.StructField

	v := reflect.New(t).Elem()
	flen := v.NumField()

	for i := 0; i < flen; i++ {

		sf := t.Field(i)

		if skipField(sf) {
			continue
		}

		// Work around issue with CanSet not working on struct fields
		c := string(sf.Name[0])
		if c == strings.ToUpper(c) {
			out = append(out, &sf)
		}
	}

	return out

}

func newDecoder(doc []byte, rv reflect.Value) (*decoder, error) {
	b := bytes.NewReader(doc)
	r := csv.NewReader(b)
	cols, err := r.Read()

	if err != nil {
		return nil, err
	}

	el := rv.Type().Elem()

	dec := decoder{
		Type: el,
		csv:  r,
		out:  rv,
		cols: cols,
	}

	dec.mapFieldsToCols(el, cols)

	return &dec, nil
}

func assign(fm *cfield, fn decoderFn) {
	fm.decode = func(f *reflect.Value, row *Row) error {
		return fn(f, row)
	}
}

func (fm *cfield) assignUnmarshaller(code int) {
	if code == impsPtr {
		assign(fm, fm.unmarshalPointer)
	} else {
		assign(fm, fm.unmarshalValue)
	}
}

func (fm *cfield) unmarshalPointer(f *reflect.Value, row *Row) error {
	val := row.At(fm.colIndex)
	m := f.Addr().Interface().(Unmarshaler)
	m.UnmarshalCSV(val, row)

	return nil
}

func (fm *cfield) unmarshalValue(f *reflect.Value, row *Row) error {
	val := row.At(fm.colIndex)
	m := f.Interface().(Unmarshaler)
	m.UnmarshalCSV(val, row)
	return nil
}

func (fm *cfield) assignDecoder() {
	switch fm.structField.Type.Kind() {
	case reflect.String:
		assign(fm, fm.decodeString)
	case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int8:
		assign(fm, fm.decodeInt)
	case reflect.Float32:
		assign(fm, fm.decodeFloat(32))
	case reflect.Float64:
		assign(fm, fm.decodeFloat(64))
	case reflect.Bool:
		assign(fm, fm.decodeBool)
	default:
		assign(fm, fm.ignoreValue)
	}
}

// Sets each field value for the el struct for the given row
func (dec *decoder) set(row *Row, el *reflect.Value) error {
	for _, fm := range dec.fms {
		field := fm.structField

		f := el.FieldByName(field.Name)
		if fm.decode != nil {
			err := fm.decode(&f, row)

			if err != nil {
				return err
			}
		} else {
			return fmt.Errorf("no decoder for %v\n", field.Name)
		}
	}

	return nil
}

func (fm *cfield) decodeBool(f *reflect.Value, row *Row) error {
	val := row.At(fm.colIndex)
	var bv bool

	bt := fm.structField.Tag.Get("true")
	bf := fm.structField.Tag.Get("false")

	switch val {
	case bt:
		bv = true
	case bf:
		bv = false
	default:
		bv = true
	}

	f.SetBool(bv)

	return nil
}

func (fm *cfield) decodeInt(f *reflect.Value, row *Row) error {
	val := row.At(fm.colIndex)
	i, e := strconv.Atoi(val)

	if e != nil {
		return e
	}

	f.SetInt(int64(i))
	return nil
}

func (fm *cfield) decodeString(f *reflect.Value, row *Row) error {
	val := row.At(fm.colIndex)
	f.SetString(val)

	return nil
}

func (fm *cfield) decodeFloat(bit int) decoderFn {
	return func(f *reflect.Value, row *Row) error {
		val := row.At(fm.colIndex)
		n, err := strconv.ParseFloat(val, bit)

		if err != nil {
			return err
		}

		f.SetFloat(n)

		return nil
	}
}

// ignoreValue does nothing. This is for unsupported types.
func (fm *cfield) ignoreValue(f *reflect.Value, row *Row) error {
	return nil
}
<|endoftext|>


<commit_before>package deploy

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"regexp"
	"strings"
	"time"

	"github.com/github/hub/git"
	"github.com/remind101/deploy/Godeps/_workspace/src/github.com/codegangsta/cli"
	hub "github.com/remind101/deploy/Godeps/_workspace/src/github.com/github/hub/github"
	"github.com/remind101/deploy/Godeps/_workspace/src/github.com/google/go-github/github"
)

const (
	Name  = "deploy"
	Usage = "A command for creating GitHub deployments"
)

const (
	DefaultRef     = "master"
	DefaultTimeout = 20 * time.Second
)

var errTimeout = errors.New("Timed out waiting for build to start. Did you add a webhook to handle deployment events?")

func init() {
	cli.AppHelpTemplate = `USAGE:
   # Deploy the master branch of remind101/acme-inc to staging
   {{.Name}} --env=staging --ref=master remind101/acme-inc

   # Deploy HEAD of the current branch to staging
   {{.Name}} --env=staging remind101/acme-inc

   # Deploy the current GitHub repo to staging
   {{.Name}} --env=staging
{{if .Flags}}
OPTIONS:
   {{range .Flags}}{{.}}
   {{end}}{{end}}
`
}

var env string
var ProtectedEnvironments = map[string]bool{
	"production": true,
}

var flags = []cli.Flag{
	cli.StringFlag{
		Name:  "ref, branch, commit, tag",
		Value: "",
		Usage: "The git ref to deploy. Can be a git commit, branch or tag.",
	},
	cli.StringFlag{
		Name:        "env, e",
		Value:       "",
		Usage:       "The environment to deploy to.",
		Destination: &env,
	},
	cli.BoolFlag{
		Name:  "force, f",
		Usage: "Ignore commit status checks.",
	},
	cli.BoolFlag{
		Name:  "detached, d",
		Usage: "Don't wait for the deployment to complete.",
	},
	cli.BoolFlag{
		Name:  "quiet, q",
		Usage: "Silence any output to STDOUT.",
	},
	cli.BoolFlag{
		Name:  "update, u",
		Usage: "Update the binary",
	},
}

// NewApp returns a new cli.App for the deploy command.
func NewApp() *cli.App {
	app := cli.NewApp()
	app.Version = Version
	app.Name = Name
	app.Usage = Usage
	app.Flags = flags
	app.Action = func(c *cli.Context) {
		if c.Bool("update") {
			updater := NewUpdater()
			if err := updater.Update(); err != nil {
				fmt.Println(err)
				os.Exit(-1)
			} else {
				os.Exit(0)
			}
		}

		if err := RunDeploy(c); err != nil {
			msg := err.Error()
			if err, ok := err.(*github.ErrorResponse); ok {
				if strings.HasPrefix(err.Message, "Conflict: Commit status checks failed for") {
					msg = "Commit status checks failed. You can bypass commit status checks with the --force flag."
				} else if strings.HasPrefix(err.Message, "No ref found for") {
					msg = fmt.Sprintf("%s. Did you push it to GitHub?", err.Message)
				} else {
					msg = err.Message
				}
			}

			fmt.Println(msg)
			os.Exit(-1)
		}
	}

	return app
}

// RunDeploy performs a deploy.
func RunDeploy(c *cli.Context) error {
	var w io.Writer
	if c.Bool("quiet") {
		w = ioutil.Discard
	} else {
		w = c.App.Writer
	}

	h, err := hub.CurrentConfig().PromptForHost("github.com")
	if err != nil {
		return err
	}

	client, err := newGitHubClient(h)
	if err != nil {
		return err
	}

	nwo, err := Repo(c.Args())
	if err != nil {
		return err
	}

	owner, repo, err := SplitRepo(nwo, os.Getenv("GITHUB_ORGANIZATION"))
	if err != nil {
		return fmt.Errorf("Invalid GitHub repo: %s", nwo)
	}

	if env == "" {
		return fmt.Errorf("--env flag is required")
	}
	env = AliasEnvironment(env)

	ref := Ref(c.String("ref"), git.Head)
	err = displayNewCommits(owner, repo, ref, env, client)
	if err != nil {
		return err
	}

	r, err := newDeploymentRequest(c, ref, env)
	if err != nil {
		return err
	}

	fmt.Fprintf(w, "Deploying %s/%s@%s to %s...\n", owner, repo, *r.Ref, *r.Environment)

	if c.Bool("detached") {
		return nil
	}

	d, _, err := client.Repositories.CreateDeployment(owner, repo, r)
	if err != nil {
		return err
	}

	started := make(chan *github.DeploymentStatus)
	completed := make(chan *github.DeploymentStatus)

	go func() {
		started <- waitState(pendingStates, owner, repo, *d.ID, client)
	}()

	go func() {
		completed <- waitState(completedStates, owner, repo, *d.ID, client)
	}()

	select {
	case <-time.After(DefaultTimeout):
		return errTimeout
	case status := <-started:
		var url string
		if status.TargetURL != nil {
			url = *status.TargetURL
		}
		fmt.Fprintf(w, "%s\n", url)
	}

	status := <-completed

	if isFailed(*status.State) {
		return errors.New("Failed to deploy")
	}

	return nil
}

func displayNewCommits(owner string, repo string, ref string, env string, client *github.Client) error {
	opt := &github.DeploymentsListOptions{
		Environment: env,
	}

	deployments, _, err := client.Repositories.ListDeployments(owner, repo, opt)
	if err != nil || len(deployments) == 0 {
		return err
	}

	sha := *deployments[0].SHA
	compare, _, err := client.Repositories.CompareCommits(owner, repo, sha, ref)
	if err != nil || len(compare.Commits) == 0 {
		return err
	}

	fmt.Println("Deploying the following commits:\n")
	for _, commit := range compare.Commits {
		message := *commit.Commit.Message
		fmt.Printf("%-20s\t%s\n", *commit.Commit.Author.Name, strings.Split(message, "\n")[0])
	}
	fmt.Printf("\nSee entire diff here: https://github.com/%s/%s/compare/%s...%s\n\n", owner, repo, sha, ref)
	return nil
}

var EnvironmentAliases = map[string]string{
	"prod":  "production",
	"stage": "staging",
}

func AliasEnvironment(env string) string {
	if a, ok := EnvironmentAliases[env]; ok {
		return a
	}

	return env
}

func newDeploymentRequest(c *cli.Context, ref string, env string) (*github.DeploymentRequest, error) {
	if ProtectedEnvironments[env] {
		yes := askYN(fmt.Sprintf("Are you sure you want to deploy %s to %s?", ref, env))
		if !yes {
			return nil, fmt.Errorf("Deployment aborted.")
		}
	}

	var contexts *[]string
	if c.Bool("force") {
		s := []string{}
		contexts = &s
	}

	return &github.DeploymentRequest{
		Ref:              github.String(ref),
		Task:             github.String("deploy"),
		AutoMerge:        github.Bool(false),
		Environment:      github.String(env),
		RequiredContexts: contexts,
		Payload: map[string]interface{}{
			"force": c.Bool("force"),
		},
		// TODO Description:
	}, nil
}

var (
	pendingStates   = []string{"pending"}
	completedStates = []string{"success", "error", "failure"}
)

func isFailed(state string) bool {
	return state == "error" || state == "failure"
}

// waitState waits for a deployment status that matches the given states, then
// sends on the returned channel.
func waitState(states []string, owner, repo string, deploymentID int, c *github.Client) *github.DeploymentStatus {
	for {
		<-time.After(1 * time.Second)

		statuses, _, err := c.Repositories.ListDeploymentStatuses(owner, repo, deploymentID, nil)
		if err != nil {
			continue
		}

		status := firstStatus(states, statuses)
		if status != nil {
			return status
		}
	}
}

// firstStatus takes a slice of github.DeploymentStatus and returns the
// first status that matches the provided slice of states.
func firstStatus(states []string, statuses []github.DeploymentStatus) *github.DeploymentStatus {
	for _, ds := range statuses {
		for _, s := range states {
			if ds.State != nil && *ds.State == s {
				return &ds
			}
		}
	}

	return nil
}

// refRegex is a regular expression that matches a full git HEAD ref.
var refRegex = regexp.MustCompile(`^refs/heads/(.*)$`)

// Ref attempts to return the proper git ref to deploy. If a ref is provided,
// that will be returned. If not, it will fallback to calling headFunc. If an
// error is returned (not in a git repo), then it will fallback to DefaultRef.
func Ref(ref string, headFunc func() (string, error)) string {
	if ref != "" {
		return ref
	}

	ref, err := headFunc()
	if err != nil {
		// An error means that we're either not in a GitRepo or we're
		// not on a branch. In this case, we just fallback to the
		// DefaultRef.
		return DefaultRef
	}

	// Convert `refs/heads/test-deploy` => `test-deploy`
	return refRegex.ReplaceAllString(ref, "$1")
}

// Repo will determine the correct GitHub repo to deploy to, based on a set of
// arguments.
func Repo(arguments []string) (string, error) {
	if len(arguments) != 0 {
		return arguments[0], nil
	}

	remotes, err := hub.Remotes()
	if err != nil {
		return "", err
	}

	repo := GitHubRepo(remotes)
	if repo == "" {
		return repo, errors.New("no GitHub repo found in .git/config")
	}

	return repo, nil
}

// A regular expression that can convert a URL.Path into a GitHub repo name.
var remoteRegex = regexp.MustCompile(`^/(.*)\.git$`)

// GitHubRepo, given a list of git remotes, will determine what the GitHub repo
// is.
func GitHubRepo(remotes []hub.Remote) string {
	// We only want to look at the `origin` remote.
	remote := findRemote("origin", remotes)
	if remote == nil {
		return ""
	}

	// Remotes that are not pointed at a GitHub repo are not valid.
	if remote.URL.Host != "github.com" {
		return ""
	}

	// Convert `/remind101/acme-inc.git` => `remind101/acme-inc`.
	return remoteRegex.ReplaceAllString(remote.URL.Path, "$1")
}

func findRemote(name string, remotes []hub.Remote) *hub.Remote {
	for _, r := range remotes {
		if r.Name == name {
			return &r
		}
	}

	return nil
}

var errInvalidRepo = errors.New("invalid repo")

// SplitRepo splits a repo string in the form remind101/acme-inc into it's owner
// and repo components.
func SplitRepo(nwo, defaultOrg string) (owner string, repo string, err error) {
	parts := strings.Split(nwo, "/")

	// If we were only given a repo name, and a default organization is set,
	// we'll use the defaultOrg as the owner.
	if len(parts) == 1 && defaultOrg != "" && parts[0] != "" {
		owner = defaultOrg
		repo = parts[0]
		return
	}

	if len(parts) != 2 {
		err = errInvalidRepo
		return
	}

	owner = parts[0]
	repo = parts[1]

	return
}

func askYN(prompt string) bool {
	r := bufio.NewReader(os.Stdin)
	fmt.Printf("%s (y/N)\n", prompt)
	a, _ := r.ReadString('\n')
	return strings.ToUpper(a) == "Y\n"
}
<commit_msg>Don’t use new codegangsta cli Destination field.<commit_after>package deploy

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"regexp"
	"strings"
	"time"

	"github.com/github/hub/git"
	"github.com/remind101/deploy/Godeps/_workspace/src/github.com/codegangsta/cli"
	hub "github.com/remind101/deploy/Godeps/_workspace/src/github.com/github/hub/github"
	"github.com/remind101/deploy/Godeps/_workspace/src/github.com/google/go-github/github"
)

const (
	Name  = "deploy"
	Usage = "A command for creating GitHub deployments"
)

const (
	DefaultRef     = "master"
	DefaultTimeout = 20 * time.Second
)

var errTimeout = errors.New("Timed out waiting for build to start. Did you add a webhook to handle deployment events?")

func init() {
	cli.AppHelpTemplate = `USAGE:
   # Deploy the master branch of remind101/acme-inc to staging
   {{.Name}} --env=staging --ref=master remind101/acme-inc

   # Deploy HEAD of the current branch to staging
   {{.Name}} --env=staging remind101/acme-inc

   # Deploy the current GitHub repo to staging
   {{.Name}} --env=staging
{{if .Flags}}
OPTIONS:
   {{range .Flags}}{{.}}
   {{end}}{{end}}
`
}

var ProtectedEnvironments = map[string]bool{
	"production": true,
}

var flags = []cli.Flag{
	cli.StringFlag{
		Name:  "ref, branch, commit, tag",
		Value: "",
		Usage: "The git ref to deploy. Can be a git commit, branch or tag.",
	},
	cli.StringFlag{
		Name:  "env, e",
		Value: "",
		Usage: "The environment to deploy to.",
	},
	cli.BoolFlag{
		Name:  "force, f",
		Usage: "Ignore commit status checks.",
	},
	cli.BoolFlag{
		Name:  "detached, d",
		Usage: "Don't wait for the deployment to complete.",
	},
	cli.BoolFlag{
		Name:  "quiet, q",
		Usage: "Silence any output to STDOUT.",
	},
	cli.BoolFlag{
		Name:  "update, u",
		Usage: "Update the binary",
	},
}

// NewApp returns a new cli.App for the deploy command.
func NewApp() *cli.App {
	app := cli.NewApp()
	app.Version = Version
	app.Name = Name
	app.Usage = Usage
	app.Flags = flags
	app.Action = func(c *cli.Context) {
		if c.Bool("update") {
			updater := NewUpdater()
			if err := updater.Update(); err != nil {
				fmt.Println(err)
				os.Exit(-1)
			} else {
				os.Exit(0)
			}
		}

		if err := RunDeploy(c); err != nil {
			msg := err.Error()
			if err, ok := err.(*github.ErrorResponse); ok {
				if strings.HasPrefix(err.Message, "Conflict: Commit status checks failed for") {
					msg = "Commit status checks failed. You can bypass commit status checks with the --force flag."
				} else if strings.HasPrefix(err.Message, "No ref found for") {
					msg = fmt.Sprintf("%s. Did you push it to GitHub?", err.Message)
				} else {
					msg = err.Message
				}
			}

			fmt.Println(msg)
			os.Exit(-1)
		}
	}

	return app
}

// RunDeploy performs a deploy.
func RunDeploy(c *cli.Context) error {
	var w io.Writer
	if c.Bool("quiet") {
		w = ioutil.Discard
	} else {
		w = c.App.Writer
	}

	h, err := hub.CurrentConfig().PromptForHost("github.com")
	if err != nil {
		return err
	}

	client, err := newGitHubClient(h)
	if err != nil {
		return err
	}

	nwo, err := Repo(c.Args())
	if err != nil {
		return err
	}

	owner, repo, err := SplitRepo(nwo, os.Getenv("GITHUB_ORGANIZATION"))
	if err != nil {
		return fmt.Errorf("Invalid GitHub repo: %s", nwo)
	}

	if c.String("env") == "" {
		return fmt.Errorf("--env flag is required")
	}

	env := AliasEnvironment(c.String("env"))
	ref := Ref(c.String("ref"), git.Head)

	err = displayNewCommits(owner, repo, ref, env, client)
	if err != nil {
		return err
	}

	r, err := newDeploymentRequest(c, ref, env)
	if err != nil {
		return err
	}

	fmt.Fprintf(w, "Deploying %s/%s@%s to %s...\n", owner, repo, *r.Ref, *r.Environment)

	if c.Bool("detached") {
		return nil
	}

	d, _, err := client.Repositories.CreateDeployment(owner, repo, r)
	if err != nil {
		return err
	}

	started := make(chan *github.DeploymentStatus)
	completed := make(chan *github.DeploymentStatus)

	go func() {
		started <- waitState(pendingStates, owner, repo, *d.ID, client)
	}()

	go func() {
		completed <- waitState(completedStates, owner, repo, *d.ID, client)
	}()

	select {
	case <-time.After(DefaultTimeout):
		return errTimeout
	case status := <-started:
		var url string
		if status.TargetURL != nil {
			url = *status.TargetURL
		}
		fmt.Fprintf(w, "%s\n", url)
	}

	status := <-completed

	if isFailed(*status.State) {
		return errors.New("Failed to deploy")
	}

	return nil
}

func displayNewCommits(owner string, repo string, ref string, env string, client *github.Client) error {
	opt := &github.DeploymentsListOptions{
		Environment: env,
	}

	deployments, _, err := client.Repositories.ListDeployments(owner, repo, opt)
	if err != nil || len(deployments) == 0 {
		return err
	}

	sha := *deployments[0].SHA
	compare, _, err := client.Repositories.CompareCommits(owner, repo, sha, ref)
	if err != nil || len(compare.Commits) == 0 {
		return err
	}

	fmt.Println("Deploying the following commits:\n")
	for _, commit := range compare.Commits {
		message := *commit.Commit.Message
		fmt.Printf("%-20s\t%s\n", *commit.Commit.Author.Name, strings.Split(message, "\n")[0])
	}
	fmt.Printf("\nSee entire diff here: https://github.com/%s/%s/compare/%s...%s\n\n", owner, repo, sha, ref)
	return nil
}

var EnvironmentAliases = map[string]string{
	"prod":  "production",
	"stage": "staging",
}

func AliasEnvironment(env string) string {
	if a, ok := EnvironmentAliases[env]; ok {
		return a
	}

	return env
}

func newDeploymentRequest(c *cli.Context, ref string, env string) (*github.DeploymentRequest, error) {
	if ProtectedEnvironments[env] {
		yes := askYN(fmt.Sprintf("Are you sure you want to deploy %s to %s?", ref, env))
		if !yes {
			return nil, fmt.Errorf("Deployment aborted.")
		}
	}

	var contexts *[]string
	if c.Bool("force") {
		s := []string{}
		contexts = &s
	}

	return &github.DeploymentRequest{
		Ref:              github.String(ref),
		Task:             github.String("deploy"),
		AutoMerge:        github.Bool(false),
		Environment:      github.String(env),
		RequiredContexts: contexts,
		Payload: map[string]interface{}{
			"force": c.Bool("force"),
		},
		// TODO Description:
	}, nil
}

var (
	pendingStates   = []string{"pending"}
	completedStates = []string{"success", "error", "failure"}
)

func isFailed(state string) bool {
	return state == "error" || state == "failure"
}

// waitState waits for a deployment status that matches the given states, then
// sends on the returned channel.
func waitState(states []string, owner, repo string, deploymentID int, c *github.Client) *github.DeploymentStatus {
	for {
		<-time.After(1 * time.Second)

		statuses, _, err := c.Repositories.ListDeploymentStatuses(owner, repo, deploymentID, nil)
		if err != nil {
			continue
		}

		status := firstStatus(states, statuses)
		if status != nil {
			return status
		}
	}
}

// firstStatus takes a slice of github.DeploymentStatus and returns the
// first status that matches the provided slice of states.
func firstStatus(states []string, statuses []github.DeploymentStatus) *github.DeploymentStatus {
	for _, ds := range statuses {
		for _, s := range states {
			if ds.State != nil && *ds.State == s {
				return &ds
			}
		}
	}

	return nil
}

// refRegex is a regular expression that matches a full git HEAD ref.
var refRegex = regexp.MustCompile(`^refs/heads/(.*)$`)

// Ref attempts to return the proper git ref to deploy. If a ref is provided,
// that will be returned. If not, it will fallback to calling headFunc. If an
// error is returned (not in a git repo), then it will fallback to DefaultRef.
func Ref(ref string, headFunc func() (string, error)) string {
	if ref != "" {
		return ref
	}

	ref, err := headFunc()
	if err != nil {
		// An error means that we're either not in a GitRepo or we're
		// not on a branch. In this case, we just fallback to the
		// DefaultRef.
		return DefaultRef
	}

	// Convert `refs/heads/test-deploy` => `test-deploy`
	return refRegex.ReplaceAllString(ref, "$1")
}

// Repo will determine the correct GitHub repo to deploy to, based on a set of
// arguments.
func Repo(arguments []string) (string, error) {
	if len(arguments) != 0 {
		return arguments[0], nil
	}

	remotes, err := hub.Remotes()
	if err != nil {
		return "", err
	}

	repo := GitHubRepo(remotes)
	if repo == "" {
		return repo, errors.New("no GitHub repo found in .git/config")
	}

	return repo, nil
}

// A regular expression that can convert a URL.Path into a GitHub repo name.
var remoteRegex = regexp.MustCompile(`^/(.*)\.git$`)

// GitHubRepo, given a list of git remotes, will determine what the GitHub repo
// is.
func GitHubRepo(remotes []hub.Remote) string {
	// We only want to look at the `origin` remote.
	remote := findRemote("origin", remotes)
	if remote == nil {
		return ""
	}

	// Remotes that are not pointed at a GitHub repo are not valid.
	if remote.URL.Host != "github.com" {
		return ""
	}

	// Convert `/remind101/acme-inc.git` => `remind101/acme-inc`.
	return remoteRegex.ReplaceAllString(remote.URL.Path, "$1")
}

func findRemote(name string, remotes []hub.Remote) *hub.Remote {
	for _, r := range remotes {
		if r.Name == name {
			return &r
		}
	}

	return nil
}

var errInvalidRepo = errors.New("invalid repo")

// SplitRepo splits a repo string in the form remind101/acme-inc into it's owner
// and repo components.
func SplitRepo(nwo, defaultOrg string) (owner string, repo string, err error) {
	parts := strings.Split(nwo, "/")

	// If we were only given a repo name, and a default organization is set,
	// we'll use the defaultOrg as the owner.
	if len(parts) == 1 && defaultOrg != "" && parts[0] != "" {
		owner = defaultOrg
		repo = parts[0]
		return
	}

	if len(parts) != 2 {
		err = errInvalidRepo
		return
	}

	owner = parts[0]
	repo = parts[1]

	return
}

func askYN(prompt string) bool {
	r := bufio.NewReader(os.Stdin)
	fmt.Printf("%s (y/N)\n", prompt)
	a, _ := r.ReadString('\n')
	return strings.ToUpper(a) == "Y\n"
}
<|endoftext|>


<commit_before>package main

import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"fmt"
	"github.com/bgentry/heroku-go"
	hk "github.com/heroku/hk/hkclient"
	// "io"
	"io/ioutil"
	"os"
	"path/filepath"
)

const (
	PLUGIN_NAME    = "deploy"
	PLUGIN_VERSION = 1
	// PLUGIN_USER_AGENT = "hk-" + PLUGIN_NAME "/1"
)

var client *heroku.Client
var nrc *hk.NetRc

func help() {}

func init() {
	nrc, err := hk.LoadNetRc()
	if err != nil && os.IsNotExist(err) {
		nrc = &hk.NetRc{}
	}

	clients, err := hk.New(nrc, "TODO user agent")

	if err == nil {
		client = clients.Client
	} else {
		// TODO
	}
}

func shouldIgnore(path string) bool {
	// TODO: gitignore-ish rules, if a .gitignore exists?
	return path == ".git"
}

// FIXME: This is currently building a tgz that fails to extract without error:
//	x deploy.go
//	x web/README.md
//	x web/main.go: Truncated input file (needed 418 bytes, only 0 available)
//	tar: Error exit delayed from previous errors.
//
// But the files seem intact?
func buildTgz(root string) bytes.Buffer {
	buf := new(bytes.Buffer)
	gz := gzip.NewWriter(buf)
	tw := tar.NewWriter(gz)

	filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
		// TODO: handle incoming err

		if shouldIgnore(path) {
			return filepath.SkipDir
		}

		if info.IsDir() {
			return nil
		}

		fmt.Printf("Adding %s (size: %d).\n", path, info.Size())
		hdr, err := tar.FileInfoHeader(info, path)
		if err != nil {
			return err
		}
		hdr.Name = path
		tw.WriteHeader(hdr)

		body, err := ioutil.ReadFile(path)
		if err != nil {
			return err
		}
		tw.Write(body)

		return nil
	})

	gz.Close() // `buf` now contains the tgz

	return *buf
}

func main() {
	if os.Getenv("HKPLUGINMODE") == "info" {
		help()
		os.Exit(0)
	}

	if len(os.Args) < 2 {
		help()
		os.Exit(1)
	}

	dir := os.Args[1] // TODO: Maybe fallback to CWD or Git root?
	tgz := buildTgz(dir)
	fmt.Printf("%v %d\n", tgz.Bytes(), tgz.Len())
	// fmt.Println(string(tgz.Bytes()))

	/*
		TODO:
			* Check that we have an APP context or set it
			* upload tgz to S3 with an object expiry of ~5min
			* hit build API with that link
			* tail output (if build api has implemented that)
	*/
}
<commit_msg>Add error checking everywhere to figure out why it's broken<commit_after>package main

import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"fmt"
	"github.com/bgentry/heroku-go"
	hk "github.com/heroku/hk/hkclient"
	// "io"
	"io/ioutil"
	"os"
	"path/filepath"
)

const (
	PLUGIN_NAME    = "deploy"
	PLUGIN_VERSION = 1
	// PLUGIN_USER_AGENT = "hk-" + PLUGIN_NAME "/1"
)

var client *heroku.Client
var nrc *hk.NetRc

func help() {}

func init() {
	nrc, err := hk.LoadNetRc()
	if err != nil && os.IsNotExist(err) {
		nrc = &hk.NetRc{}
	}

	clients, err := hk.New(nrc, "TODO user agent")

	if err == nil {
		client = clients.Client
	} else {
		// TODO
	}
}

func shouldIgnore(path string) bool {
	// TODO: gitignore-ish rules, if a .gitignore exists?
	return path == ".git"
}

// FIXME: This is currently building a tgz that fails to extract without error:
//	x deploy.go
//	x web/README.md
//	x web/main.go: Truncated input file (needed 418 bytes, only 0 available)
//	tar: Error exit delayed from previous errors.
//
// But the files seem intact?
func buildTgz(root string) bytes.Buffer {
	buf := new(bytes.Buffer)
	gz := gzip.NewWriter(buf)
	tw := tar.NewWriter(gz)

	filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
		// TODO: handle incoming err more meaningfully
		if err != nil {
			fmt.Println(err.Error())
			return err
		}

		if shouldIgnore(path) {
			// FIXME path may not always be a dir here
			return filepath.SkipDir
		}

		if info.IsDir() {
			return nil
		}

		fmt.Printf("Adding %s (size: %d).\n", path, info.Size())
		hdr, err := tar.FileInfoHeader(info, path)
		if err != nil {
			fmt.Println(err.Error())
			return err
		}
		hdr.Name = path

		if err = tw.WriteHeader(hdr); err != nil {
			fmt.Println(err.Error())
			return err
		}

		body, err := ioutil.ReadFile(path)
		if err != nil {
			fmt.Println(err.Error())
			return err
		}

		if _, err = tw.Write(body); err != nil {
			fmt.Println(err.Error())
			return err
		}

		return nil
	})

	if err := gz.Close(); err != nil {
		fmt.Println(err.Error())
	}

	return *buf
}

func main() {
	if os.Getenv("HKPLUGINMODE") == "info" {
		help()
		os.Exit(0)
	}

	if len(os.Args) < 2 {
		help()
		os.Exit(1)
	}

	dir := os.Args[1] // TODO: Maybe fallback to CWD or Git root?
	tgz := buildTgz(dir)
	fmt.Printf("%v %d\n", tgz.Bytes(), tgz.Len())
	// fmt.Println(string(tgz.Bytes()))

	/*
		TODO:
			* Check that we have an APP context or set it
			* upload tgz to S3 with an object expiry of ~5min
			* hit build API with that link
			* tail output (if build api has implemented that)
	*/
}
<|endoftext|>


<commit_before>package testutil

// TestServer is a test helper. It uses a fork/exec model to create
// a test Consul server instance in the background and initialize it
// with some data and/or services. The test server can then be used
// to run a unit test, and offers an easy API to tear itself down
// when the test has completed. The only prerequisite is to have a consul
// binary available on the $PATH.
//
// This package does not use Consul's official API client. This is
// because we use TestServer to test the API client, which would
// otherwise cause an import cycle.

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"sync/atomic"
	"testing"
)

// offset is used to atomically increment the port numbers.
var offset uint64

// TestPortConfig configures the various ports used for services
// provided by the Consul server.
type TestPortConfig struct {
	DNS     int `json:"dns,omitempty"`
	HTTP    int `json:"http,omitempty"`
	RPC     int `json:"rpc,omitempty"`
	SerfLan int `json:"serf_lan,omitempty"`
	SerfWan int `json:"serf_wan,omitempty"`
	Server  int `json:"server,omitempty"`
}

// TestAddressConfig contains the bind addresses for various
// components of the Consul server.
type TestAddressConfig struct {
	HTTP string `json:"http,omitempty"`
}

// TestServerConfig is the main server configuration struct.
type TestServerConfig struct {
	Bootstrap bool               `json:"bootstrap,omitempty"`
	Server    bool               `json:"server,omitempty"`
	DataDir   string             `json:"data_dir,omitempty"`
	LogLevel  string             `json:"log_level,omitempty"`
	Addresses *TestAddressConfig `json:"addresses,omitempty"`
	Ports     *TestPortConfig    `json:"ports,omitempty"`
}

// ServerConfigCallback is a function interface which can be
// passed to NewTestServerConfig to modify the server config.
type ServerConfigCallback func(c *TestServerConfig)

// defaultServerConfig returns a new TestServerConfig struct
// with all of the listen ports incremented by one.
func defaultServerConfig() *TestServerConfig {
	idx := int(atomic.AddUint64(&offset, 1))

	return &TestServerConfig{
		Bootstrap: true,
		Server:    true,
		LogLevel:  "debug",
		Ports: &TestPortConfig{
			DNS:     20000 + idx,
			HTTP:    21000 + idx,
			RPC:     22000 + idx,
			SerfLan: 23000 + idx,
			SerfWan: 24000 + idx,
			Server:  25000 + idx,
		},
	}
}

// TestService is used to serialize a service definition.
type TestService struct {
	ID      string   `json:",omitempty"`
	Name    string   `json:",omitempty"`
	Tags    []string `json:",omitempty"`
	Address string   `json:",omitempty"`
	Port    int      `json:",omitempty"`
}

// TestCheck is used to serialize a check definition.
type TestCheck struct {
	ID        string `json:",omitempty"`
	Name      string `json:",omitempty"`
	ServiceID string `json:",omitempty"`
	TTL       string `json:",omitempty"`
}

// TestServer is the main server wrapper struct.
type TestServer struct {
	PID      int
	Config   *TestServerConfig
	HTTPAddr string
	t        *testing.T
}

// NewTestServer is an easy helper method to create a new Consul
// test server with the most basic configuration.
func NewTestServer(t *testing.T) *TestServer {
	return NewTestServerConfig(t, nil)
}

// NewTestServerConfig creates a new TestServer, and makes a call to
// an optional callback function to modify the configuration.
func NewTestServerConfig(t *testing.T, cb ServerConfigCallback) *TestServer {
	if path, err := exec.LookPath("consul"); err != nil || path == "" {
		t.Skip("consul not found on $PATH, skipping")
	}

	dataDir, err := ioutil.TempDir("", "consul")
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	configFile, err := ioutil.TempFile("", "consul")
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	defer os.Remove(configFile.Name())

	consulConfig := defaultServerConfig()
	consulConfig.DataDir = dataDir

	if cb != nil {
		cb(consulConfig)
	}

	configContent, err := json.Marshal(consulConfig)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if _, err := configFile.Write(configContent); err != nil {
		t.Fatalf("err: %s", err)
	}
	configFile.Close()

	// Start the server
	cmd := exec.Command("consul", "agent", "-config-file", configFile.Name())
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Start(); err != nil {
		t.Fatalf("err: %s", err)
	}

	server := &TestServer{
		Config:   consulConfig,
		PID:      cmd.Process.Pid,
		HTTPAddr: fmt.Sprintf("127.0.0.1:%d", consulConfig.Ports.HTTP),
		t:        t,
	}

	// Wait for the server to be ready
	if err := server.waitForLeader(); err != nil {
		t.Fatalf("err: %s", err)
	}

	return server
}

// Stop stops the test Consul server, and removes the Consul data
// directory once we are done.
func (s *TestServer) Stop() {
	defer os.RemoveAll(s.Config.DataDir)

	cmd := exec.Command("kill", "-9", fmt.Sprintf("%d", s.PID))
	if err := cmd.Run(); err != nil {
		panic(err)
	}
}

// waitForLeader waits for the Consul server's HTTP API to become
// available, and then waits for a known leader and an index of
// 1 or more to be observed to confirm leader election is done.
func (s *TestServer) waitForLeader() error {
	url := fmt.Sprintf("http://127.0.0.1:%d/v1/catalog/nodes", s.Config.Ports.HTTP)

	WaitForResult(func() (bool, error) {
		resp, err := http.Get(url)
		if err != nil {
			return false, err
		}
		resp.Body.Close()

		// Ensure we have a leader and a node registeration
		if leader := resp.Header.Get("X-Consul-KnownLeader"); leader != "true" {
			fmt.Println(leader)
			return false, fmt.Errorf("Consul leader status: %#v", leader)
		}
		if resp.Header.Get("X-Consul-Index") == "0" {
			return false, fmt.Errorf("Consul index is 0")
		}

		return true, nil
	}, func(err error) {
		s.Stop()
		panic(err)
	})

	return nil
}

// url is a helper function which takes a relative URL and
// makes it into a proper URL against the local Consul server.
func (s *TestServer) url(path string) string {
	return fmt.Sprintf("http://127.0.0.1:%d%s", s.Config.Ports.HTTP, path)
}

// put performs a new HTTP PUT request.
func (s *TestServer) put(path string, body io.Reader) {
	req, err := http.NewRequest("PUT", s.url(path), body)
	if err != nil {
		s.t.Fatalf("err: %s", err)
	}
	s.request(req)
}

// request is a generic HTTP request helper to make a request and
// ensure the response code is acceptable.
func (s *TestServer) request(req *http.Request) {
	// Perform the PUT
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		s.t.Fatalf("err: %s", err)
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		s.t.Fatalf("err: %s", err)
	}

	// Check status code
	if resp.StatusCode != 200 {
		s.t.Fatalf("Bad response code: %d\nBody:\n%s", resp.StatusCode, body)
	}
}

// encodePayload returns a new io.Reader wrapping the encoded contents
// of the payload, suitable for passing directly to a new request.
func (s *TestServer) encodePayload(payload interface{}) io.Reader {
	var encoded bytes.Buffer
	enc := json.NewEncoder(&encoded)
	if err := enc.Encode(payload); err != nil {
		s.t.Fatalf("err: %s", err)
	}
	return &encoded
}

// SetKV sets an individual key in the K/V store.
func (s *TestServer) SetKV(key string, val []byte) {
	s.put("/v1/kv/"+key, bytes.NewBuffer(val))
}

// PopulateKV fills the Consul KV with data from a generic map.
func (s *TestServer) PopulateKV(data map[string][]byte) {
	for k, v := range data {
		s.SetKV(k, v)
	}
}

// AddService adds a new service to the Consul instance. It also
// automatically adds a health check with the given status, which
// can be one of "passing", "warning", or "critical".
func (s *TestServer) AddService(name, status string, tags []string) {
	svc := &TestService{
		Name: name,
		Tags: tags,
	}
	payload := s.encodePayload(svc)
	s.put("/v1/agent/service/register", payload)

	chkName := "service:" + name
	chk := &TestCheck{
		Name:      chkName,
		ServiceID: name,
		TTL:       "10m",
	}
	payload = s.encodePayload(chk)
	s.put("/v1/agent/check/register", payload)

	switch status {
	case "passing":
		s.put("/v1/agent/check/pass/"+chkName, nil)
	case "warning":
		s.put("/v1/agent/check/warn/"+chkName, nil)
	case "critical":
		s.put("/v1/agent/check/fail/"+chkName, nil)
	default:
		s.t.Fatalf("Unrecognized status: %s", status)
	}
}

// AddCheck adds a check to the Consul instance. If the serviceID is
// left empty (""), then the check will be associated with the node.
// The check status may be "passing", "warning", or "critical".
func (s *TestServer) AddCheck(name, serviceID, status string) {
	chk := &TestCheck{
		ID:   name,
		Name: name,
		TTL:  "10m",
	}
	if serviceID != "" {
		chk.ServiceID = serviceID
	}

	payload := s.encodePayload(chk)
	s.put("/v1/agent/check/register", payload)

	switch status {
	case "passing":
		s.put("/v1/agent/check/pass/"+name, nil)
	case "warning":
		s.put("/v1/agent/check/warn/"+name, nil)
	case "critical":
		s.put("/v1/agent/check/fail/"+name, nil)
	default:
		s.t.Fatalf("Unrecognized status: %s", status)
	}
}
<commit_msg>testutil: support joining and specifying datacenters<commit_after>package testutil

// TestServer is a test helper. It uses a fork/exec model to create
// a test Consul server instance in the background and initialize it
// with some data and/or services. The test server can then be used
// to run a unit test, and offers an easy API to tear itself down
// when the test has completed. The only prerequisite is to have a consul
// binary available on the $PATH.
//
// This package does not use Consul's official API client. This is
// because we use TestServer to test the API client, which would
// otherwise cause an import cycle.

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"sync/atomic"
	"testing"
)

// offset is used to atomically increment the port numbers.
var offset uint64

// TestPortConfig configures the various ports used for services
// provided by the Consul server.
type TestPortConfig struct {
	DNS     int `json:"dns,omitempty"`
	HTTP    int `json:"http,omitempty"`
	RPC     int `json:"rpc,omitempty"`
	SerfLan int `json:"serf_lan,omitempty"`
	SerfWan int `json:"serf_wan,omitempty"`
	Server  int `json:"server,omitempty"`
}

// TestAddressConfig contains the bind addresses for various
// components of the Consul server.
type TestAddressConfig struct {
	HTTP string `json:"http,omitempty"`
}

// TestServerConfig is the main server configuration struct.
type TestServerConfig struct {
	Bootstrap  bool               `json:"bootstrap,omitempty"`
	Server     bool               `json:"server,omitempty"`
	DataDir    string             `json:"data_dir,omitempty"`
	Datacenter string             `json:"datacenter,omitempty"`
	LogLevel   string             `json:"log_level,omitempty"`
	Addresses  *TestAddressConfig `json:"addresses,omitempty"`
	Ports      *TestPortConfig    `json:"ports,omitempty"`
}

// ServerConfigCallback is a function interface which can be
// passed to NewTestServerConfig to modify the server config.
type ServerConfigCallback func(c *TestServerConfig)

// defaultServerConfig returns a new TestServerConfig struct
// with all of the listen ports incremented by one.
func defaultServerConfig() *TestServerConfig {
	idx := int(atomic.AddUint64(&offset, 1))

	return &TestServerConfig{
		Bootstrap: true,
		Server:    true,
		LogLevel:  "debug",
		Ports: &TestPortConfig{
			DNS:     20000 + idx,
			HTTP:    21000 + idx,
			RPC:     22000 + idx,
			SerfLan: 23000 + idx,
			SerfWan: 24000 + idx,
			Server:  25000 + idx,
		},
	}
}

// TestService is used to serialize a service definition.
type TestService struct {
	ID      string   `json:",omitempty"`
	Name    string   `json:",omitempty"`
	Tags    []string `json:",omitempty"`
	Address string   `json:",omitempty"`
	Port    int      `json:",omitempty"`
}

// TestCheck is used to serialize a check definition.
type TestCheck struct {
	ID        string `json:",omitempty"`
	Name      string `json:",omitempty"`
	ServiceID string `json:",omitempty"`
	TTL       string `json:",omitempty"`
}

// TestServer is the main server wrapper struct.
type TestServer struct {
	PID    int
	Config *TestServerConfig
	t      *testing.T

	HTTPAddr string
	LANAddr  string
	WANAddr  string
}

// NewTestServer is an easy helper method to create a new Consul
// test server with the most basic configuration.
func NewTestServer(t *testing.T) *TestServer {
	return NewTestServerConfig(t, nil)
}

// NewTestServerConfig creates a new TestServer, and makes a call to
// an optional callback function to modify the configuration.
func NewTestServerConfig(t *testing.T, cb ServerConfigCallback) *TestServer {
	if path, err := exec.LookPath("consul"); err != nil || path == "" {
		t.Skip("consul not found on $PATH, skipping")
	}

	dataDir, err := ioutil.TempDir("", "consul")
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	configFile, err := ioutil.TempFile("", "consul")
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	defer os.Remove(configFile.Name())

	consulConfig := defaultServerConfig()
	consulConfig.DataDir = dataDir

	if cb != nil {
		cb(consulConfig)
	}

	configContent, err := json.Marshal(consulConfig)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if _, err := configFile.Write(configContent); err != nil {
		t.Fatalf("err: %s", err)
	}
	configFile.Close()

	// Start the server
	cmd := exec.Command("consul", "agent", "-config-file", configFile.Name())
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Start(); err != nil {
		t.Fatalf("err: %s", err)
	}

	server := &TestServer{
		Config: consulConfig,
		PID:    cmd.Process.Pid,
		t:      t,

		HTTPAddr: fmt.Sprintf("127.0.0.1:%d", consulConfig.Ports.HTTP),
		LANAddr:  fmt.Sprintf("127.0.0.1:%d", consulConfig.Ports.SerfLan),
		WANAddr:  fmt.Sprintf("127.0.0.1:%d", consulConfig.Ports.SerfWan),
	}

	// Wait for the server to be ready
	if err := server.waitForLeader(); err != nil {
		t.Fatalf("err: %s", err)
	}

	return server
}

// Stop stops the test Consul server, and removes the Consul data
// directory once we are done.
func (s *TestServer) Stop() {
	defer os.RemoveAll(s.Config.DataDir)

	cmd := exec.Command("kill", "-9", fmt.Sprintf("%d", s.PID))
	if err := cmd.Run(); err != nil {
		panic(err)
	}
}

// waitForLeader waits for the Consul server's HTTP API to become
// available, and then waits for a known leader and an index of
// 1 or more to be observed to confirm leader election is done.
func (s *TestServer) waitForLeader() error {
	url := fmt.Sprintf("http://127.0.0.1:%d/v1/catalog/nodes", s.Config.Ports.HTTP)

	WaitForResult(func() (bool, error) {
		resp, err := http.Get(url)
		if err != nil {
			return false, err
		}
		resp.Body.Close()

		// Ensure we have a leader and a node registeration
		if leader := resp.Header.Get("X-Consul-KnownLeader"); leader != "true" {
			fmt.Println(leader)
			return false, fmt.Errorf("Consul leader status: %#v", leader)
		}
		if resp.Header.Get("X-Consul-Index") == "0" {
			return false, fmt.Errorf("Consul index is 0")
		}

		return true, nil
	}, func(err error) {
		s.Stop()
		panic(err)
	})

	return nil
}

// url is a helper function which takes a relative URL and
// makes it into a proper URL against the local Consul server.
func (s *TestServer) url(path string) string {
	return fmt.Sprintf("http://127.0.0.1:%d%s", s.Config.Ports.HTTP, path)
}

// put performs a new HTTP PUT request.
func (s *TestServer) put(path string, body io.Reader) {
	req, err := http.NewRequest("PUT", s.url(path), body)
	if err != nil {
		s.t.Fatalf("err: %s", err)
	}
	s.request(req)
}

// get performs a new HTTP GET request.
func (s *TestServer) get(path string) []byte {
	req, err := http.NewRequest("GET", s.url(path), nil)
	if err != nil {
		s.t.Fatalf("err: %s", err)
	}
	return s.request(req)
}

// request is a generic HTTP request helper to make a request and
// ensure the response code is acceptable.
func (s *TestServer) request(req *http.Request) []byte {
	// Perform the PUT
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		s.t.Fatalf("err: %s", err)
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		s.t.Fatalf("err: %s", err)
	}

	// Check status code
	if resp.StatusCode != 200 {
		s.t.Fatalf("Bad response code: %d\nBody:\n%s", resp.StatusCode, body)
	}

	return body
}

// encodePayload returns a new io.Reader wrapping the encoded contents
// of the payload, suitable for passing directly to a new request.
func (s *TestServer) encodePayload(payload interface{}) io.Reader {
	var encoded bytes.Buffer
	enc := json.NewEncoder(&encoded)
	if err := enc.Encode(payload); err != nil {
		s.t.Fatalf("err: %s", err)
	}
	return &encoded
}

// JoinLAN is used to join nodes within the same datacenter.
func (s *TestServer) JoinLAN(addr string) {
	s.get("/v1/agent/join/" + addr)
}

// JoinWAN is used to join remote datacenters together.
func (s *TestServer) JoinWAN(addr string) {
	s.get("/v1/agent/join/" + addr + "?wan=1")
}

// SetKV sets an individual key in the K/V store.
func (s *TestServer) SetKV(key string, val []byte) {
	s.put("/v1/kv/"+key, bytes.NewBuffer(val))
}

// PopulateKV fills the Consul KV with data from a generic map.
func (s *TestServer) PopulateKV(data map[string][]byte) {
	for k, v := range data {
		s.SetKV(k, v)
	}
}

// AddService adds a new service to the Consul instance. It also
// automatically adds a health check with the given status, which
// can be one of "passing", "warning", or "critical".
func (s *TestServer) AddService(name, status string, tags []string) {
	svc := &TestService{
		Name: name,
		Tags: tags,
	}
	payload := s.encodePayload(svc)
	s.put("/v1/agent/service/register", payload)

	chkName := "service:" + name
	chk := &TestCheck{
		Name:      chkName,
		ServiceID: name,
		TTL:       "10m",
	}
	payload = s.encodePayload(chk)
	s.put("/v1/agent/check/register", payload)

	switch status {
	case "passing":
		s.put("/v1/agent/check/pass/"+chkName, nil)
	case "warning":
		s.put("/v1/agent/check/warn/"+chkName, nil)
	case "critical":
		s.put("/v1/agent/check/fail/"+chkName, nil)
	default:
		s.t.Fatalf("Unrecognized status: %s", status)
	}
}

// AddCheck adds a check to the Consul instance. If the serviceID is
// left empty (""), then the check will be associated with the node.
// The check status may be "passing", "warning", or "critical".
func (s *TestServer) AddCheck(name, serviceID, status string) {
	chk := &TestCheck{
		ID:   name,
		Name: name,
		TTL:  "10m",
	}
	if serviceID != "" {
		chk.ServiceID = serviceID
	}

	payload := s.encodePayload(chk)
	s.put("/v1/agent/check/register", payload)

	switch status {
	case "passing":
		s.put("/v1/agent/check/pass/"+name, nil)
	case "warning":
		s.put("/v1/agent/check/warn/"+name, nil)
	case "critical":
		s.put("/v1/agent/check/fail/"+name, nil)
	default:
		s.t.Fatalf("Unrecognized status: %s", status)
	}
}
<|endoftext|>


<commit_before>package commands

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/signal"
	"path/filepath"
	"time"

	"github.com/kardianos/osext"

	"code.cloudfoundry.org/lager"

	"cred-alert/inflator"
	"cred-alert/kolsch"
	"cred-alert/mimetype"
	"cred-alert/scanners"
	"cred-alert/scanners/diffscanner"
	"cred-alert/scanners/dirscanner"
	"cred-alert/scanners/filescanner"
	"cred-alert/sniff"
	"cred-alert/sniff/matchers"
)

type ScanCommand struct {
	File            string `short:"f" long:"file" description:"the file or directory to scan" value-name:"FILE"`
	Diff            bool   `long:"diff" description:"content to be scanned is a git diff"`
	ShowCredentials bool   `long:"show-suspected-credentials" description:"allow credentials to be shown in output"`
	Regexp          string `long:"regexp" description:"override default regexp matcher" value-name:"REGEXP"`
	RegexpFile      string `long:"regexp-file" description:"path to regexp file" value-name:"PATH"`
}

func (command *ScanCommand) Execute(args []string) error {
	warnIfOldExecutable()

	logger := lager.NewLogger("scan")
	logger.RegisterSink(lager.NewWriterSink(os.Stdout, lager.INFO))

	if command.Regexp != "" && command.RegexpFile != "" {
		fmt.Fprintln(os.Stderr, yellow("[WARN]"), "Two options specified for Regexp, only using: --regexp", command.Regexp)
	}

	var sniffer sniff.Sniffer
	switch {
	case command.Regexp != "":
		sniffer = sniff.NewSniffer(matchers.Format(command.Regexp), nil)
	case command.RegexpFile != "":
		file, err := os.Open(command.RegexpFile)
		if err != nil {
			return err
		}
		defer file.Close()

		matcher := matchers.UpcasedMultiMatcherFromReader(file)
		sniffer = sniff.NewSniffer(matcher, nil)
	default:
		sniffer = sniff.NewDefaultSniffer()
	}

	signalsCh := make(chan os.Signal)
	signal.Notify(signalsCh, os.Interrupt)

	var exitFuncs []func()
	go func() {
		<-signalsCh
		log.SetFlags(0)
		log.Println("\ncleaning up...")
		for _, f := range exitFuncs {
			f()
		}
		os.Exit(1)
	}()

	var credsFound int
	handler := func(logger lager.Logger, violation scanners.Violation) error {
		line := violation.Line
		credsFound++
		output := fmt.Sprintf("%s %s:%d", red("[CRED]"), line.Path, line.LineNumber)
		if command.ShowCredentials {
			output = output + fmt.Sprintf(" [%s]", violation.Credential())
		}
		fmt.Println(output)

		return nil
	}

	quietLogger := kolsch.NewLogger()

	switch {
	case command.File != "":
		fi, err := os.Stat(command.File)
		if err != nil {
			return err
		}

		if fi.IsDir() {
			dirScanner := dirscanner.New(handler, sniffer)
			err = dirScanner.Scan(quietLogger, command.File)
			if err != nil {
				return err
			}
		} else {
			file, err := os.Open(command.File)
			if err != nil {
				return err
			}

			br := bufio.NewReader(file)
			if mime, isArchive := mimetype.IsArchive(logger, br); isArchive {
				inflateDir, err := ioutil.TempDir("", "cred-alert-cli")
				if err != nil {
					return err
				}

				inflate := inflator.New()
				exitFuncs = append(exitFuncs, func() {
					inflate.Close()
					os.RemoveAll(inflateDir)
				})

				inflateArchive(logger, inflate, inflateDir, mime, command.File)

				violationsDir, err := ioutil.TempDir("", "cred-alert-cli-violations")
				if err != nil {
					return err
				}

				archiveHandler := newArchiveHandler(handler, inflateDir, violationsDir)
				scanner := dirscanner.New(archiveHandler, sniffer)

				err = scanner.Scan(quietLogger, inflateDir)
				if err != nil {
					return err
				}
			} else {
				sniffer.Sniff(logger, filescanner.New(br, command.File), handler)
			}
		}
	case command.Diff:
		sniffer.Sniff(logger, diffscanner.NewDiffScanner(os.Stdin), handler)
	default:
		sniffer.Sniff(logger, filescanner.New(os.Stdin, "STDIN"), handler)
	}

	if credsFound > 0 {
		fmt.Println()
		fmt.Println("Yikes! Looks like we found some credentials.")
		fmt.Println()
		fmt.Println("There are a few cases for what this may be:")
		fmt.Println()
		fmt.Println("1. An actual credential in a repository which shouldn't be")
		fmt.Println("   committed! Remove it and try committing again.")
		fmt.Println()
		fmt.Println("2. An example credential in tests or documentation. You can")
		fmt.Println("   use the words 'fake' and/or 'example' in your credential so it is")
		fmt.Println("   ignored.")
		fmt.Println()
		fmt.Println("3. An actual credential in a credential repository. If you are calling this")
		fmt.Println("   via Git hook and if you want the false positive to go away, you can pass `-n`")
		fmt.Println("   to skip the hook for now.")
		fmt.Println()
		fmt.Println("4. A false positive which isn't a credential at all! Please let us know about ")
		fmt.Println("   the this case in our Slack channel (#pcf-sec-enablement).")

		exitFuncs = append(exitFuncs, func() {
			os.Exit(3)
		})
	}

	for _, f := range exitFuncs {
		f()
	}

	return nil
}

func newArchiveHandler(
	handler sniff.ViolationHandlerFunc,
	inflateDir string,
	violationsDir string,
) sniff.ViolationHandlerFunc {
	return func(logger lager.Logger, violation scanners.Violation) error {
		line := violation.Line

		relPath, err := filepath.Rel(inflateDir, line.Path)
		if err != nil {
			return err
		}

		destPath := filepath.Join(violationsDir, relPath)
		err = os.MkdirAll(filepath.Dir(destPath), os.ModePerm)
		if err != nil {
			return err
		}

		err = copyFile(line.Path, destPath)
		if err != nil {
			return err
		}

		return handler(logger, violation)
	}
}

func inflateArchive(
	logger lager.Logger,
	inflate inflator.Inflator,
	inflateDir string,
	mime string,
	file string,
) error {
	inflateStart := time.Now()
	fmt.Print("Inflating archive... ")
	err := inflate.Inflate(logger, mime, file, inflateDir)
	if err != nil {
		fmt.Printf("%s\n", red("FAILED"))
		return err
	}
	fmt.Printf("%s\n", green("DONE"))

	fmt.Println()
	fmt.Println("Time taken (inflating):", time.Since(inflateStart))
	fmt.Println("Any archive inflation errors can be found in: ", inflate.LogPath())
	fmt.Println()

	return nil
}

func copyFile(srcPath, destPath string) error {
	destFile, err := os.Create(destPath)
	if err != nil {
		return err
	}
	defer destFile.Close()

	srcFile, err := os.Open(srcPath)
	if err != nil {
		return err
	}
	defer srcFile.Close()

	_, err = io.Copy(destFile, srcFile)
	return err
}

func warnIfOldExecutable() {
	const twoWeeks = 14 * 24 * time.Hour

	exePath, err := osext.Executable()
	if err != nil {
		return
	}

	info, err := os.Stat(exePath)
	if err != nil {
		return
	}

	mtime := info.ModTime()

	if time.Since(mtime) > twoWeeks {
		fmt.Fprintln(os.Stderr, yellow("[WARN]"), "Executable is old! Please consider running `cred-alert-cli update`.")
	}
}
<commit_msg>Give inflator a quiet logger<commit_after>package commands

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/signal"
	"path/filepath"
	"time"

	"github.com/kardianos/osext"

	"code.cloudfoundry.org/lager"

	"cred-alert/inflator"
	"cred-alert/kolsch"
	"cred-alert/mimetype"
	"cred-alert/scanners"
	"cred-alert/scanners/diffscanner"
	"cred-alert/scanners/dirscanner"
	"cred-alert/scanners/filescanner"
	"cred-alert/sniff"
	"cred-alert/sniff/matchers"
)

type ScanCommand struct {
	File            string `short:"f" long:"file" description:"the file or directory to scan" value-name:"FILE"`
	Diff            bool   `long:"diff" description:"content to be scanned is a git diff"`
	ShowCredentials bool   `long:"show-suspected-credentials" description:"allow credentials to be shown in output"`
	Regexp          string `long:"regexp" description:"override default regexp matcher" value-name:"REGEXP"`
	RegexpFile      string `long:"regexp-file" description:"path to regexp file" value-name:"PATH"`
}

func (command *ScanCommand) Execute(args []string) error {
	warnIfOldExecutable()

	logger := lager.NewLogger("scan")
	logger.RegisterSink(lager.NewWriterSink(os.Stdout, lager.INFO))

	if command.Regexp != "" && command.RegexpFile != "" {
		fmt.Fprintln(os.Stderr, yellow("[WARN]"), "Two options specified for Regexp, only using: --regexp", command.Regexp)
	}

	var sniffer sniff.Sniffer
	switch {
	case command.Regexp != "":
		sniffer = sniff.NewSniffer(matchers.Format(command.Regexp), nil)
	case command.RegexpFile != "":
		file, err := os.Open(command.RegexpFile)
		if err != nil {
			return err
		}
		defer file.Close()

		matcher := matchers.UpcasedMultiMatcherFromReader(file)
		sniffer = sniff.NewSniffer(matcher, nil)
	default:
		sniffer = sniff.NewDefaultSniffer()
	}

	signalsCh := make(chan os.Signal)
	signal.Notify(signalsCh, os.Interrupt)

	var exitFuncs []func()
	go func() {
		<-signalsCh
		log.SetFlags(0)
		log.Println("\ncleaning up...")
		for _, f := range exitFuncs {
			f()
		}
		os.Exit(1)
	}()

	var credsFound int
	handler := func(logger lager.Logger, violation scanners.Violation) error {
		line := violation.Line
		credsFound++
		output := fmt.Sprintf("%s %s:%d", red("[CRED]"), line.Path, line.LineNumber)
		if command.ShowCredentials {
			output = output + fmt.Sprintf(" [%s]", violation.Credential())
		}
		fmt.Println(output)

		return nil
	}

	quietLogger := kolsch.NewLogger()

	switch {
	case command.File != "":
		fi, err := os.Stat(command.File)
		if err != nil {
			return err
		}

		if fi.IsDir() {
			dirScanner := dirscanner.New(handler, sniffer)
			err = dirScanner.Scan(quietLogger, command.File)
			if err != nil {
				return err
			}
		} else {
			file, err := os.Open(command.File)
			if err != nil {
				return err
			}

			br := bufio.NewReader(file)
			if mime, isArchive := mimetype.IsArchive(logger, br); isArchive {
				inflateDir, err := ioutil.TempDir("", "cred-alert-cli")
				if err != nil {
					return err
				}

				inflate := inflator.New()
				exitFuncs = append(exitFuncs, func() {
					inflate.Close()
					os.RemoveAll(inflateDir)
				})

				inflateArchive(quietLogger, inflate, inflateDir, mime, command.File)

				violationsDir, err := ioutil.TempDir("", "cred-alert-cli-violations")
				if err != nil {
					return err
				}

				archiveHandler := newArchiveHandler(handler, inflateDir, violationsDir)
				scanner := dirscanner.New(archiveHandler, sniffer)

				err = scanner.Scan(quietLogger, inflateDir)
				if err != nil {
					return err
				}
			} else {
				sniffer.Sniff(logger, filescanner.New(br, command.File), handler)
			}
		}
	case command.Diff:
		sniffer.Sniff(logger, diffscanner.NewDiffScanner(os.Stdin), handler)
	default:
		sniffer.Sniff(logger, filescanner.New(os.Stdin, "STDIN"), handler)
	}

	if credsFound > 0 {
		fmt.Println()
		fmt.Println("Yikes! Looks like we found some credentials.")
		fmt.Println()
		fmt.Println("There are a few cases for what this may be:")
		fmt.Println()
		fmt.Println("1. An actual credential in a repository which shouldn't be")
		fmt.Println("   committed! Remove it and try committing again.")
		fmt.Println()
		fmt.Println("2. An example credential in tests or documentation. You can")
		fmt.Println("   use the words 'fake' and/or 'example' in your credential so it is")
		fmt.Println("   ignored.")
		fmt.Println()
		fmt.Println("3. An actual credential in a credential repository. If you are calling this")
		fmt.Println("   via Git hook and if you want the false positive to go away, you can pass `-n`")
		fmt.Println("   to skip the hook for now.")
		fmt.Println()
		fmt.Println("4. A false positive which isn't a credential at all! Please let us know about ")
		fmt.Println("   the this case in our Slack channel (#pcf-sec-enablement).")

		exitFuncs = append(exitFuncs, func() {
			os.Exit(3)
		})
	}

	for _, f := range exitFuncs {
		f()
	}

	return nil
}

func newArchiveHandler(
	handler sniff.ViolationHandlerFunc,
	inflateDir string,
	violationsDir string,
) sniff.ViolationHandlerFunc {
	return func(logger lager.Logger, violation scanners.Violation) error {
		line := violation.Line

		relPath, err := filepath.Rel(inflateDir, line.Path)
		if err != nil {
			return err
		}

		destPath := filepath.Join(violationsDir, relPath)
		err = os.MkdirAll(filepath.Dir(destPath), os.ModePerm)
		if err != nil {
			return err
		}

		err = copyFile(line.Path, destPath)
		if err != nil {
			return err
		}

		return handler(logger, violation)
	}
}

func inflateArchive(
	logger lager.Logger,
	inflate inflator.Inflator,
	inflateDir string,
	mime string,
	file string,
) error {
	inflateStart := time.Now()
	fmt.Print("Inflating archive... ")
	err := inflate.Inflate(logger, mime, file, inflateDir)
	if err != nil {
		fmt.Printf("%s\n", red("FAILED"))
		return err
	}
	fmt.Printf("%s\n", green("DONE"))

	fmt.Println()
	fmt.Println("Time taken (inflating):", time.Since(inflateStart))
	fmt.Println("Any archive inflation errors can be found in: ", inflate.LogPath())
	fmt.Println()

	return nil
}

func copyFile(srcPath, destPath string) error {
	destFile, err := os.Create(destPath)
	if err != nil {
		return err
	}
	defer destFile.Close()

	srcFile, err := os.Open(srcPath)
	if err != nil {
		return err
	}
	defer srcFile.Close()

	_, err = io.Copy(destFile, srcFile)
	return err
}

func warnIfOldExecutable() {
	const twoWeeks = 14 * 24 * time.Hour

	exePath, err := osext.Executable()
	if err != nil {
		return
	}

	info, err := os.Stat(exePath)
	if err != nil {
		return
	}

	mtime := info.ModTime()

	if time.Since(mtime) > twoWeeks {
		fmt.Fprintln(os.Stderr, yellow("[WARN]"), "Executable is old! Please consider running `cred-alert-cli update`.")
	}
}
<|endoftext|>


<commit_before>package textlength

import "testing"
import "errors"
import "fmt"

func TestThreeDigitOrLessNumbers(t *testing.T) {
	runNumberTest(1, "one", t)
	runNumberTest(10, "ten", t)
	runNumberTest(15, "fifteen", t)
	runNumberTest(16, "sixteen", t)
	runNumberTest(42, "forty-two", t)
	runNumberTest(88, "eighty-eight", t)
	runNumberTest(100, "one hundred", t)
	runNumberTest(110, "one hundred ten", t)
	runNumberTest(320, "three hundred twenty", t)
	runNumberTest(330, "three hundred thirty", t)
	runNumberTest(380, "three hundred eighty", t)
	runNumberTest(562, "five hundred sixty-two", t)
	runNumberTest(814, "eight hundred fourteen", t)
	runNumberTest(900, "nine hundred", t)
	runNumberTest(999, "nine hundred ninety-nine", t)
}

func TestFourToSixDigitNumbers(t *testing.T) {
	runNumberTest(2001, "two thousand one", t)
	runNumberTest(2010, "two thousand ten", t)
	runNumberTest(2012, "two thousand twelve", t)
	runNumberTest(2562, "two thousand five hundred sixty-two", t)
	runNumberTest(9000, "nine thousand", t)
	runNumberTest(9999, "nine thousand nine hundred ninety-nine", t)
	runNumberTest(12001, "twelve thousand one", t)
	runNumberTest(22010, "twenty-two thousand ten", t)
	runNumberTest(32012, "thirty-two thousand twelve", t)
	runNumberTest(42562, "forty-two thousand five hundred sixty-two", t)
	runNumberTest(59000, "fifty-nine thousand", t)
	runNumberTest(69999, "sixty-nine thousand nine hundred ninety-nine", t)
	runNumberTest(938469, "nine hundred thirty-eight thousand four hundred sixty-nine", t)
	runNumberTest(957395, "nine hundred fifty-seven thousand three hundred ninety-five", t)
	runNumberTest(791124, "seven hundred ninety-one thousand one hundred twenty-four", t)
	runNumberTest(642886, "six hundred forty-two thousand eight hundred eighty-six", t)
	runNumberTest(559621, "five hundred fifty-nine thousand six hundred twenty-one", t)
	runNumberTest(492319, "four hundred ninety-two thousand three hundred nineteen", t)
	runNumberTest(999999, "nine hundred ninety-nine thousand nine hundred ninety-nine", t)
}

func TestSevenToNineDigitNumbers(t *testing.T) {
	runNumberTest(2000000, "two million", t)
	runNumberTest(2032012, "two million thirty-two thousand twelve", t)
	runNumberTest(1938469, "one million nine hundred thirty-eight thousand four hundred sixty-nine", t)
	runNumberTest(62032776, "sixty-two million thirty-two thousand seven hundred seventy-six", t)
	runNumberTest(72132888, "seventy-two million one hundred thirty-two thousand eight hundred eighty-eight", t)
	runNumberTest(800000000, "eight hundred million", t)
	runNumberTest(813015010, "eight hundred thirteen million fifteen thousand ten", t)
	runNumberTest(999999999, "nine hundred ninety-nine million nine hundred ninety-nine thousand nine hundred ninety-nine", t)
}

func TestOverOneBillion(t *testing.T) {
	runNumberTest(1000000000, "one billion", t)
	runNumberTest(999999999999, "nine hundred ninety-nine billion nine hundred ninety-nine million nine hundred ninety-nine thousand nine hundred ninety-nine", t)
}

func runNumberTest(num int, text string, t *testing.T) (string, error) {
	result, err := GetTextForInt(num)
	if err != nil {
		t.Error(err)
	} else {
		if result != text {
			err = errors.New(fmt.Sprintf("\nGave: %v\nExpected: {%v}\nResult: {%v}", num, text, result))
			t.Error(err)
		}
	}
	return result, nil
}

func TestTextLengthItem(t *testing.T) {
	tli := TextLengthItem{"This text is forty characters long.", 20}
	if tli.Length() != 35 {
		t.Errorf("Length was %v, expected %v.", tli.Length(), 3)
	}
}
<commit_msg>might want this later<commit_after>package textlength

import "testing"
import "errors"
import "fmt"

func TestThreeDigitOrLessNumbers(t *testing.T) {
	runNumberTest(1, "one", t)
	runNumberTest(10, "ten", t)
	runNumberTest(15, "fifteen", t)
	runNumberTest(16, "sixteen", t)
	runNumberTest(42, "forty-two", t)
	runNumberTest(88, "eighty-eight", t)
	runNumberTest(100, "one hundred", t)
	runNumberTest(110, "one hundred ten", t)
	runNumberTest(320, "three hundred twenty", t)
	runNumberTest(330, "three hundred thirty", t)
	runNumberTest(380, "three hundred eighty", t)
	runNumberTest(562, "five hundred sixty-two", t)
	runNumberTest(814, "eight hundred fourteen", t)
	runNumberTest(900, "nine hundred", t)
	runNumberTest(999, "nine hundred ninety-nine", t)
}

func TestFourToSixDigitNumbers(t *testing.T) {
	runNumberTest(2001, "two thousand one", t)
	runNumberTest(2010, "two thousand ten", t)
	runNumberTest(2012, "two thousand twelve", t)
	runNumberTest(2562, "two thousand five hundred sixty-two", t)
	runNumberTest(9000, "nine thousand", t)
	runNumberTest(9999, "nine thousand nine hundred ninety-nine", t)
	runNumberTest(12001, "twelve thousand one", t)
	runNumberTest(22010, "twenty-two thousand ten", t)
	runNumberTest(32012, "thirty-two thousand twelve", t)
	runNumberTest(42562, "forty-two thousand five hundred sixty-two", t)
	runNumberTest(59000, "fifty-nine thousand", t)
	runNumberTest(69999, "sixty-nine thousand nine hundred ninety-nine", t)
	runNumberTest(938469, "nine hundred thirty-eight thousand four hundred sixty-nine", t)
	runNumberTest(957395, "nine hundred fifty-seven thousand three hundred ninety-five", t)
	runNumberTest(791124, "seven hundred ninety-one thousand one hundred twenty-four", t)
	runNumberTest(642886, "six hundred forty-two thousand eight hundred eighty-six", t)
	runNumberTest(559621, "five hundred fifty-nine thousand six hundred twenty-one", t)
	runNumberTest(492319, "four hundred ninety-two thousand three hundred nineteen", t)
	runNumberTest(999999, "nine hundred ninety-nine thousand nine hundred ninety-nine", t)
}

func TestSevenToNineDigitNumbers(t *testing.T) {
	runNumberTest(2000000, "two million", t)
	runNumberTest(2032012, "two million thirty-two thousand twelve", t)
	runNumberTest(1938469, "one million nine hundred thirty-eight thousand four hundred sixty-nine", t)
	runNumberTest(62032776, "sixty-two million thirty-two thousand seven hundred seventy-six", t)
	runNumberTest(72132888, "seventy-two million one hundred thirty-two thousand eight hundred eighty-eight", t)
	runNumberTest(800000000, "eight hundred million", t)
	runNumberTest(813015010, "eight hundred thirteen million fifteen thousand ten", t)
	runNumberTest(999999999, "nine hundred ninety-nine million nine hundred ninety-nine thousand nine hundred ninety-nine", t)
}

func TestOverOneBillion(t *testing.T) {
	runNumberTest(1000000000, "one billion", t)
	runNumberTest(999999999999, "nine hundred ninety-nine billion nine hundred ninety-nine million nine hundred ninety-nine thousand nine hundred ninety-nine", t)
}

func runNumberTest(num int, text string, t *testing.T) (string, error) {
	result, err := GetTextForInt(num)
	if err != nil {
		t.Error(err)
	} else {
		if result != text {
			err = errors.New(fmt.Sprintf("\nGave: %v\nExpected: {%v}\nResult: {%v}", num, text, result))
			t.Error(err)
		}
	}
	// fmt.Printf("Test for %v returned %v just fine.\n", num, text)
	return result, nil
}

func TestTextLengthItem(t *testing.T) {
	tli := TextLengthItem{"This text is forty characters long.", 20}
	if tli.Length() != 35 {
		t.Errorf("Length was %v, expected %v.", tli.Length(), 3)
	}
}
<|endoftext|>


<commit_before>package assert

import (
	"fmt"
	"strings"
)

type Line []string

func (line Line) String() string {
	return strings.Join(line, ", ")
}

type Lines []Line

func SliceContains(actual []string, expected Lines, msgAndArgs ...interface{}) bool {
	expectedIndex := 0
	for _, actualValue := range actual {
		allStringsFound := true
		for _, expectedValue := range expected[expectedIndex] {
			allStringsFound = allStringsFound && strings.Contains(strings.ToLower(actualValue), strings.ToLower(expectedValue))
		}

		if allStringsFound {
			expectedIndex++
			if expectedIndex == len(expected) {
				return true
			}
		}
	}
	return Fail(fmt.Sprintf("\"%s\" not found", expected[expectedIndex]), msgAndArgs...)
}

func SliceDoesNotContain(actual []string, expected Lines, msgAndArgs ...interface{}) bool {
	for i, actualValue := range actual {
		for _, expectedLine := range expected {
			allStringsFound := true
			for _, expectedValue := range expectedLine {
				allStringsFound = allStringsFound && strings.Contains(strings.ToLower(actualValue), strings.ToLower(expectedValue))
			}
			if allStringsFound {
				return Fail(fmt.Sprintf("\"%s\" found on line %d", expectedLine, i), msgAndArgs...)
			}
		}
	}
	return true
}
<commit_msg>Improve error message for SliceContains<commit_after>package assert

import (
	"fmt"
	"strings"
)

type Line []string

func (line Line) String() string {
	return strings.Join(line, ", ")
}

type Lines []Line

func SliceContains(actual Line, expected Lines, msgAndArgs ...interface{}) bool {
	expectedIndex := 0
	for _, actualValue := range actual {
		allStringsFound := true
		for _, expectedValue := range expected[expectedIndex] {
			allStringsFound = allStringsFound && strings.Contains(strings.ToLower(actualValue), strings.ToLower(expectedValue))
		}

		if allStringsFound {
			expectedIndex++
			if expectedIndex == len(expected) {
				return true
			}
		}
	}
	return Fail(fmt.Sprintf("\"%s\" not found in actual:\n'%s'\n", expected[expectedIndex], actual), msgAndArgs...)
}

func SliceDoesNotContain(actual Line, expected Lines, msgAndArgs ...interface{}) bool {
	for i, actualValue := range actual {
		for _, expectedLine := range expected {
			allStringsFound := true
			for _, expectedValue := range expectedLine {
				allStringsFound = allStringsFound && strings.Contains(strings.ToLower(actualValue), strings.ToLower(expectedValue))
			}
			if allStringsFound {
				return Fail(fmt.Sprintf("\"%s\" found on line %d", expectedLine, i), msgAndArgs...)
			}
		}
	}
	return true
}
<|endoftext|>


<commit_before>package acl

import (
	"fmt"

	"github.com/lxc/lxd/lxd/db"
	deviceConfig "github.com/lxc/lxd/lxd/device/config"
	"github.com/lxc/lxd/lxd/project"
	"github.com/lxc/lxd/lxd/state"
	"github.com/lxc/lxd/lxd/util"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
)

// LoadByName loads and initialises a Network ACL from the database by project and name.
func LoadByName(s *state.State, projectName string, name string) (NetworkACL, error) {
	id, aclInfo, err := s.Cluster.GetNetworkACL(projectName, name)
	if err != nil {
		return nil, err
	}

	var acl NetworkACL = &common{} // Only a single driver currently.
	acl.init(s, id, projectName, aclInfo)

	return acl, nil
}

// Create validates supplied record and creates new Network ACL record in the database.
func Create(s *state.State, projectName string, aclInfo *api.NetworkACLsPost) error {
	var acl NetworkACL = &common{} // Only a single driver currently.
	acl.init(s, -1, projectName, nil)

	err := acl.validateName(aclInfo.Name)
	if err != nil {
		return err
	}

	err = acl.validateConfig(&aclInfo.NetworkACLPut)
	if err != nil {
		return err
	}

	// Insert DB record.
	_, err = s.Cluster.CreateNetworkACL(projectName, aclInfo)
	if err != nil {
		return err
	}

	return nil
}

// Exists checks the ACL name(s) provided exists in the project.
// If multiple names are provided, also checks that duplicate names aren't specified in the list.
func Exists(s *state.State, projectName string, name ...string) error {
	existingACLNames, err := s.Cluster.GetNetworkACLs(projectName)
	if err != nil {
		return err
	}

	checkedACLNames := make(map[string]struct{}, len(name))

	for _, aclName := range name {
		if !shared.StringInSlice(aclName, existingACLNames) {
			return fmt.Errorf("Network ACL %q does not exist", aclName)
		}

		_, found := checkedACLNames[aclName]
		if found {
			return fmt.Errorf("Network ACL %q specified multiple times", aclName)
		}

		checkedACLNames[aclName] = struct{}{}
	}

	return nil
}

// UsedBy finds all networks and instance NICs that use an ACL and executes usageFunc with info about the usage.
func UsedBy(s *state.State, projectName string, aclName string, usageFunc func(usageType string, projectName string, name string, config map[string]string) error) error {
	return s.Cluster.InstanceList(nil, func(inst db.Instance, p api.Project, profiles []api.Profile) error {
		// Get the instance's effective network project name.
		instNetworkProject := project.NetworkProjectFromRecord(&p)

		// Skip instances who's effective network project doesn't match this Network ACL's project.
		if instNetworkProject != projectName {
			return nil
		}

		devices := db.ExpandInstanceDevices(deviceConfig.NewDevices(inst.Devices), profiles).CloneNative()

		// Iterate through each of the instance's devices, looking for NICs that are linked this ACL.
		for _, devConfig := range devices {
			// Only NICs linked to managed networks can use network ACLs.
			if devConfig["type"] != "nic" || devConfig["network"] == "" {
				continue
			}

			nicACLNames := util.SplitNTrimSpace(devConfig["security.acls"], ",", -1, true)
			for _, nicACLName := range nicACLNames {
				if nicACLName == aclName {
					err := usageFunc("instance", inst.Project, inst.Name, devConfig)
					if err != nil {
						return err
					}
				}
			}
		}

		return nil
	})
}
<commit_msg>lxd/network/acl/acl/load: Updates UsedBy to allow searching for multiple ACLs in one pass<commit_after>package acl

import (
	"fmt"

	"github.com/pkg/errors"

	"github.com/lxc/lxd/lxd/db"
	deviceConfig "github.com/lxc/lxd/lxd/device/config"
	"github.com/lxc/lxd/lxd/project"
	"github.com/lxc/lxd/lxd/state"
	"github.com/lxc/lxd/lxd/util"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
)

// LoadByName loads and initialises a Network ACL from the database by project and name.
func LoadByName(s *state.State, projectName string, name string) (NetworkACL, error) {
	id, aclInfo, err := s.Cluster.GetNetworkACL(projectName, name)
	if err != nil {
		return nil, err
	}

	var acl NetworkACL = &common{} // Only a single driver currently.
	acl.init(s, id, projectName, aclInfo)

	return acl, nil
}

// Create validates supplied record and creates new Network ACL record in the database.
func Create(s *state.State, projectName string, aclInfo *api.NetworkACLsPost) error {
	var acl NetworkACL = &common{} // Only a single driver currently.
	acl.init(s, -1, projectName, nil)

	err := acl.validateName(aclInfo.Name)
	if err != nil {
		return err
	}

	err = acl.validateConfig(&aclInfo.NetworkACLPut)
	if err != nil {
		return err
	}

	// Insert DB record.
	_, err = s.Cluster.CreateNetworkACL(projectName, aclInfo)
	if err != nil {
		return err
	}

	return nil
}

// Exists checks the ACL name(s) provided exists in the project.
// If multiple names are provided, also checks that duplicate names aren't specified in the list.
func Exists(s *state.State, projectName string, name ...string) error {
	existingACLNames, err := s.Cluster.GetNetworkACLs(projectName)
	if err != nil {
		return err
	}

	checkedACLNames := make(map[string]struct{}, len(name))

	for _, aclName := range name {
		if !shared.StringInSlice(aclName, existingACLNames) {
			return fmt.Errorf("Network ACL %q does not exist", aclName)
		}

		_, found := checkedACLNames[aclName]
		if found {
			return fmt.Errorf("Network ACL %q specified multiple times", aclName)
		}

		checkedACLNames[aclName] = struct{}{}
	}

	return nil
}

// UsedBy finds all networks, profiles and instance NICs that use any of the specified ACLs and executes usageFunc
// once for each resource using one or more of the ACLs with info about the resource and matched ACLs being used.
func UsedBy(s *state.State, aclProjectName string, usageFunc func(matchedACLNames []string, usageType interface{}, nicName string, nicConfig map[string]string) error, matchACLNames ...string) error {
	if len(matchACLNames) <= 0 {
		return nil
	}

	// Find networks using the ACLs. Cheapest to do.
	networkNames, err := s.Cluster.GetCreatedNetworks(aclProjectName)
	if err != nil && err != db.ErrNoSuchObject {
		return errors.Wrapf(err, "Failed loading networks for project %q", aclProjectName)
	}

	for _, networkName := range networkNames {
		_, network, _, err := s.Cluster.GetNetworkInAnyState(aclProjectName, networkName)
		if err != nil {
			return errors.Wrapf(err, "Failed to get network config for %q", networkName)
		}

		netACLNames := util.SplitNTrimSpace(network.Config["security.acls"], ",", -1, true)
		matchedACLNames := []string{}
		for _, netACLName := range netACLNames {
			if shared.StringInSlice(netACLName, matchACLNames) {
				matchedACLNames = append(matchedACLNames, netACLName)
			}
		}

		if len(matchedACLNames) > 0 {
			// Call usageFunc with a list of matched ACLs and info about the network.
			err := usageFunc(matchedACLNames, network, "", nil)
			if err != nil {
				return err
			}
		}
	}

	// Look for profiles. Next cheapest to do.
	var profiles []db.Profile
	err = s.Cluster.Transaction(func(tx *db.ClusterTx) error {
		profiles, err = tx.GetProfiles(db.ProfileFilter{})
		if err != nil {
			return err
		}

		return nil
	})
	if err != nil {
		return err
	}

	for _, profile := range profiles {
		// Get the profiles's effective network project name.
		profileNetworkProjectName, _, err := project.NetworkProject(s.Cluster, profile.Project)
		if err != nil {
			return err
		}

		// Skip profiles who's effective network project doesn't match this Network ACL's project.
		if profileNetworkProjectName != aclProjectName {
			return nil
		}

		// Iterate through each of the instance's devices, looking for NICs that are using any of the ACLs.
		for devName, devConfig := range deviceConfig.NewDevices(profile.Devices) {
			matchedACLNames := isInUseByDevice(devConfig, matchACLNames...)
			if len(matchedACLNames) > 0 {
				// Call usageFunc with a list of matched ACLs and info about the instance NIC.
				err := usageFunc(matchedACLNames, profile, devName, devConfig)
				if err != nil {
					return err
				}
			}
		}
	}

	// Find ACLs that have rules that reference the ACLs.
	aclNames, err := s.Cluster.GetNetworkACLs(aclProjectName)
	if err != nil {
		return err
	}

	for _, aclName := range aclNames {
		_, aclInfo, err := s.Cluster.GetNetworkACL(aclProjectName, aclName)
		if err != nil {
			return err
		}

		matchedACLNames := []string{}

		// Ingress rules can specify ACL names in their Source subjects.
		for _, rule := range aclInfo.Ingress {
			for _, subject := range util.SplitNTrimSpace(rule.Source, ",", -1, true) {

				// Look for matching ACLs, but ignore our own ACL reference in our own rules.
				if shared.StringInSlice(subject, matchACLNames) && subject != aclName {
					matchedACLNames = append(matchedACLNames, subject)
				}
			}
		}

		// Egress rules can specify ACL names in their Destination subjects.
		for _, rule := range aclInfo.Egress {
			for _, subject := range util.SplitNTrimSpace(rule.Destination, ",", -1, true) {

				// Look for matching ACLs, but ignore our own ACL reference in our own rules.
				if shared.StringInSlice(subject, matchACLNames) && subject != aclName {
					matchedACLNames = append(matchedACLNames, subject)
				}
			}
		}

		if len(matchedACLNames) > 0 {
			// Call usageFunc with a list of matched ACLs and info about the ACL.
			err := usageFunc(matchedACLNames, aclInfo, "", nil)
			if err != nil {
				return err
			}
		}
	}

	// Find instances using the ACLs. Most expensive to do.
	err = s.Cluster.InstanceList(nil, func(inst db.Instance, p api.Project, profiles []api.Profile) error {
		// Get the instance's effective network project name.
		instNetworkProject := project.NetworkProjectFromRecord(&p)

		// Skip instances who's effective network project doesn't match this Network ACL's project.
		if instNetworkProject != aclProjectName {
			return nil
		}

		devices := db.ExpandInstanceDevices(deviceConfig.NewDevices(inst.Devices), profiles)

		// Iterate through each of the instance's devices, looking for NICs that are using any of the ACLs.
		for devName, devConfig := range devices {
			matchedACLNames := isInUseByDevice(devConfig, matchACLNames...)
			if len(matchedACLNames) > 0 {
				// Call usageFunc with a list of matched ACLs and info about the instance NIC.
				err := usageFunc(matchedACLNames, inst, devName, devConfig)
				if err != nil {
					return err
				}
			}
		}

		return nil
	})
	if err != nil {
		return err
	}

	return nil
}

// isInUseByDevice returns any of the supplied matching ACL names found referenced by the NIC device.
func isInUseByDevice(d deviceConfig.Device, matchACLNames ...string) []string {
	matchedACLNames := []string{}

	// Only NICs linked to managed networks can use network ACLs.
	if d["type"] != "nic" || d["network"] == "" {
		return matchedACLNames
	}

	for _, nicACLName := range util.SplitNTrimSpace(d["security.acls"], ",", -1, true) {
		if shared.StringInSlice(nicACLName, matchACLNames) {
			matchedACLNames = append(matchedACLNames, nicACLName)
		}
	}

	return matchedACLNames
}
<|endoftext|>