<commit_before>/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package provenance

import (
	"bytes"
	"crypto"
	"encoding/hex"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/pkg/errors"
	"golang.org/x/crypto/openpgp"
	"golang.org/x/crypto/openpgp/clearsign"
	"golang.org/x/crypto/openpgp/packet"
	"sigs.k8s.io/yaml"

	hapi "helm.sh/helm/pkg/chart"
	"helm.sh/helm/pkg/chart/loader"
)

var defaultPGPConfig = packet.Config{
	DefaultHash: crypto.SHA512,
}

// SumCollection represents a collection of file and image checksums.
//
// Files are of the form:
//	FILENAME: "sha256:SUM"
// Images are of the form:
//	"IMAGE:TAG": "sha256:SUM"
// Docker optionally supports sha512, and if this is the case, the hash marker
// will be 'sha512' instead of 'sha256'.
type SumCollection struct {
	Files  map[string]string `json:"files"`
	Images map[string]string `json:"images,omitempty"`
}

// Verification contains information about a verification operation.
type Verification struct {
	// SignedBy contains the entity that signed a chart.
	SignedBy *openpgp.Entity
	// FileHash is the hash, prepended with the scheme, for the file that was verified.
	FileHash string
	// FileName is the name of the file that FileHash verifies.
	FileName string
}

// Signatory signs things.
//
// Signatories can be constructed from a PGP private key file using NewFromFiles
// or they can be constructed manually by setting the Entity to a valid
// PGP entity.
//
// The same Signatory can be used to sign or validate multiple charts.
type Signatory struct {
	// The signatory for this instance of Helm. This is used for signing.
	Entity *openpgp.Entity
	// The keyring for this instance of Helm. This is used for verification.
	KeyRing openpgp.EntityList
}

// NewFromFiles constructs a new Signatory from the PGP key in the given filename.
//
// This will emit an error if it cannot find a valid GPG keyfile (entity) at the
// given location.
//
// Note that the keyfile may have just a public key, just a private key, or
// both. The Signatory methods may have different requirements of the keys. For
// example, ClearSign must have a valid `openpgp.Entity.PrivateKey` before it
// can sign something.
func NewFromFiles(keyfile, keyringfile string) (*Signatory, error) {
	e, err := loadKey(keyfile)
	if err != nil {
		return nil, err
	}

	ring, err := loadKeyRing(keyringfile)
	if err != nil {
		return nil, err
	}

	return &Signatory{
		Entity:  e,
		KeyRing: ring,
	}, nil
}

// NewFromKeyring reads a keyring file and creates a Signatory.
//
// If id is not the empty string, this will also try to find an Entity in the
// keyring whose name matches, and set that as the signing entity. It will return
// an error if the id is not empty and also not found.
func NewFromKeyring(keyringfile, id string) (*Signatory, error) {
	ring, err := loadKeyRing(keyringfile)
	if err != nil {
		return nil, err
	}

	s := &Signatory{KeyRing: ring}

	// If the ID is empty, we can return now.
	if id == "" {
		return s, nil
	}

	// We're gonna go all GnuPG on this and look for a string that _contains_. If
	// two or more keys contain the string and none are a direct match, we error
	// out.
	var candidate *openpgp.Entity
	vague := false
	for _, e := range ring {
		for n := range e.Identities {
			if n == id {
				s.Entity = e
				return s, nil
			}
			if strings.Contains(n, id) {
				if candidate != nil {
					vague = true
				}
				candidate = e
			}
		}
	}
	if vague {
		return s, errors.Errorf("more than one key contain the id %q", id)
	}

	s.Entity = candidate
	return s, nil
}

// PassphraseFetcher returns a passphrase for decrypting keys.
//
// This is used as a callback to read a passphrase from some other location. The
// given name is the Name field on the key, typically of the form:
//
//	USER_NAME (COMMENT) <EMAIL>
type PassphraseFetcher func(name string) ([]byte, error)

// DecryptKey decrypts a private key in the Signatory.
//
// If the key is not encrypted, this will return without error.
//
// If the key does not exist, this will return an error.
//
// If the key exists, but cannot be unlocked with the passphrase returned by
// the PassphraseFetcher, this will return an error.
//
// If the key is successfully unlocked, it will return nil.
func (s *Signatory) DecryptKey(fn PassphraseFetcher) error {
	if s.Entity == nil {
		return errors.New("private key not found")
	} else if s.Entity.PrivateKey == nil {
		return errors.New("provided key is not a private key")
	}

	// Nothing else to do if key is not encrypted.
	if !s.Entity.PrivateKey.Encrypted {
		return nil
	}

	fname := "Unknown"
	for i := range s.Entity.Identities {
		if i != "" {
			fname = i
			break
		}
	}

	p, err := fn(fname)
	if err != nil {
		return err
	}

	return s.Entity.PrivateKey.Decrypt(p)
}

// ClearSign signs a chart with the given key.
//
// This takes the path to a chart archive file and a key, and it returns a clear signature.
//
// The Signatory must have a valid Entity.PrivateKey for this to work. If it does
// not, an error will be returned.
func (s *Signatory) ClearSign(chartpath string) (string, error) {
	if s.Entity == nil {
		return "", errors.New("private key not found")
	} else if s.Entity.PrivateKey == nil {
		return "", errors.New("provided key is not a private key")
	}

	if fi, err := os.Stat(chartpath); err != nil {
		return "", err
	} else if fi.IsDir() {
		return "", errors.New("cannot sign a directory")
	}

	out := bytes.NewBuffer(nil)

	b, err := messageBlock(chartpath)
	if err != nil {
		return "", nil
	}

	// Sign the buffer
	w, err := clearsign.Encode(out, s.Entity.PrivateKey, &defaultPGPConfig)
	if err != nil {
		return "", err
	}
	_, err = io.Copy(w, b)
	w.Close()
	return out.String(), err
}

// Verify checks a signature and verifies that it is legit for a chart.
func (s *Signatory) Verify(chartpath, sigpath string) (*Verification, error) {
	ver := &Verification{}
	for _, fname := range []string{chartpath, sigpath} {
		if fi, err := os.Stat(fname); err != nil {
			return ver, err
		} else if fi.IsDir() {
			return ver, errors.Errorf("%s cannot be a directory", fname)
		}
	}

	// First verify the signature
	sig, err := s.decodeSignature(sigpath)
	if err != nil {
		return ver, errors.Wrap(err, "failed to decode signature")
	}

	by, err := s.verifySignature(sig)
	if err != nil {
		return ver, err
	}
	ver.SignedBy = by

	// Second, verify the hash of the tarball.
	sum, err := DigestFile(chartpath)
	if err != nil {
		return ver, err
	}
	_, sums, err := parseMessageBlock(sig.Plaintext)
	if err != nil {
		return ver, err
	}

	sum = "sha256:" + sum
	basename := filepath.Base(chartpath)
	if sha, ok := sums.Files[basename]; !ok {
		return ver, errors.Errorf("provenance does not contain a SHA for a file named %q", basename)
	} else if sha != sum {
		return ver, errors.Errorf("sha256 sum does not match for %s: %q != %q", basename, sha, sum)
	}
	ver.FileHash = sum
	ver.FileName = basename

	// TODO: when image signing is added, verify that here.

	return ver, nil
}

func (s *Signatory) decodeSignature(filename string) (*clearsign.Block, error) {
	data, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}

	block, _ := clearsign.Decode(data)
	if block == nil {
		// There was no sig in the file.
		return nil, errors.New("signature block not found")
	}

	return block, nil
}

// verifySignature verifies that the given block is validly signed, and returns the signer.
func (s *Signatory) verifySignature(block *clearsign.Block) (*openpgp.Entity, error) {
	return openpgp.CheckDetachedSignature(
		s.KeyRing,
		bytes.NewBuffer(block.Bytes),
		block.ArmoredSignature.Body,
	)
}

func messageBlock(chartpath string) (*bytes.Buffer, error) {
	var b *bytes.Buffer
	// Checksum the archive
	chash, err := DigestFile(chartpath)
	if err != nil {
		return b, err
	}

	base := filepath.Base(chartpath)
	sums := &SumCollection{
		Files: map[string]string{
			base: "sha256:" + chash,
		},
	}

	// Load the archive into memory.
	chart, err := loader.LoadFile(chartpath)
	if err != nil {
		return b, err
	}

	// Buffer a hash + checksums YAML file
	data, err := yaml.Marshal(chart.Metadata)
	if err != nil {
		return b, err
	}

	// FIXME: YAML uses ---\n as a file start indicator, but this is not legal in a PGP
	// clearsign block. So we use ...\n, which is the YAML document end marker.
	// http://yaml.org/spec/1.2/spec.html#id2800168
	b = bytes.NewBuffer(data)
	b.WriteString("\n...\n")

	data, err = yaml.Marshal(sums)
	if err != nil {
		return b, err
	}
	b.Write(data)

	return b, nil
}

// parseMessageBlock
func parseMessageBlock(data []byte) (*hapi.Metadata, *SumCollection, error) {
	// This sucks.
	parts := bytes.Split(data, []byte("\n...\n"))
	if len(parts) < 2 {
		return nil, nil, errors.New("message block must have at least two parts")
	}

	md := &hapi.Metadata{}
	sc := &SumCollection{}

	if err := yaml.Unmarshal(parts[0], md); err != nil {
		return md, sc, err
	}
	err := yaml.Unmarshal(parts[1], sc)
	return md, sc, err
}

// loadKey loads a GPG key found at a particular path.
func loadKey(keypath string) (*openpgp.Entity, error) {
	f, err := os.Open(keypath)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	pr := packet.NewReader(f)
	return openpgp.ReadEntity(pr)
}

func loadKeyRing(ringpath string) (openpgp.EntityList, error) {
	f, err := os.Open(ringpath)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	return openpgp.ReadKeyRing(f)
}

// DigestFile calculates a SHA256 hash (like Docker) for a given file.
//
// It takes the path to the archive file, and returns a string representation of
// the SHA256 sum.
//
// The intended use of this function is to generate a sum of a chart TGZ file.
func DigestFile(filename string) (string, error) {
	f, err := os.Open(filename)
	if err != nil {
		return "", err
	}
	defer f.Close()
	return Digest(f)
}

// Digest hashes a reader and returns a SHA256 digest.
//
// Helm uses SHA256 as its default hash for all non-cryptographic applications.
func Digest(in io.Reader) (string, error) {
	hash := crypto.SHA256.New()
	io.Copy(hash, in)
	return hex.EncodeToString(hash.Sum(nil)), nil
}
<commit_msg>fix(provenance): Ports error check for `Digest` to v3<commit_after>/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package provenance

import (
	"bytes"
	"crypto"
	"encoding/hex"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/pkg/errors"
	"golang.org/x/crypto/openpgp"
	"golang.org/x/crypto/openpgp/clearsign"
	"golang.org/x/crypto/openpgp/packet"
	"sigs.k8s.io/yaml"

	hapi "helm.sh/helm/pkg/chart"
	"helm.sh/helm/pkg/chart/loader"
)

var defaultPGPConfig = packet.Config{
	DefaultHash: crypto.SHA512,
}

// SumCollection represents a collection of file and image checksums.
//
// Files are of the form:
//	FILENAME: "sha256:SUM"
// Images are of the form:
//	"IMAGE:TAG": "sha256:SUM"
// Docker optionally supports sha512, and if this is the case, the hash marker
// will be 'sha512' instead of 'sha256'.
type SumCollection struct {
	Files  map[string]string `json:"files"`
	Images map[string]string `json:"images,omitempty"`
}

// Verification contains information about a verification operation.
type Verification struct {
	// SignedBy contains the entity that signed a chart.
	SignedBy *openpgp.Entity
	// FileHash is the hash, prepended with the scheme, for the file that was verified.
	FileHash string
	// FileName is the name of the file that FileHash verifies.
	FileName string
}

// Signatory signs things.
//
// Signatories can be constructed from a PGP private key file using NewFromFiles
// or they can be constructed manually by setting the Entity to a valid
// PGP entity.
//
// The same Signatory can be used to sign or validate multiple charts.
type Signatory struct {
	// The signatory for this instance of Helm. This is used for signing.
	Entity *openpgp.Entity
	// The keyring for this instance of Helm. This is used for verification.
	KeyRing openpgp.EntityList
}

// NewFromFiles constructs a new Signatory from the PGP key in the given filename.
//
// This will emit an error if it cannot find a valid GPG keyfile (entity) at the
// given location.
//
// Note that the keyfile may have just a public key, just a private key, or
// both. The Signatory methods may have different requirements of the keys. For
// example, ClearSign must have a valid `openpgp.Entity.PrivateKey` before it
// can sign something.
func NewFromFiles(keyfile, keyringfile string) (*Signatory, error) {
	e, err := loadKey(keyfile)
	if err != nil {
		return nil, err
	}

	ring, err := loadKeyRing(keyringfile)
	if err != nil {
		return nil, err
	}

	return &Signatory{
		Entity:  e,
		KeyRing: ring,
	}, nil
}

// NewFromKeyring reads a keyring file and creates a Signatory.
//
// If id is not the empty string, this will also try to find an Entity in the
// keyring whose name matches, and set that as the signing entity. It will return
// an error if the id is not empty and also not found.
func NewFromKeyring(keyringfile, id string) (*Signatory, error) {
	ring, err := loadKeyRing(keyringfile)
	if err != nil {
		return nil, err
	}

	s := &Signatory{KeyRing: ring}

	// If the ID is empty, we can return now.
	if id == "" {
		return s, nil
	}

	// We're gonna go all GnuPG on this and look for a string that _contains_. If
	// two or more keys contain the string and none are a direct match, we error
	// out.
	var candidate *openpgp.Entity
	vague := false
	for _, e := range ring {
		for n := range e.Identities {
			if n == id {
				s.Entity = e
				return s, nil
			}
			if strings.Contains(n, id) {
				if candidate != nil {
					vague = true
				}
				candidate = e
			}
		}
	}
	if vague {
		return s, errors.Errorf("more than one key contain the id %q", id)
	}

	s.Entity = candidate
	return s, nil
}

// PassphraseFetcher returns a passphrase for decrypting keys.
//
// This is used as a callback to read a passphrase from some other location. The
// given name is the Name field on the key, typically of the form:
//
//	USER_NAME (COMMENT) <EMAIL>
type PassphraseFetcher func(name string) ([]byte, error)

// DecryptKey decrypts a private key in the Signatory.
//
// If the key is not encrypted, this will return without error.
//
// If the key does not exist, this will return an error.
//
// If the key exists, but cannot be unlocked with the passphrase returned by
// the PassphraseFetcher, this will return an error.
//
// If the key is successfully unlocked, it will return nil.
func (s *Signatory) DecryptKey(fn PassphraseFetcher) error {
	if s.Entity == nil {
		return errors.New("private key not found")
	} else if s.Entity.PrivateKey == nil {
		return errors.New("provided key is not a private key")
	}

	// Nothing else to do if key is not encrypted.
	if !s.Entity.PrivateKey.Encrypted {
		return nil
	}

	fname := "Unknown"
	for i := range s.Entity.Identities {
		if i != "" {
			fname = i
			break
		}
	}

	p, err := fn(fname)
	if err != nil {
		return err
	}

	return s.Entity.PrivateKey.Decrypt(p)
}

// ClearSign signs a chart with the given key.
//
// This takes the path to a chart archive file and a key, and it returns a clear signature.
//
// The Signatory must have a valid Entity.PrivateKey for this to work. If it does
// not, an error will be returned.
func (s *Signatory) ClearSign(chartpath string) (string, error) {
	if s.Entity == nil {
		return "", errors.New("private key not found")
	} else if s.Entity.PrivateKey == nil {
		return "", errors.New("provided key is not a private key")
	}

	if fi, err := os.Stat(chartpath); err != nil {
		return "", err
	} else if fi.IsDir() {
		return "", errors.New("cannot sign a directory")
	}

	out := bytes.NewBuffer(nil)

	b, err := messageBlock(chartpath)
	if err != nil {
		return "", nil
	}

	// Sign the buffer
	w, err := clearsign.Encode(out, s.Entity.PrivateKey, &defaultPGPConfig)
	if err != nil {
		return "", err
	}
	_, err = io.Copy(w, b)
	w.Close()
	return out.String(), err
}

// Verify checks a signature and verifies that it is legit for a chart.
func (s *Signatory) Verify(chartpath, sigpath string) (*Verification, error) {
	ver := &Verification{}
	for _, fname := range []string{chartpath, sigpath} {
		if fi, err := os.Stat(fname); err != nil {
			return ver, err
		} else if fi.IsDir() {
			return ver, errors.Errorf("%s cannot be a directory", fname)
		}
	}

	// First verify the signature
	sig, err := s.decodeSignature(sigpath)
	if err != nil {
		return ver, errors.Wrap(err, "failed to decode signature")
	}

	by, err := s.verifySignature(sig)
	if err != nil {
		return ver, err
	}
	ver.SignedBy = by

	// Second, verify the hash of the tarball.
	sum, err := DigestFile(chartpath)
	if err != nil {
		return ver, err
	}
	_, sums, err := parseMessageBlock(sig.Plaintext)
	if err != nil {
		return ver, err
	}

	sum = "sha256:" + sum
	basename := filepath.Base(chartpath)
	if sha, ok := sums.Files[basename]; !ok {
		return ver, errors.Errorf("provenance does not contain a SHA for a file named %q", basename)
	} else if sha != sum {
		return ver, errors.Errorf("sha256 sum does not match for %s: %q != %q", basename, sha, sum)
	}
	ver.FileHash = sum
	ver.FileName = basename

	// TODO: when image signing is added, verify that here.

	return ver, nil
}

func (s *Signatory) decodeSignature(filename string) (*clearsign.Block, error) {
	data, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}

	block, _ := clearsign.Decode(data)
	if block == nil {
		// There was no sig in the file.
		return nil, errors.New("signature block not found")
	}

	return block, nil
}

// verifySignature verifies that the given block is validly signed, and returns the signer.
func (s *Signatory) verifySignature(block *clearsign.Block) (*openpgp.Entity, error) {
	return openpgp.CheckDetachedSignature(
		s.KeyRing,
		bytes.NewBuffer(block.Bytes),
		block.ArmoredSignature.Body,
	)
}

func messageBlock(chartpath string) (*bytes.Buffer, error) {
	var b *bytes.Buffer
	// Checksum the archive
	chash, err := DigestFile(chartpath)
	if err != nil {
		return b, err
	}

	base := filepath.Base(chartpath)
	sums := &SumCollection{
		Files: map[string]string{
			base: "sha256:" + chash,
		},
	}

	// Load the archive into memory.
	chart, err := loader.LoadFile(chartpath)
	if err != nil {
		return b, err
	}

	// Buffer a hash + checksums YAML file
	data, err := yaml.Marshal(chart.Metadata)
	if err != nil {
		return b, err
	}

	// FIXME: YAML uses ---\n as a file start indicator, but this is not legal in a PGP
	// clearsign block. So we use ...\n, which is the YAML document end marker.
	// http://yaml.org/spec/1.2/spec.html#id2800168
	b = bytes.NewBuffer(data)
	b.WriteString("\n...\n")

	data, err = yaml.Marshal(sums)
	if err != nil {
		return b, err
	}
	b.Write(data)

	return b, nil
}

// parseMessageBlock
func parseMessageBlock(data []byte) (*hapi.Metadata, *SumCollection, error) {
	// This sucks.
	parts := bytes.Split(data, []byte("\n...\n"))
	if len(parts) < 2 {
		return nil, nil, errors.New("message block must have at least two parts")
	}

	md := &hapi.Metadata{}
	sc := &SumCollection{}

	if err := yaml.Unmarshal(parts[0], md); err != nil {
		return md, sc, err
	}
	err := yaml.Unmarshal(parts[1], sc)
	return md, sc, err
}

// loadKey loads a GPG key found at a particular path.
func loadKey(keypath string) (*openpgp.Entity, error) {
	f, err := os.Open(keypath)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	pr := packet.NewReader(f)
	return openpgp.ReadEntity(pr)
}

func loadKeyRing(ringpath string) (openpgp.EntityList, error) {
	f, err := os.Open(ringpath)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	return openpgp.ReadKeyRing(f)
}

// DigestFile calculates a SHA256 hash (like Docker) for a given file.
//
// It takes the path to the archive file, and returns a string representation of
// the SHA256 sum.
//
// The intended use of this function is to generate a sum of a chart TGZ file.
func DigestFile(filename string) (string, error) {
	f, err := os.Open(filename)
	if err != nil {
		return "", err
	}
	defer f.Close()
	return Digest(f)
}

// Digest hashes a reader and returns a SHA256 digest.
//
// Helm uses SHA256 as its default hash for all non-cryptographic applications.
func Digest(in io.Reader) (string, error) {
	hash := crypto.SHA256.New()
	if _, err := io.Copy(hash, in); err != nil {
		return "", nil
	}
	return hex.EncodeToString(hash.Sum(nil)), nil
}
<|endoftext|>


<commit_before>/*
 * MinIO Go Library for Amazon S3 Compatible Cloud Storage
 * Copyright 2015-2017 MinIO, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package minio

import (
	"encoding/base64"
	"fmt"
	"strings"
	"time"
)

// expirationDateFormat date format for expiration key in json policy.
const expirationDateFormat = "2006-01-02T15:04:05.999Z"

// policyCondition explanation:
// http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
//
// Example:
//
//   policyCondition {
//       matchType: "$eq",
//       key: "$Content-Type",
//       value: "image/png",
//   }
//
type policyCondition struct {
	matchType string
	condition string
	value     string
}

// PostPolicy - Provides strict static type conversion and validation
// for Amazon S3's POST policy JSON string.
type PostPolicy struct {
	// Expiration date and time of the POST policy.
	expiration time.Time
	// Collection of different policy conditions.
	conditions []policyCondition
	// ContentLengthRange minimum and maximum allowable size for the
	// uploaded content.
	contentLengthRange struct {
		min int64
		max int64
	}

	// Post form data.
	formData map[string]string
}

// NewPostPolicy - Instantiate new post policy.
func NewPostPolicy() *PostPolicy {
	p := &PostPolicy{}
	p.conditions = make([]policyCondition, 0)
	p.formData = make(map[string]string)
	return p
}

// SetExpires - Sets expiration time for the new policy.
func (p *PostPolicy) SetExpires(t time.Time) error {
	if t.IsZero() {
		return ErrInvalidArgument("No expiry time set.")
	}
	p.expiration = t
	return nil
}

// SetKey - Sets an object name for the policy based upload.
func (p *PostPolicy) SetKey(key string) error {
	if strings.TrimSpace(key) == "" || key == "" {
		return ErrInvalidArgument("Object name is empty.")
	}
	policyCond := policyCondition{
		matchType: "eq",
		condition: "$key",
		value:     key,
	}
	if err := p.addNewPolicy(policyCond); err != nil {
		return err
	}
	p.formData["key"] = key
	return nil
}

// SetKeyStartsWith - Sets an object name that an policy based upload
// can start with.
func (p *PostPolicy) SetKeyStartsWith(keyStartsWith string) error {
	if strings.TrimSpace(keyStartsWith) == "" || keyStartsWith == "" {
		return ErrInvalidArgument("Object prefix is empty.")
	}
	policyCond := policyCondition{
		matchType: "starts-with",
		condition: "$key",
		value:     keyStartsWith,
	}
	if err := p.addNewPolicy(policyCond); err != nil {
		return err
	}
	p.formData["key"] = keyStartsWith
	return nil
}

// SetBucket - Sets bucket at which objects will be uploaded to.
func (p *PostPolicy) SetBucket(bucketName string) error {
	if strings.TrimSpace(bucketName) == "" || bucketName == "" {
		return ErrInvalidArgument("Bucket name is empty.")
	}
	policyCond := policyCondition{
		matchType: "eq",
		condition: "$bucket",
		value:     bucketName,
	}
	if err := p.addNewPolicy(policyCond); err != nil {
		return err
	}
	p.formData["bucket"] = bucketName
	return nil
}

// SetContentType - Sets content-type of the object for this policy
// based upload.
func (p *PostPolicy) SetContentType(contentType string) error {
	if strings.TrimSpace(contentType) == "" || contentType == "" {
		return ErrInvalidArgument("No content type specified.")
	}
	policyCond := policyCondition{
		matchType: "eq",
		condition: "$Content-Type",
		value:     contentType,
	}
	if err := p.addNewPolicy(policyCond); err != nil {
		return err
	}
	p.formData["Content-Type"] = contentType
	return nil
}

// SetContentLengthRange - Set new min and max content length
// condition for all incoming uploads.
func (p *PostPolicy) SetContentLengthRange(min, max int64) error {
	if min > max {
		return ErrInvalidArgument("Minimum limit is larger than maximum limit.")
	}
	if min < 0 {
		return ErrInvalidArgument("Minimum limit cannot be negative.")
	}
	if max < 0 {
		return ErrInvalidArgument("Maximum limit cannot be negative.")
	}
	p.contentLengthRange.min = min
	p.contentLengthRange.max = max
	return nil
}

// SetSuccessStatusAction - Sets the status success code of the object for this policy
// based upload.
func (p *PostPolicy) SetSuccessStatusAction(status string) error {
	if strings.TrimSpace(status) == "" || status == "" {
		return ErrInvalidArgument("Status is empty")
	}
	policyCond := policyCondition{
		matchType: "eq",
		condition: "$success_action_status",
		value:     status,
	}
	if err := p.addNewPolicy(policyCond); err != nil {
		return err
	}
	p.formData["success_action_status"] = status
	return nil
}

// SetUserMetadata - Set user metadata as a key/value couple.
// Can be retrieved through a HEAD request or an event.
func (p *PostPolicy) SetUserMetadata(key string, value string) error {
	if strings.TrimSpace(key) == "" || key == "" {
		return ErrInvalidArgument("Key is empty")
	}
	if strings.TrimSpace(value) == "" || value == "" {
		return ErrInvalidArgument("Value is empty")
	}
	headerName := fmt.Sprintf("x-amz-meta-%s", key)
	policyCond := policyCondition{
		matchType: "eq",
		condition: fmt.Sprintf("$%s", headerName),
		value:     value,
	}
	if err := p.addNewPolicy(policyCond); err != nil {
		return err
	}
	p.formData[headerName] = value
	return nil
}

// SetUserData - Set user data as a key/value couple.
// Can be retrieved through a HEAD request or an event.
func (p *PostPolicy) SetUserData(key string, value string) error {
	if key == "" {
		return ErrInvalidArgument("Key is empty")
	}
	if value == "" {
		return ErrInvalidArgument("Value is empty")
	}
	headerName := fmt.Sprintf("x-amz-%s", key)
	policyCond := policyCondition{
		matchType: "eq",
		condition: fmt.Sprintf("$%s", headerName),
		value:     value,
	}
	if err := p.addNewPolicy(policyCond); err != nil {
		return err
	}
	p.formData[headerName] = value
	return nil
}

// addNewPolicy - internal helper to validate adding new policies.
func (p *PostPolicy) addNewPolicy(policyCond policyCondition) error {
	if policyCond.matchType == "" || policyCond.condition == "" || policyCond.value == "" {
		return ErrInvalidArgument("Policy fields are empty.")
	}
	p.conditions = append(p.conditions, policyCond)
	return nil
}

// Stringer interface for printing policy in json formatted string.
func (p PostPolicy) String() string {
	return string(p.marshalJSON())
}

// marshalJSON - Provides Marshalled JSON in bytes.
func (p PostPolicy) marshalJSON() []byte {
	expirationStr := `"expiration":"` + p.expiration.Format(expirationDateFormat) + `"`
	var conditionsStr string
	conditions := []string{}
	for _, po := range p.conditions {
		conditions = append(conditions, fmt.Sprintf("[\"%s\",\"%s\",\"%s\"]", po.matchType, po.condition, po.value))
	}
	if p.contentLengthRange.min != 0 || p.contentLengthRange.max != 0 {
		conditions = append(conditions, fmt.Sprintf("[\"content-length-range\", %d, %d]",
			p.contentLengthRange.min, p.contentLengthRange.max))
	}
	if len(conditions) > 0 {
		conditionsStr = `"conditions":[` + strings.Join(conditions, ",") + "]"
	}
	retStr := "{"
	retStr = retStr + expirationStr + ","
	retStr = retStr + conditionsStr
	retStr = retStr + "}"
	return []byte(retStr)
}

// base64 - Produces base64 of PostPolicy's Marshalled json.
func (p PostPolicy) base64() string {
	return base64.StdEncoding.EncodeToString(p.marshalJSON())
}
<commit_msg>Add funcion to set policy parameter (#1200)<commit_after>/*
 * MinIO Go Library for Amazon S3 Compatible Cloud Storage
 * Copyright 2015-2017 MinIO, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package minio

import (
	"encoding/base64"
	"fmt"
	"strings"
	"time"
)

// expirationDateFormat date format for expiration key in json policy.
const expirationDateFormat = "2006-01-02T15:04:05.999Z"

// policyCondition explanation:
// http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
//
// Example:
//
//   policyCondition {
//       matchType: "$eq",
//       key: "$Content-Type",
//       value: "image/png",
//   }
//
type policyCondition struct {
	matchType string
	condition string
	value     string
}

// PostPolicy - Provides strict static type conversion and validation
// for Amazon S3's POST policy JSON string.
type PostPolicy struct {
	// Expiration date and time of the POST policy.
	expiration time.Time
	// Collection of different policy conditions.
	conditions []policyCondition
	// ContentLengthRange minimum and maximum allowable size for the
	// uploaded content.
	contentLengthRange struct {
		min int64
		max int64
	}

	// Post form data.
	formData map[string]string
}

// NewPostPolicy - Instantiate new post policy.
func NewPostPolicy() *PostPolicy {
	p := &PostPolicy{}
	p.conditions = make([]policyCondition, 0)
	p.formData = make(map[string]string)
	return p
}

// SetExpires - Sets expiration time for the new policy.
func (p *PostPolicy) SetExpires(t time.Time) error {
	if t.IsZero() {
		return ErrInvalidArgument("No expiry time set.")
	}
	p.expiration = t
	return nil
}

// SetKey - Sets an object name for the policy based upload.
func (p *PostPolicy) SetKey(key string) error {
	if strings.TrimSpace(key) == "" || key == "" {
		return ErrInvalidArgument("Object name is empty.")
	}
	policyCond := policyCondition{
		matchType: "eq",
		condition: "$key",
		value:     key,
	}
	if err := p.addNewPolicy(policyCond); err != nil {
		return err
	}
	p.formData["key"] = key
	return nil
}

// SetKeyStartsWith - Sets an object name that an policy based upload
// can start with.
func (p *PostPolicy) SetKeyStartsWith(keyStartsWith string) error {
	if strings.TrimSpace(keyStartsWith) == "" || keyStartsWith == "" {
		return ErrInvalidArgument("Object prefix is empty.")
	}
	policyCond := policyCondition{
		matchType: "starts-with",
		condition: "$key",
		value:     keyStartsWith,
	}
	if err := p.addNewPolicy(policyCond); err != nil {
		return err
	}
	p.formData["key"] = keyStartsWith
	return nil
}

// SetBucket - Sets bucket at which objects will be uploaded to.
func (p *PostPolicy) SetBucket(bucketName string) error {
	if strings.TrimSpace(bucketName) == "" || bucketName == "" {
		return ErrInvalidArgument("Bucket name is empty.")
	}
	policyCond := policyCondition{
		matchType: "eq",
		condition: "$bucket",
		value:     bucketName,
	}
	if err := p.addNewPolicy(policyCond); err != nil {
		return err
	}
	p.formData["bucket"] = bucketName
	return nil
}

// SetCondition - Sets condition for credentials, date and algorithm
func (p *PostPolicy) SetCondition(matchType, condition, value string) error {
	if strings.TrimSpace(value) == "" || value == "" {
		return ErrInvalidArgument("No value specified for condition")
	}

	policyCond := policyCondition{
		matchType: matchType,
		condition: "$" + condition,
		value:     value,
	}
	if condition == "X-Amz-Credential" || condition == "X-Amz-Date" || condition == "X-Amz-Algorithm" {
		if err := p.addNewPolicy(policyCond); err != nil {
			return err
		}
		p.formData[condition] = value
		return nil
	}
	return ErrInvalidArgument("Invalid condition in policy")
}

// SetContentType - Sets content-type of the object for this policy
// based upload.
func (p *PostPolicy) SetContentType(contentType string) error {
	if strings.TrimSpace(contentType) == "" || contentType == "" {
		return ErrInvalidArgument("No content type specified.")
	}
	policyCond := policyCondition{
		matchType: "eq",
		condition: "$Content-Type",
		value:     contentType,
	}
	if err := p.addNewPolicy(policyCond); err != nil {
		return err
	}
	p.formData["Content-Type"] = contentType
	return nil
}

// SetContentLengthRange - Set new min and max content length
// condition for all incoming uploads.
func (p *PostPolicy) SetContentLengthRange(min, max int64) error {
	if min > max {
		return ErrInvalidArgument("Minimum limit is larger than maximum limit.")
	}
	if min < 0 {
		return ErrInvalidArgument("Minimum limit cannot be negative.")
	}
	if max < 0 {
		return ErrInvalidArgument("Maximum limit cannot be negative.")
	}
	p.contentLengthRange.min = min
	p.contentLengthRange.max = max
	return nil
}

// SetSuccessStatusAction - Sets the status success code of the object for this policy
// based upload.
func (p *PostPolicy) SetSuccessStatusAction(status string) error {
	if strings.TrimSpace(status) == "" || status == "" {
		return ErrInvalidArgument("Status is empty")
	}
	policyCond := policyCondition{
		matchType: "eq",
		condition: "$success_action_status",
		value:     status,
	}
	if err := p.addNewPolicy(policyCond); err != nil {
		return err
	}
	p.formData["success_action_status"] = status
	return nil
}

// SetUserMetadata - Set user metadata as a key/value couple.
// Can be retrieved through a HEAD request or an event.
func (p *PostPolicy) SetUserMetadata(key string, value string) error {
	if strings.TrimSpace(key) == "" || key == "" {
		return ErrInvalidArgument("Key is empty")
	}
	if strings.TrimSpace(value) == "" || value == "" {
		return ErrInvalidArgument("Value is empty")
	}
	headerName := fmt.Sprintf("x-amz-meta-%s", key)
	policyCond := policyCondition{
		matchType: "eq",
		condition: fmt.Sprintf("$%s", headerName),
		value:     value,
	}
	if err := p.addNewPolicy(policyCond); err != nil {
		return err
	}
	p.formData[headerName] = value
	return nil
}

// SetUserData - Set user data as a key/value couple.
// Can be retrieved through a HEAD request or an event.
func (p *PostPolicy) SetUserData(key string, value string) error {
	if key == "" {
		return ErrInvalidArgument("Key is empty")
	}
	if value == "" {
		return ErrInvalidArgument("Value is empty")
	}
	headerName := fmt.Sprintf("x-amz-%s", key)
	policyCond := policyCondition{
		matchType: "eq",
		condition: fmt.Sprintf("$%s", headerName),
		value:     value,
	}
	if err := p.addNewPolicy(policyCond); err != nil {
		return err
	}
	p.formData[headerName] = value
	return nil
}

// addNewPolicy - internal helper to validate adding new policies.
func (p *PostPolicy) addNewPolicy(policyCond policyCondition) error {
	if policyCond.matchType == "" || policyCond.condition == "" || policyCond.value == "" {
		return ErrInvalidArgument("Policy fields are empty.")
	}
	p.conditions = append(p.conditions, policyCond)
	return nil
}

// Stringer interface for printing policy in json formatted string.
func (p PostPolicy) String() string {
	return string(p.marshalJSON())
}

// marshalJSON - Provides Marshalled JSON in bytes.
func (p PostPolicy) marshalJSON() []byte {
	expirationStr := `"expiration":"` + p.expiration.Format(expirationDateFormat) + `"`
	var conditionsStr string
	conditions := []string{}
	for _, po := range p.conditions {
		conditions = append(conditions, fmt.Sprintf("[\"%s\",\"%s\",\"%s\"]", po.matchType, po.condition, po.value))
	}
	if p.contentLengthRange.min != 0 || p.contentLengthRange.max != 0 {
		conditions = append(conditions, fmt.Sprintf("[\"content-length-range\", %d, %d]",
			p.contentLengthRange.min, p.contentLengthRange.max))
	}
	if len(conditions) > 0 {
		conditionsStr = `"conditions":[` + strings.Join(conditions, ",") + "]"
	}
	retStr := "{"
	retStr = retStr + expirationStr + ","
	retStr = retStr + conditionsStr
	retStr = retStr + "}"
	return []byte(retStr)
}

// base64 - Produces base64 of PostPolicy's Marshalled json.
func (p PostPolicy) base64() string {
	return base64.StdEncoding.EncodeToString(p.marshalJSON())
}
<|endoftext|>


<commit_before>package csv

import (
	"bytes"
	"fmt"
	"io"
	"reflect"
	"strings"
)

type Encoder struct {
	basicEncoder
	path [][]string
}

func NewEncoder(w io.Writer) *Encoder {
	return &Encoder{
		basicEncoder: basicEncoder{
			delimiter: ',',
			tagKey:    "csv",
			lineBreak: "\n",
			w:         w,
		},
	}
}

func (e *Encoder) SetDelimeter(delim rune) *Encoder       { e.delimiter = delim; return e }
func (e *Encoder) SetTagKey(tagKey string) *Encoder       { e.tagKey = tagKey; return e }
func (e *Encoder) SetLineBreak(lineBreak string) *Encoder { e.lineBreak = lineBreak; return e }
func (e *Encoder) SetExpandPath(path ...string) *Encoder {
	e.path = make([][]string, len(path))
	for i := range path {
		e.path[i] = strings.Split(path[i], ".")
	}
	return e
}

func (e *Encoder) Encode(value interface{}) error {
	if len(e.path) == 0 {
		return e.basicEncoder.encodeLine(value)
	}
	v := reflect.ValueOf(value)
	return e.expand(v)
}

func (e *Encoder) Tags() []Tag     { return e.tags }
func (e *Encoder) Names() []string { return e.names }

func (e *Encoder) expand(v reflect.Value) error {
	fields, err := e.marshal(v)
	if err != nil {
		return err
	}
	slice, err := getSlice(v, e.path[0])
	if err != nil {
		return err
	}
	its := its{{
		slice:  slice,
		prefix: fields,
	}}
	for {
		it := its.top()
		v, ok := it.next()
		if !ok {
			its.pop()
			break
		}
		fields, err := e.marshal(v)
		if err != nil {
			return err
		}
		if it.level+1 < len(e.path) {
			slice, err := getSlice(v, e.path[it.level+1])
			if err != nil {
				return err
			}
			its.push(&iter{
				slice:  slice,
				level:  it.level + 1,
				prefix: fields,
			})
			continue
		}
		if _, err := e.w.Write(bytes.Join(append(its.prefixes(), fields), []byte(string(e.delimiter)))); err != nil {
			return err
		}
		e.written = true
		if _, err := e.w.Write([]byte(e.lineBreak)); err != nil {
			return err
		}
	}
	return nil
}

func (e *Encoder) marshal(v reflect.Value) ([]byte, error) {
	w := new(bytes.Buffer)
	enc := basicEncoder{w: w, delimiter: e.delimiter, tagKey: e.tagKey}
	if err := enc.encodeValue(v); err != nil {
		return nil, err
	}
	if !e.written { // only accumulate names for the first record
		e.tags = append(e.tags, enc.tags...)
		e.names = append(e.names, enc.names...)
	}
	return w.Bytes(), nil
}

type its []*iter

func (s *its) top() *iter {
	return (*s)[len(*s)-1]
}

func (s *its) push(it *iter) {
	*s = append(*s, it)
}

func (s *its) pop() (res *iter) {
	*s, res = (*s)[:len(*s)-1], (*s)[len(*s)-1]
	return
}

func (s *its) prefixes() [][]byte {
	bufs := make([][]byte, len(*s))
	for i := range *s {
		bufs[i] = (*s)[i].prefix
	}
	return bufs
}

type iter struct {
	slice  reflect.Value
	i      int
	level  int
	prefix []byte
}

func (it *iter) next() (reflect.Value, bool) {
	v := reflect.New(it.slice.Type().Elem()).Elem()
	if it.i >= it.slice.Len() {
		return reflect.Value{}, false
	}
	v.Set(it.slice.Index(it.i))
	it.i++
	return v, it.i <= it.slice.Len()
}

func getSlice(v reflect.Value, path []string) (reflect.Value, error) {
	for _, name := range path {
		if v.Kind() == reflect.Ptr && !v.IsNil() {
			v = v.Elem()
		}
		if v.Kind() != reflect.Struct {
			return reflect.Value{}, fmt.Errorf("expect struct type but got %v in path %v", v.Kind(), path)
		}
		v = v.FieldByName(name)
	}
	switch v.Kind() {
	case reflect.Slice, reflect.Array:
	default:
		return reflect.Value{}, fmt.Errorf("expect slice or array type but got %v for path %v", v.Kind(), path)
	}
	return v, nil
}
<commit_msg>fix typo<commit_after>package csv

import (
	"bytes"
	"fmt"
	"io"
	"reflect"
	"strings"
)

type Encoder struct {
	basicEncoder
	path [][]string
}

func NewEncoder(w io.Writer) *Encoder {
	return &Encoder{
		basicEncoder: basicEncoder{
			delimiter: ',',
			tagKey:    "csv",
			lineBreak: "\n",
			w:         w,
		},
	}
}

func (e *Encoder) SetDelimiter(delim rune) *Encoder       { e.delimiter = delim; return e }
func (e *Encoder) SetTagKey(tagKey string) *Encoder       { e.tagKey = tagKey; return e }
func (e *Encoder) SetLineBreak(lineBreak string) *Encoder { e.lineBreak = lineBreak; return e }
func (e *Encoder) SetExpandPath(path ...string) *Encoder {
	e.path = make([][]string, len(path))
	for i := range path {
		e.path[i] = strings.Split(path[i], ".")
	}
	return e
}

func (e *Encoder) Encode(value interface{}) error {
	if len(e.path) == 0 {
		return e.basicEncoder.encodeLine(value)
	}
	v := reflect.ValueOf(value)
	return e.expand(v)
}

func (e *Encoder) Tags() []Tag     { return e.tags }
func (e *Encoder) Names() []string { return e.names }

func (e *Encoder) expand(v reflect.Value) error {
	fields, err := e.marshal(v)
	if err != nil {
		return err
	}
	slice, err := getSlice(v, e.path[0])
	if err != nil {
		return err
	}
	its := its{{
		slice:  slice,
		prefix: fields,
	}}
	for {
		it := its.top()
		v, ok := it.next()
		if !ok {
			its.pop()
			break
		}
		fields, err := e.marshal(v)
		if err != nil {
			return err
		}
		if it.level+1 < len(e.path) {
			slice, err := getSlice(v, e.path[it.level+1])
			if err != nil {
				return err
			}
			its.push(&iter{
				slice:  slice,
				level:  it.level + 1,
				prefix: fields,
			})
			continue
		}
		if _, err := e.w.Write(bytes.Join(append(its.prefixes(), fields), []byte(string(e.delimiter)))); err != nil {
			return err
		}
		e.written = true
		if _, err := e.w.Write([]byte(e.lineBreak)); err != nil {
			return err
		}
	}
	return nil
}

func (e *Encoder) marshal(v reflect.Value) ([]byte, error) {
	w := new(bytes.Buffer)
	enc := basicEncoder{w: w, delimiter: e.delimiter, tagKey: e.tagKey}
	if err := enc.encodeValue(v); err != nil {
		return nil, err
	}
	if !e.written { // only accumulate names for the first record
		e.tags = append(e.tags, enc.tags...)
		e.names = append(e.names, enc.names...)
	}
	return w.Bytes(), nil
}

type its []*iter

func (s *its) top() *iter {
	return (*s)[len(*s)-1]
}

func (s *its) push(it *iter) {
	*s = append(*s, it)
}

func (s *its) pop() (res *iter) {
	*s, res = (*s)[:len(*s)-1], (*s)[len(*s)-1]
	return
}

func (s *its) prefixes() [][]byte {
	bufs := make([][]byte, len(*s))
	for i := range *s {
		bufs[i] = (*s)[i].prefix
	}
	return bufs
}

type iter struct {
	slice  reflect.Value
	i      int
	level  int
	prefix []byte
}

func (it *iter) next() (reflect.Value, bool) {
	v := reflect.New(it.slice.Type().Elem()).Elem()
	if it.i >= it.slice.Len() {
		return reflect.Value{}, false
	}
	v.Set(it.slice.Index(it.i))
	it.i++
	return v, it.i <= it.slice.Len()
}

func getSlice(v reflect.Value, path []string) (reflect.Value, error) {
	for _, name := range path {
		if v.Kind() == reflect.Ptr && !v.IsNil() {
			v = v.Elem()
		}
		if v.Kind() != reflect.Struct {
			return reflect.Value{}, fmt.Errorf("expect struct type but got %v in path %v", v.Kind(), path)
		}
		v = v.FieldByName(name)
	}
	switch v.Kind() {
	case reflect.Slice, reflect.Array:
	default:
		return reflect.Value{}, fmt.Errorf("expect slice or array type but got %v for path %v", v.Kind(), path)
	}
	return v, nil
}
<|endoftext|>


<commit_before>package web

import (
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/hellofresh/janus/pkg/api"
	"github.com/hellofresh/janus/pkg/errors"
	"github.com/hellofresh/janus/pkg/opentracing"
	"github.com/hellofresh/janus/pkg/render"
	"github.com/hellofresh/janus/pkg/router"
)

// APIHandler is the api rest controller
type APIHandler struct {
	configurationChan chan<- api.ConfigurationMessage
	Cfgs              *api.Configuration
}

// NewAPIHandler creates a new instance of Controller
func NewAPIHandler(cfgChan chan<- api.ConfigurationMessage) *APIHandler {
	return &APIHandler{
		configurationChan: cfgChan,
	}
}

// Get is the find all handler
func (c *APIHandler) Get() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		span := opentracing.FromContext(r.Context(), "definitions.GetAll")
		defer span.Finish()

		if c.Cfgs.Definitions == nil {
			// id definitions list is empty - fake it with simple slice to get the empty JSON array in the output
			render.JSON(w, http.StatusOK, []int{})
			return
		}

		render.JSON(w, http.StatusOK, c.Cfgs.Definitions)
	}
}

// GetBy is the find by handler
func (c *APIHandler) GetBy() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		name := router.URLParam(r, "name")
		span := opentracing.FromContext(r.Context(), "datastore.FindByName")
		cfg := c.findByName(name)
		span.Finish()

		if cfg == nil {
			errors.Handler(w, api.ErrAPIDefinitionNotFound)
			return
		}

		render.JSON(w, http.StatusOK, cfg)
	}
}

// PutBy is the update handler
func (c *APIHandler) PutBy() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var err error

		name := router.URLParam(r, "name")
		span := opentracing.FromContext(r.Context(), "datastore.FindByName")
		cfg := c.findByName(name)
		span.Finish()

		if cfg == nil {
			errors.Handler(w, api.ErrAPIDefinitionNotFound)
			return
		}

		err = json.NewDecoder(r.Body).Decode(cfg)
		if err != nil {
			errors.Handler(w, err)
			return
		}

		isValid, err := cfg.Validate()
		if false == isValid && err != nil {
			errors.Handler(w, errors.New(http.StatusBadRequest, err.Error()))
			return
		}

		// avoid situation when trying to update existing definition with new path
		// that is already registered with another name
		span = opentracing.FromContext(r.Context(), "datastore.FindByListenPath")
		existingCfg := c.findByListenPath(cfg.Proxy.ListenPath)
		span.Finish()

		if existingCfg != nil && existingCfg.Name != cfg.Name {
			errors.Handler(w, api.ErrAPIListenPathExists)
			return
		}

		span = opentracing.FromContext(r.Context(), "datastore.Update")
		c.configurationChan <- api.ConfigurationMessage{
			Operation:     api.UpdatedOperation,
			Configuration: cfg,
		}
		span.Finish()

		w.WriteHeader(http.StatusOK)
	}
}

// Post is the create handler
func (c *APIHandler) Post() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		cfg := api.NewDefinition()

		err := json.NewDecoder(r.Body).Decode(cfg)
		if nil != err {
			errors.Handler(w, err)
			return
		}

		isValid, err := cfg.Validate()
		if false == isValid && err != nil {
			errors.Handler(w, errors.New(http.StatusBadRequest, err.Error()))
			return
		}

		span := opentracing.FromContext(r.Context(), "datastore.Exists")
		exists, err := c.exists(cfg)
		span.Finish()

		if err != nil || exists {
			errors.Handler(w, err)
			return
		}

		span = opentracing.FromContext(r.Context(), "datastore.Add")
		c.configurationChan <- api.ConfigurationMessage{
			Operation:     api.AddedOperation,
			Configuration: cfg,
		}
		span.Finish()

		w.Header().Add("Location", fmt.Sprintf("/apis/%s", cfg.Name))
		w.WriteHeader(http.StatusCreated)
	}
}

// DeleteBy is the delete handler
func (c *APIHandler) DeleteBy() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		span := opentracing.FromContext(r.Context(), "datastore.Remove")
		defer span.Finish()

		name := router.URLParam(r, "name")
		cfg := c.findByName(name)
		if cfg == nil {
			errors.Handler(w, api.ErrAPIDefinitionNotFound)
			return
		}

		c.configurationChan <- api.ConfigurationMessage{
			Operation:     api.RemovedOperation,
			Configuration: cfg,
		}

		w.WriteHeader(http.StatusNoContent)
	}
}

func (c *APIHandler) exists(cfg *api.Definition) (bool, error) {
	for _, storedCfg := range c.Cfgs.Definitions {
		if storedCfg.Name == cfg.Name {
			return true, api.ErrAPINameExists
		}

		if storedCfg.Proxy.ListenPath == cfg.Proxy.ListenPath {
			return true, api.ErrAPIListenPathExists
		}
	}

	return false, nil
}

func (c *APIHandler) findByName(name string) *api.Definition {
	for _, cfg := range c.Cfgs.Definitions {
		if cfg.Name == name {
			return cfg
		}
	}

	return nil
}

func (c *APIHandler) findByListenPath(listenPath string) *api.Definition {
	for _, cfg := range c.Cfgs.Definitions {
		if cfg.Proxy.ListenPath == listenPath {
			return cfg
		}
	}

	return nil
}
<commit_msg>Validate plugin configuration on PUT and POST<commit_after>package web

import (
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/hellofresh/janus/pkg/plugin"

	"github.com/hellofresh/janus/pkg/api"
	"github.com/hellofresh/janus/pkg/errors"
	"github.com/hellofresh/janus/pkg/opentracing"
	"github.com/hellofresh/janus/pkg/render"
	"github.com/hellofresh/janus/pkg/router"
)

// APIHandler is the api rest controller
type APIHandler struct {
	configurationChan chan<- api.ConfigurationMessage
	Cfgs              *api.Configuration
}

// NewAPIHandler creates a new instance of Controller
func NewAPIHandler(cfgChan chan<- api.ConfigurationMessage) *APIHandler {
	return &APIHandler{
		configurationChan: cfgChan,
	}
}

// Get is the find all handler
func (c *APIHandler) Get() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		span := opentracing.FromContext(r.Context(), "definitions.GetAll")
		defer span.Finish()

		if c.Cfgs.Definitions == nil {
			// id definitions list is empty - fake it with simple slice to get the empty JSON array in the output
			render.JSON(w, http.StatusOK, []int{})
			return
		}

		render.JSON(w, http.StatusOK, c.Cfgs.Definitions)
	}
}

// GetBy is the find by handler
func (c *APIHandler) GetBy() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		name := router.URLParam(r, "name")
		span := opentracing.FromContext(r.Context(), "datastore.FindByName")
		cfg := c.findByName(name)
		span.Finish()

		if cfg == nil {
			errors.Handler(w, api.ErrAPIDefinitionNotFound)
			return
		}

		render.JSON(w, http.StatusOK, cfg)
	}
}

// PutBy is the update handler
func (c *APIHandler) PutBy() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var err error

		name := router.URLParam(r, "name")
		span := opentracing.FromContext(r.Context(), "datastore.FindByName")
		cfg := c.findByName(name)
		span.Finish()

		if cfg == nil {
			errors.Handler(w, api.ErrAPIDefinitionNotFound)
			return
		}

		err = json.NewDecoder(r.Body).Decode(cfg)
		if err != nil {
			errors.Handler(w, err)
			return
		}

		isValid, err := cfg.Validate()
		if false == isValid && err != nil {
			errors.Handler(w, errors.New(http.StatusBadRequest, err.Error()))
			return
		}

		// Additionally validate plugin configuration
		for _, plg := range cfg.Plugins {
			isValid, err := plugin.ValidateConfig(plg.Name, plg.Config)
			if !isValid || err != nil {
				errors.Handler(w, errors.New(http.StatusBadRequest, err.Error()))
				return
			}
		}

		// avoid situation when trying to update existing definition with new path
		// that is already registered with another name
		span = opentracing.FromContext(r.Context(), "datastore.FindByListenPath")
		existingCfg := c.findByListenPath(cfg.Proxy.ListenPath)
		span.Finish()

		if existingCfg != nil && existingCfg.Name != cfg.Name {
			errors.Handler(w, api.ErrAPIListenPathExists)
			return
		}

		span = opentracing.FromContext(r.Context(), "datastore.Update")
		c.configurationChan <- api.ConfigurationMessage{
			Operation:     api.UpdatedOperation,
			Configuration: cfg,
		}
		span.Finish()

		w.WriteHeader(http.StatusOK)
	}
}

// Post is the create handler
func (c *APIHandler) Post() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		cfg := api.NewDefinition()

		err := json.NewDecoder(r.Body).Decode(cfg)
		if nil != err {
			errors.Handler(w, err)
			return
		}

		isValid, err := cfg.Validate()
		if false == isValid && err != nil {
			errors.Handler(w, errors.New(http.StatusBadRequest, err.Error()))
			return
		}

		// Additionally validate plugin configuration
		for _, plg := range cfg.Plugins {
			isValid, err := plugin.ValidateConfig(plg.Name, plg.Config)
			if !isValid || err != nil {
				errors.Handler(w, errors.New(http.StatusBadRequest, err.Error()))
				return
			}
		}

		span := opentracing.FromContext(r.Context(), "datastore.Exists")
		exists, err := c.exists(cfg)
		span.Finish()

		if err != nil || exists {
			errors.Handler(w, err)
			return
		}

		span = opentracing.FromContext(r.Context(), "datastore.Add")
		c.configurationChan <- api.ConfigurationMessage{
			Operation:     api.AddedOperation,
			Configuration: cfg,
		}
		span.Finish()

		w.Header().Add("Location", fmt.Sprintf("/apis/%s", cfg.Name))
		w.WriteHeader(http.StatusCreated)
	}
}

// DeleteBy is the delete handler
func (c *APIHandler) DeleteBy() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		span := opentracing.FromContext(r.Context(), "datastore.Remove")
		defer span.Finish()

		name := router.URLParam(r, "name")
		cfg := c.findByName(name)
		if cfg == nil {
			errors.Handler(w, api.ErrAPIDefinitionNotFound)
			return
		}

		c.configurationChan <- api.ConfigurationMessage{
			Operation:     api.RemovedOperation,
			Configuration: cfg,
		}

		w.WriteHeader(http.StatusNoContent)
	}
}

func (c *APIHandler) exists(cfg *api.Definition) (bool, error) {
	for _, storedCfg := range c.Cfgs.Definitions {
		if storedCfg.Name == cfg.Name {
			return true, api.ErrAPINameExists
		}

		if storedCfg.Proxy.ListenPath == cfg.Proxy.ListenPath {
			return true, api.ErrAPIListenPathExists
		}
	}

	return false, nil
}

func (c *APIHandler) findByName(name string) *api.Definition {
	for _, cfg := range c.Cfgs.Definitions {
		if cfg.Name == name {
			return cfg
		}
	}

	return nil
}

func (c *APIHandler) findByListenPath(listenPath string) *api.Definition {
	for _, cfg := range c.Cfgs.Definitions {
		if cfg.Proxy.ListenPath == listenPath {
			return cfg
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package cmdtest

import (
	"bytes"
	"fmt"
	"io"
	"regexp"
	"sync"
	"time"
)

type Expector struct {
	output         io.Reader
	defaultTimeout time.Duration

	outputError chan error
	listen      chan bool

	offset     int
	buffer     *bytes.Buffer
	fullBuffer *bytes.Buffer

	sync.RWMutex
}

type ExpectationFailed struct {
	Wanted string
	Next   string
	Output string
}

func (e ExpectationFailed) Error() string {
	return fmt.Sprintf("Expected to see '%s', got stuck at: %#v.\n\nFull output:\n\n%s", e.Wanted, e.Next, e.Output)
}

func NewExpector(out io.Reader, defaultTimeout time.Duration) *Expector {
	e := &Expector{
		output:         out,
		defaultTimeout: defaultTimeout,

		outputError: make(chan error),
		listen:      make(chan bool),

		buffer:     new(bytes.Buffer),
		fullBuffer: new(bytes.Buffer),
	}

	go e.monitor()

	return e
}

func (e *Expector) Expect(pattern string) error {
	return e.ExpectWithTimeout(pattern, e.defaultTimeout)
}

func (e *Expector) ExpectWithTimeout(pattern string, timeout time.Duration) error {
	regexp, err := regexp.Compile(pattern)
	if err != nil {
		return err
	}

	cancel := make(chan bool, 1)

	select {
	case <-e.match(regexp, cancel):
		return nil
	case err := <-e.outputError:
		if err == io.EOF {
			return e.matchFailure(pattern)
		} else {
			return err
		}
	case <-time.After(timeout):
		cancel <- true
		return e.matchFailure(pattern)
	}
}

func (e *Expector) matchFailure(pattern string) ExpectationFailed {
	return ExpectationFailed{
		Wanted: pattern,
		Next:   string(e.nextOutput()),
		Output: string(e.fullOutput()),
	}
}

func (e *Expector) match(regexp *regexp.Regexp, cancel chan bool) chan bool {
	matchResult := make(chan bool)

	go func() {
		for {
			found := regexp.FindIndex(e.nextOutput())
			if found != nil {
				e.forwardOutput(found[1])
				matchResult <- true
				break
			}

			select {
			case <-e.listen:
			case <-time.After(100 * time.Millisecond):
			case <-cancel:
				return
			}
		}
	}()

	return matchResult
}

func (e *Expector) monitor() {
	var buf [1024]byte

	for {
		read, err := e.output.Read(buf[:])

		if read > 0 {
			e.addOutput(buf[:read])
		}

		e.notify()

		if err != nil {
			e.outputError <- err
			break
		}
	}
}

func (e *Expector) addOutput(out []byte) {
	e.Lock()
	defer e.Unlock()

	e.buffer.Write(out)
	e.fullBuffer.Write(out)
}

func (e *Expector) forwardOutput(count int) {
	e.Lock()
	defer e.Unlock()

	e.buffer.Next(count)
}

func (e *Expector) nextOutput() []byte {
	e.RLock()
	defer e.RUnlock()

	return e.buffer.Bytes()
}

func (e *Expector) fullOutput() []byte {
	e.RLock()
	defer e.RUnlock()

	return e.fullBuffer.Bytes()
}

func (e *Expector) notify() {
	select {
	case e.listen <- true:
	default:
	}
}
<commit_msg>don't consider EOF an error<commit_after>package cmdtest

import (
	"bytes"
	"fmt"
	"io"
	"regexp"
	"sync"
	"time"
)

type Expector struct {
	output         io.Reader
	defaultTimeout time.Duration

	outputError chan error
	listen      chan bool

	offset     int
	buffer     *bytes.Buffer
	fullBuffer *bytes.Buffer

	sync.RWMutex
}

type ExpectationFailed struct {
	Wanted string
	Next   string
	Output string
}

func (e ExpectationFailed) Error() string {
	return fmt.Sprintf("Expected to see '%s', got stuck at: %#v.\n\nFull output:\n\n%s", e.Wanted, e.Next, e.Output)
}

func NewExpector(out io.Reader, defaultTimeout time.Duration) *Expector {
	e := &Expector{
		output:         out,
		defaultTimeout: defaultTimeout,

		outputError: make(chan error),
		listen:      make(chan bool),

		buffer:     new(bytes.Buffer),
		fullBuffer: new(bytes.Buffer),
	}

	go e.monitor()

	return e
}

func (e *Expector) Expect(pattern string) error {
	return e.ExpectWithTimeout(pattern, e.defaultTimeout)
}

func (e *Expector) ExpectWithTimeout(pattern string, timeout time.Duration) error {
	regexp, err := regexp.Compile(pattern)
	if err != nil {
		return err
	}

	cancel := make(chan bool, 1)

	select {
	case <-e.match(regexp, cancel):
		return nil
	case err := <-e.outputError:
		return err
	case <-time.After(timeout):
		cancel <- true
		return e.matchFailure(pattern)
	}
}

func (e *Expector) matchFailure(pattern string) ExpectationFailed {
	return ExpectationFailed{
		Wanted: pattern,
		Next:   string(e.nextOutput()),
		Output: string(e.fullOutput()),
	}
}

func (e *Expector) match(regexp *regexp.Regexp, cancel chan bool) chan bool {
	matchResult := make(chan bool)

	go func() {
		for {
			found := regexp.FindIndex(e.nextOutput())
			if found != nil {
				e.forwardOutput(found[1])
				matchResult <- true
				break
			}

			select {
			case <-e.listen:
			case <-time.After(100 * time.Millisecond):
			case <-cancel:
				return
			}
		}
	}()

	return matchResult
}

func (e *Expector) monitor() {
	var buf [1024]byte

	for {
		read, err := e.output.Read(buf[:])

		if read > 0 {
			e.addOutput(buf[:read])
		}

		e.notify()

		if err != nil {
			if err != io.EOF {
				e.outputError <- err
			}

			break
		}
	}
}

func (e *Expector) addOutput(out []byte) {
	e.Lock()
	defer e.Unlock()

	e.buffer.Write(out)
	e.fullBuffer.Write(out)
}

func (e *Expector) forwardOutput(count int) {
	e.Lock()
	defer e.Unlock()

	e.buffer.Next(count)
}

func (e *Expector) nextOutput() []byte {
	e.RLock()
	defer e.RUnlock()

	return e.buffer.Bytes()
}

func (e *Expector) fullOutput() []byte {
	e.RLock()
	defer e.RUnlock()

	return e.fullBuffer.Bytes()
}

func (e *Expector) notify() {
	select {
	case e.listen <- true:
	default:
	}
}
<|endoftext|>


<commit_before>package cli_test

import (
	"time"

	. "github.com/jwaldrip/odin/cli"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("CLI Start", func() {

	var cli *CLI
	var cmd Command
	var didRun bool

	BeforeEach(func() {
		didRun = false
		runFn := func(c Command) {
			cmd = c
			didRun = true
		}
		cli = NewCLI("v1.0.0", "sample description", runFn)
		cli.ErrorHandling = PanicOnError
		cli.Mute()
	})

	Describe("flag definitions", func() {
		Describe("BoolFlag", func() {

			It("should set and parse", func() {
				cli.DefineBoolFlag("boolflag", false, "desc")
				cli.Start("cmd", "--boolflag")
				Expect(cmd.Flag("boolflag").Get()).To(Equal(true))
			})

			It("should set and parse from var", func() {
				var val bool
				cli.DefineBoolFlagVar(&val, "boolflag", false, "desc")
				cli.DefineBoolFlagVar(&val, "boolflag2", false, "desc")
				cli.Start("cmd", "--boolflag")
				Expect(cmd.Flag("boolflag2").Get()).To(Equal(true))
			})
		})

		Describe("DurationFlag", func() {

			It("should set and parse", func() {
				cli.DefineDurationFlag("durflag", time.Duration(60)*time.Second, "desc")
				cli.Start("cmd", "--durflag=30s")
				Expect(cmd.Flag("durflag").Get()).To(Equal(time.Duration(30) * time.Second))
			})

			It("should set and parse from var", func() {
				var val time.Duration
				cli.DefineDurationFlagVar(&val, "durflag", time.Duration(60)*time.Second, "desc")
				cli.DefineDurationFlagVar(&val, "durflag2", time.Duration(45)*time.Second, "desc")
				cli.Start("cmd", "--durflag=100s")
				Expect(cmd.Flag("durflag2").Get()).To(Equal(time.Duration(100) * time.Second))
			})
		})

		Describe("Float64Flag", func() {

			It("should set and parse", func() {
				cli.DefineFloat64Flag("float64flag", 10.1, "desc")
				cli.Start("cmd", "--float64flag=9.9")
				Expect(cmd.Flag("float64flag").Get()).To(Equal(9.9))
			})

			It("should set and parse from var", func() {
				var val float64
				cli.DefineFloat64FlagVar(&val, "float64flag", 10.10, "desc")
				cli.DefineFloat64FlagVar(&val, "float64flag2", 9.9, "desc")
				cli.Start("cmd", "--float64flag=8.8")
				Expect(cmd.Flag("float64flag2").Get()).To(Equal(8.8))
			})
		})

		Describe("Int64Flag", func() {

			It("should set and parse", func() {
				cli.DefineInt64Flag("int64flag", 10, "desc")
				cli.Start("cmd", "--int64flag=9")
				Expect(cmd.Flag("int64flag").Get()).To(Equal(int64(9)))
			})

			It("should set and parse from var", func() {
				var val int64
				cli.DefineInt64FlagVar(&val, "int64flag", 10, "desc")
				cli.DefineInt64FlagVar(&val, "int64flag2", 9, "desc")
				cli.Start("cmd", "--int64flag=8")
				Expect(cmd.Flag("int64flag2").Get()).To(Equal(int64(8)))
			})
		})

	})

})
<commit_msg>int tests<commit_after>package cli_test

import (
	"time"

	. "github.com/jwaldrip/odin/cli"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("CLI Start", func() {

	var cli *CLI
	var cmd Command
	var didRun bool

	BeforeEach(func() {
		didRun = false
		runFn := func(c Command) {
			cmd = c
			didRun = true
		}
		cli = NewCLI("v1.0.0", "sample description", runFn)
		cli.ErrorHandling = PanicOnError
		cli.Mute()
	})

	Describe("flag definitions", func() {
		Describe("BoolFlag", func() {

			It("should set and parse", func() {
				cli.DefineBoolFlag("boolflag", false, "desc")
				cli.Start("cmd", "--boolflag")
				Expect(cmd.Flag("boolflag").Get()).To(Equal(true))
			})

			It("should set and parse from var", func() {
				var val bool
				cli.DefineBoolFlagVar(&val, "boolflag", false, "desc")
				cli.DefineBoolFlagVar(&val, "boolflag2", false, "desc")
				cli.Start("cmd", "--boolflag")
				Expect(cmd.Flag("boolflag2").Get()).To(Equal(true))
			})
		})

		Describe("DurationFlag", func() {

			It("should set and parse", func() {
				cli.DefineDurationFlag("durflag", time.Duration(60)*time.Second, "desc")
				cli.Start("cmd", "--durflag=30s")
				Expect(cmd.Flag("durflag").Get()).To(Equal(time.Duration(30) * time.Second))
			})

			It("should set and parse from var", func() {
				var val time.Duration
				cli.DefineDurationFlagVar(&val, "durflag", time.Duration(60)*time.Second, "desc")
				cli.DefineDurationFlagVar(&val, "durflag2", time.Duration(45)*time.Second, "desc")
				cli.Start("cmd", "--durflag=100s")
				Expect(cmd.Flag("durflag2").Get()).To(Equal(time.Duration(100) * time.Second))
			})
		})

		Describe("Float64Flag", func() {

			It("should set and parse", func() {
				cli.DefineFloat64Flag("float64flag", 10.1, "desc")
				cli.Start("cmd", "--float64flag=9.9")
				Expect(cmd.Flag("float64flag").Get()).To(Equal(9.9))
			})

			It("should set and parse from var", func() {
				var val float64
				cli.DefineFloat64FlagVar(&val, "float64flag", 10.10, "desc")
				cli.DefineFloat64FlagVar(&val, "float64flag2", 9.9, "desc")
				cli.Start("cmd", "--float64flag=8.8")
				Expect(cmd.Flag("float64flag2").Get()).To(Equal(8.8))
			})
		})

		Describe("Int64Flag", func() {

			It("should set and parse", func() {
				cli.DefineInt64Flag("int64flag", 10, "desc")
				cli.Start("cmd", "--int64flag=9")
				Expect(cmd.Flag("int64flag").Get()).To(Equal(int64(9)))
			})

			It("should set and parse from var", func() {
				var val int64
				cli.DefineInt64FlagVar(&val, "int64flag", 10, "desc")
				cli.DefineInt64FlagVar(&val, "int64flag2", 9, "desc")
				cli.Start("cmd", "--int64flag=8")
				Expect(cmd.Flag("int64flag2").Get()).To(Equal(int64(8)))
			})
		})

		Describe("IntFlag", func() {

			It("should set and parse", func() {
				cli.DefineIntFlag("intflag", 10, "desc")
				cli.Start("cmd", "--intflag=9")
				Expect(cmd.Flag("intflag").Get()).To(Equal(int(9)))
			})

			It("should set and parse from var", func() {
				var val int
				cli.DefineIntFlagVar(&val, "intflag", 10, "desc")
				cli.DefineIntFlagVar(&val, "intflag2", 9, "desc")
				cli.Start("cmd", "--intflag=8")
				Expect(cmd.Flag("intflag2").Get()).To(Equal(int(8)))
			})
		})

	})

})
<|endoftext|>


<commit_before>/*
   Copyright 2020 Docker Compose CLI authors

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package compose

import (
	"context"
	"os"

	"github.com/spf13/cobra"

	"github.com/docker/compose-cli/api/client"
	"github.com/docker/compose-cli/api/compose"
	"github.com/docker/compose-cli/api/progress"
	"github.com/docker/compose-cli/cli/formatter"
)

type startOptions struct {
	*projectOptions
	Detach bool
}

func startCommand(p *projectOptions) *cobra.Command {
	opts := startOptions{
		projectOptions: p,
	}
	startCmd := &cobra.Command{
		Use:   "start [SERVICE...]",
		Short: "Start services",
		RunE: func(cmd *cobra.Command, args []string) error {
			return runStart(cmd.Context(), opts, args)
		},
	}

	startCmd.Flags().BoolVarP(&opts.Detach, "detach", "d", false, "Detached mode: Run containers in the background")
	return startCmd
}

func runStart(ctx context.Context, opts startOptions, services []string) error {
	c, err := client.NewWithDefaultLocalBackend(ctx)
	if err != nil {
		return err
	}

	var consumer compose.LogConsumer
	if !opts.Detach {
		consumer = formatter.NewLogConsumer(ctx, os.Stdout)
	}
	_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
		project, err := opts.toProject()
		if err != nil {
			return "", err
		}

		err = filter(project, services)
		if err != nil {
			return "", err
		}
		return "", c.ComposeService().Start(ctx, project, consumer)
	})
	return err
}
<commit_msg>Fix start display : do not mix logs with progress<commit_after>/*
   Copyright 2020 Docker Compose CLI authors

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package compose

import (
	"context"
	"os"

	"github.com/spf13/cobra"

	"github.com/docker/compose-cli/api/client"
	"github.com/docker/compose-cli/api/compose"
	"github.com/docker/compose-cli/cli/formatter"
)

type startOptions struct {
	*projectOptions
	Detach bool
}

func startCommand(p *projectOptions) *cobra.Command {
	opts := startOptions{
		projectOptions: p,
	}
	startCmd := &cobra.Command{
		Use:   "start [SERVICE...]",
		Short: "Start services",
		RunE: func(cmd *cobra.Command, args []string) error {
			return runStart(cmd.Context(), opts, args)
		},
	}

	startCmd.Flags().BoolVarP(&opts.Detach, "detach", "d", false, "Detached mode: Run containers in the background")
	return startCmd
}

func runStart(ctx context.Context, opts startOptions, services []string) error {
	c, err := client.NewWithDefaultLocalBackend(ctx)
	if err != nil {
		return err
	}

	var consumer compose.LogConsumer
	if !opts.Detach {
		consumer = formatter.NewLogConsumer(ctx, os.Stdout)
	}

	project, err := opts.toProject()
	if err != nil {
		return err
	}

	err = filter(project, services)
	if err != nil {
		return err
	}

	return c.ComposeService().Start(ctx, project, consumer)
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/tls"
	"fmt"
	"net"
	"net/http"
	"path/filepath"

	"github.com/Sirupsen/logrus"
	"github.com/dkumor/acmewrapper"
	"github.com/mcuadros/go-stable"
	"github.com/urfave/negroni"
)

type ServerCommand struct {
	Host         string `long:"host" description:"host of the server"`
	Server       string `long:"server" default:"github.com" description:"repository git server"`
	Organization string `long:"organazation" description:"repository organization"`
	Repository   string `long:"repository" default:"github.com" description:"repository name"`
	BaseRoute    string `long:"base-route" description:"base gorilla/mux route"`

	Addr         string `long:"addr" default:":443" description:"http server addr"`
	RedirectAddr string `long:"redirect-addr" default:":80" description:"http to https redirect server addr"`
	CertFile     string `long:"tls-cert" description:"TLS certificate file path."`
	KeyFile      string `long:"tls-key" description:"TLS key file path."`
	ACMEFolder   string `long:"acme-folder" description:"folder where all ACME generated certs are stored"`

	LogLevel  string `long:"log-level" default:"info" description:"log level, values: debug, info, warn or panic"`
	LogFormat string `long:"log-format" default:"text" description:"log format, values: text or json"`

	s        *stable.Server
	redirect *http.Server
}

func (c *ServerCommand) Execute(args []string) error {
	if err := c.buildServer(); err != nil {
		return err
	}

	if err := c.buildMiddleware(); err != nil {
		return err
	}

	c.buildRedirectHTTP()
	return c.listen()
}

func (c *ServerCommand) buildServer() error {
	if c.Host == "" {
		return fmt.Errorf("missing host name, please set `--host`")
	}

	if c.BaseRoute == "" {
		c.BaseRoute = stable.DefaultBaseRoute
	}

	c.s = stable.NewServer(c.BaseRoute, c.Host)
	c.s.Addr = c.Addr
	c.s.Default.Server = c.Server
	c.s.Default.Organization = c.Organization
	c.s.Default.Repository = c.Repository

	return nil
}

func (c *ServerCommand) buildMiddleware() error {
	logger, err := c.getLogrusMiddleware()
	if err != nil {
		return err
	}

	n := negroni.New()
	n.Use(negroni.NewRecovery())
	n.Use(logger)
	n.UseHandler(c.s.Handler)

	c.s.Handler = n

	return nil
}

func (c *ServerCommand) getLogrusMiddleware() (negroni.Handler, error) {
	level, err := c.getLogLevel()
	if err != nil {
		return nil, err
	}

	format, err := c.getLogFormat()
	if err != nil {
		return nil, err
	}

	return NewLogger(level, format), nil
}

func (c *ServerCommand) getLogLevel() (level logrus.Level, err error) {
	switch c.LogLevel {
	case "debug":
		level = logrus.DebugLevel
	case "info":
		level = logrus.InfoLevel
	case "warn":
		level = logrus.WarnLevel
	case "panic":
		level = logrus.PanicLevel
	default:
		err = fmt.Errorf("invalid log-level, %q", c.LogLevel)
	}

	return
}

func (c *ServerCommand) getLogFormat() (format logrus.Formatter, err error) {
	switch c.LogFormat {
	case "text":
		format = &logrus.TextFormatter{}
	case "json":
		format = &logrus.JSONFormatter{}
	default:
		err = fmt.Errorf("invalid log-format, %q", c.LogLevel)
	}

	return
}

func (c *ServerCommand) listen() error {
	acme, err := c.getACME()
	if err != nil {
		return err
	}

	c.s.TLSConfig = acme.TLSConfig()

	listener, err := tls.Listen("tcp", c.Addr, c.s.TLSConfig)
	if err != nil {
		return err
	}

	go c.listenRedirectHTTP()
	return c.s.Serve(listener)
}

func (c *ServerCommand) getACME() (*acmewrapper.AcmeWrapper, error) {
	cert := "cert.pem"
	if c.CertFile == "" {
		cert = c.CertFile
	}

	key := "key.pem"
	if c.KeyFile == "" {
		key = c.KeyFile
	}

	return acmewrapper.New(acmewrapper.Config{
		Domains:          []string{c.Host},
		Address:          c.Addr,
		TLSCertFile:      cert,
		TLSKeyFile:       key,
		RegistrationFile: filepath.Join(c.ACMEFolder, "user.reg"),
		PrivateKeyFile:   filepath.Join(c.ACMEFolder, "private.pem"),
		TOSCallback:      acmewrapper.TOSAgree,
	})
}

func (c *ServerCommand) listenRedirectHTTP() {
	if c.redirect == nil {
		return
	}

	c.redirect.ListenAndServe()
}

func (c *ServerCommand) buildRedirectHTTP() {
	if c.RedirectAddr == "" {
		return
	}

	m := http.NewServeMux()
	m.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
		url := req.URL
		url.Scheme = "https"
		url.Host = c.Host

		_, port, _ := net.SplitHostPort(c.Addr)
		if port != "443" {
			url.Host += ":" + port
		}

		http.Redirect(w, req, url.String(), http.StatusMovedPermanently)
	})

	c.redirect = &http.Server{
		Addr:    c.RedirectAddr,
		Handler: m,
	}
}
<commit_msg>cli/stable: acme config fix?<commit_after>package main

import (
	"crypto/tls"
	"fmt"
	"net"
	"net/http"
	"path/filepath"

	"github.com/Sirupsen/logrus"
	"github.com/dkumor/acmewrapper"
	"github.com/mcuadros/go-stable"
	"github.com/urfave/negroni"
)

type ServerCommand struct {
	Host         string `long:"host" description:"host of the server"`
	Server       string `long:"server" default:"github.com" description:"repository git server"`
	Organization string `long:"organazation" description:"repository organization"`
	Repository   string `long:"repository" default:"github.com" description:"repository name"`
	BaseRoute    string `long:"base-route" description:"base gorilla/mux route"`

	Addr         string `long:"addr" default:":443" description:"http server addr"`
	RedirectAddr string `long:"redirect-addr" default:":80" description:"http to https redirect server addr"`
	CertFile     string `long:"tls-cert" description:"TLS certificate file path."`
	KeyFile      string `long:"tls-key" description:"TLS key file path."`
	ACMEFolder   string `long:"acme-folder" description:"folder where all ACME generated certs are stored"`

	LogLevel  string `long:"log-level" default:"info" description:"log level, values: debug, info, warn or panic"`
	LogFormat string `long:"log-format" default:"text" description:"log format, values: text or json"`

	s        *stable.Server
	redirect *http.Server
}

func (c *ServerCommand) Execute(args []string) error {
	if err := c.buildServer(); err != nil {
		return err
	}

	if err := c.buildMiddleware(); err != nil {
		return err
	}

	c.buildRedirectHTTP()
	return c.listen()
}

func (c *ServerCommand) buildServer() error {
	if c.Host == "" {
		return fmt.Errorf("missing host name, please set `--host`")
	}

	if c.BaseRoute == "" {
		c.BaseRoute = stable.DefaultBaseRoute
	}

	c.s = stable.NewServer(c.BaseRoute, c.Host)
	c.s.Addr = c.Addr
	c.s.Default.Server = c.Server
	c.s.Default.Organization = c.Organization
	c.s.Default.Repository = c.Repository

	return nil
}

func (c *ServerCommand) buildMiddleware() error {
	logger, err := c.getLogrusMiddleware()
	if err != nil {
		return err
	}

	n := negroni.New()
	n.Use(negroni.NewRecovery())
	n.Use(logger)
	n.UseHandler(c.s.Handler)

	c.s.Handler = n

	return nil
}

func (c *ServerCommand) getLogrusMiddleware() (negroni.Handler, error) {
	level, err := c.getLogLevel()
	if err != nil {
		return nil, err
	}

	format, err := c.getLogFormat()
	if err != nil {
		return nil, err
	}

	return NewLogger(level, format), nil
}

func (c *ServerCommand) getLogLevel() (level logrus.Level, err error) {
	switch c.LogLevel {
	case "debug":
		level = logrus.DebugLevel
	case "info":
		level = logrus.InfoLevel
	case "warn":
		level = logrus.WarnLevel
	case "panic":
		level = logrus.PanicLevel
	default:
		err = fmt.Errorf("invalid log-level, %q", c.LogLevel)
	}

	return
}

func (c *ServerCommand) getLogFormat() (format logrus.Formatter, err error) {
	switch c.LogFormat {
	case "text":
		format = &logrus.TextFormatter{}
	case "json":
		format = &logrus.JSONFormatter{}
	default:
		err = fmt.Errorf("invalid log-format, %q", c.LogLevel)
	}

	return
}

func (c *ServerCommand) listen() error {
	acme, err := c.getACME()
	if err != nil {
		return err
	}

	c.s.TLSConfig = acme.TLSConfig()

	listener, err := tls.Listen("tcp", c.Addr, c.s.TLSConfig)
	if err != nil {
		return err
	}

	go c.listenRedirectHTTP()
	return c.s.Serve(listener)
}

func (c *ServerCommand) getACME() (*acmewrapper.AcmeWrapper, error) {
	cert := filepath.Join(c.ACMEFolder, "cert.pem")
	if c.CertFile == "" {
		cert = c.CertFile
	}

	key := filepath.Join(c.ACMEFolder, "key.pem")
	if c.KeyFile == "" {
		key = c.KeyFile
	}

	return acmewrapper.New(acmewrapper.Config{
		Domains:          []string{c.Host},
		Address:          c.Addr,
		TLSCertFile:      cert,
		TLSKeyFile:       key,
		RegistrationFile: filepath.Join(c.ACMEFolder, "user.reg"),
		PrivateKeyFile:   filepath.Join(c.ACMEFolder, "private.pem"),
		TOSCallback:      acmewrapper.TOSAgree,
	})
}

func (c *ServerCommand) listenRedirectHTTP() {
	if c.redirect == nil {
		return
	}

	c.redirect.ListenAndServe()
}

func (c *ServerCommand) buildRedirectHTTP() {
	if c.RedirectAddr == "" {
		return
	}

	m := http.NewServeMux()
	m.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
		url := req.URL
		url.Scheme = "https"
		url.Host = c.Host

		_, port, _ := net.SplitHostPort(c.Addr)
		if port != "443" {
			url.Host += ":" + port
		}

		http.Redirect(w, req, url.String(), http.StatusMovedPermanently)
	})

	c.redirect = &http.Server{
		Addr:    c.RedirectAddr,
		Handler: m,
	}
}
<|endoftext|>


<commit_before>package prj

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"regexp"
	"runtime"
	"strings"
)

var project *Project

type Project struct {
	rootFolder string
}

func GetProject() (*Project, error) {
	if project == nil {
		project = &Project{}
		gdir, gerr := Git("rev-parse --git-dir")
		gdir = strings.TrimSpace(gdir)
		// fmt.Printf("ko '%s' '%s'", gdir, gerr)
		if gerr != nil {
			return nil, gerr
		}
		if gdir != ".git" {
			project.rootFolder = gdir[:len(gdir)-5]
		} else {
			// fmt.Printf("ok")
			project.rootFolder = wd
		}
	}
	// fmt.Printf("prf '%s'", project.rootFolder)
	return project, nil
}

func (p *Project) RootFolder() string {
	return p.rootFolder
}

// Inspired by https://github.com/ghthor/journal/blob/0bd4968a4f9841befdd0dde96b2096e6c930e74c/git/git.go

var gitPath string
var goPath string
var wd string

func init() {
	gitPath = getPathForExe("git")
	goPath = getPathForExe("go")
	var err error
	wd, err = os.Getwd()
	if err != nil {
		log.Fatal("Working directory not accessible")
	}
}

func getPathForExe(exe string) string {
	var err error
	var path = ""
	if path, err = exec.LookPath(exe); err != nil {
		aliases := ""
		if runtime.GOOS == "windows" {
			aliases, err = execcmd("doskey", "/macros")
		} else {
			aliases, err = execcmd("alias", "")
		}
		r := regexp.MustCompile(`(?m)^` + exe + `=(.*)\s+[\$%@\*].*$`)
		sm := r.FindAllStringSubmatch(aliases, 1)
		if len(sm) != 1 || len(sm[0]) != 2 {
			log.Fatalf("Unable to find '%s' path in aliases '%s'", exe)
		}
		return sm[0][1]
	}
	if runtime.GOOS == "windows" {
		if strings.HasSuffix(path, ".bat") {
			bat, err := ioutil.ReadFile(path)
			if err != nil {
				log.Fatalf("Unable to read '%s' for '%s'", path, exe)
			}
			bats := string(bat)
			r := regexp.MustCompile(`(?m)^\s*?(.*)\s+[\$%@\*].*$`)
			sm := r.FindAllStringSubmatch(bats, 1)
			if len(sm) != 1 || len(sm[0]) != 2 {
				log.Fatalf("Unable to find '%s' path in file '%s'", exe, path)
			}
			return sm[0][1]
		}
	}
	if path == "" {
		log.Fatalf("Unable to get path for '%s'", exe)
	}
	return path
}

// Construct an *exec.Cmd for `git {args}` with a workingDirectory
func Git(cmd string) (string, error) {
	return execcmd(gitPath, cmd)
}
func Golang(cmd string) (string, error) {
	os.Setenv("GOPATH", project.rootFolder+`/deps`)
	os.Setenv("GOBIN", project.rootFolder+`/bin`)
	return execcmd(goPath, cmd)
}

func execcmd(exe, cmd string) (string, error) {
	args := strings.Split(cmd, " ")
	c := exec.Command(exe, args...)
	c.Dir = project.rootFolder
	var bout bytes.Buffer
	c.Stdout = &bout
	var berr bytes.Buffer
	c.Stderr = &berr
	err := c.Run()
	if err != nil {
		return bout.String(), fmt.Errorf("Unable to run '%s %s' in '%s': err '%s'\n'%s'", exe, cmd, wd, err.Error(), berr.String())
	} else if berr.String() != "" {
		return bout.String(), fmt.Errorf("Warning on run '%s %s' in '%s': '%s'", exe, cmd, wd, berr.String())
	}
	return bout.String(), nil
}
<commit_msg>projects.go: add global GOPATH to project<commit_after>package prj

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"regexp"
	"runtime"
	"strings"
)

var project *Project

type Project struct {
	rootFolder string
	// Global  GOPATH
	ggopath string
}

func GetProject() (*Project, error) {
	if project == nil {
		project = &Project{}
		gdir, gerr := Git("rev-parse --git-dir")
		gdir = strings.TrimSpace(gdir)
		// fmt.Printf("ko '%s' '%s'", gdir, gerr)
		if gerr != nil {
			return nil, gerr
		}
		if gdir != ".git" {
			project.rootFolder = gdir[:len(gdir)-5]
		} else {
			// fmt.Printf("ok")
			project.rootFolder = wd
		}
		project.ggopath = os.Getenv("GOPATH")
	}
	// fmt.Printf("prf '%s'", project.rootFolder)
	// fmt.Printf("prf '%s'", project.ggopath)
	return project, nil
}

func (p *Project) RootFolder() string {
	return p.rootFolder
}

// Inspired by https://github.com/ghthor/journal/blob/0bd4968a4f9841befdd0dde96b2096e6c930e74c/git/git.go

var gitPath string
var goPath string
var wd string

func init() {
	gitPath = getPathForExe("git")
	goPath = getPathForExe("go")
	var err error
	wd, err = os.Getwd()
	if err != nil {
		log.Fatal("Working directory not accessible")
	}
}

func getPathForExe(exe string) string {
	var err error
	var path = ""
	if path, err = exec.LookPath(exe); err != nil {
		aliases := ""
		if runtime.GOOS == "windows" {
			aliases, err = execcmd("doskey", "/macros")
		} else {
			aliases, err = execcmd("alias", "")
		}
		r := regexp.MustCompile(`(?m)^` + exe + `=(.*)\s+[\$%@\*].*$`)
		sm := r.FindAllStringSubmatch(aliases, 1)
		if len(sm) != 1 || len(sm[0]) != 2 {
			log.Fatalf("Unable to find '%s' path in aliases '%s'", exe)
		}
		return sm[0][1]
	}
	if runtime.GOOS == "windows" {
		if strings.HasSuffix(path, ".bat") {
			bat, err := ioutil.ReadFile(path)
			if err != nil {
				log.Fatalf("Unable to read '%s' for '%s'", path, exe)
			}
			bats := string(bat)
			r := regexp.MustCompile(`(?m)^\s*?(.*)\s+[\$%@\*].*$`)
			sm := r.FindAllStringSubmatch(bats, 1)
			if len(sm) != 1 || len(sm[0]) != 2 {
				log.Fatalf("Unable to find '%s' path in file '%s'", exe, path)
			}
			return sm[0][1]
		}
	}
	if path == "" {
		log.Fatalf("Unable to get path for '%s'", exe)
	}
	return path
}

// Construct an *exec.Cmd for `git {args}` with a workingDirectory
func Git(cmd string) (string, error) {
	return execcmd(gitPath, cmd)
}
func Golang(cmd string) (string, error) {
	os.Setenv("GOPATH", project.rootFolder+`/deps`)
	os.Setenv("GOBIN", project.rootFolder+`/bin`)
	return execcmd(goPath, cmd)
}

func execcmd(exe, cmd string) (string, error) {
	args := strings.Split(cmd, " ")
	c := exec.Command(exe, args...)
	c.Dir = project.rootFolder
	var bout bytes.Buffer
	c.Stdout = &bout
	var berr bytes.Buffer
	c.Stderr = &berr
	err := c.Run()
	if err != nil {
		return bout.String(), fmt.Errorf("Unable to run '%s %s' in '%s': err '%s'\n'%s'", exe, cmd, wd, err.Error(), berr.String())
	} else if berr.String() != "" {
		return bout.String(), fmt.Errorf("Warning on run '%s %s' in '%s': '%s'", exe, cmd, wd, berr.String())
	}
	return bout.String(), nil
}
<|endoftext|>


<commit_before>package scanner

import (
	"bytes"
	"fmt"
	"io"
	"syscall"
)

func compare(left, right *FileSystem, logWriter io.Writer) bool {
	if len(left.RegularInodeTable) != len(right.RegularInodeTable) {
		if logWriter != nil {
			fmt.Fprintf(logWriter,
				"left vs. right: %d vs. %d regular file inodes\n",
				len(left.RegularInodeTable), len(right.RegularInodeTable))
		}
		return false
	}
	if len(left.InodeTable) != len(right.InodeTable) {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "left vs. right: %d vs. %d inodes\n",
				len(left.InodeTable), len(right.InodeTable))
		}
		return false
	}
	if len(left.DirectoryInodeList) != len(right.DirectoryInodeList) {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "left vs. right: %d vs. %d inodes\n",
				len(left.DirectoryInodeList), len(right.DirectoryInodeList))
		}
		return false
	}
	if !compareDirectories(&left.Directory, &right.Directory, logWriter) {
		return false
	}
	if len(left.ObjectCache) != len(right.ObjectCache) {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "left vs. right: %d vs. %d objects\n",
				len(left.ObjectCache), len(right.ObjectCache))
		}
		return false
	}
	return compareObjects(left.ObjectCache, right.ObjectCache, logWriter)
}

func compareDirectories(left, right *Directory, logWriter io.Writer) bool {
	if left.Name != right.Name {
		fmt.Fprintf(logWriter, "dirname: left vs. right: %s vs. %s\n",
			left.Name, right.Name)
		return false
	}
	if left.Mode != right.Mode {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "Mode: left vs. right: %x vs. %x\n",
				left.Mode, right.Mode)
		}
		return false
	}
	if left.Uid != right.Uid {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "Uid: left vs. right: %d vs. %d\n",
				left.Uid, right.Uid)
		}
		return false
	}
	if left.Gid != right.Gid {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "Gid: left vs. right: %d vs. %d\n",
				left.Gid, right.Gid)
		}
		return false
	}
	if len(left.RegularFileList) != len(right.RegularFileList) {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "left vs. right: %d vs. %d regular files\n",
				len(left.RegularFileList), len(right.RegularFileList))
		}
		return false
	}
	if len(left.FileList) != len(right.FileList) {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "left vs. right: %d vs. %d files\n",
				len(left.FileList), len(right.FileList))
		}
		return false
	}
	if len(left.DirectoryList) != len(right.DirectoryList) {
		fmt.Fprintf(logWriter, "left vs. right: %d vs. %d subdirs\n",
			len(left.DirectoryList), len(right.DirectoryList))
		return false
	}
	for index, leftEntry := range left.RegularFileList {
		if !compareRegularFiles(leftEntry, right.RegularFileList[index],
			logWriter) {
			return false
		}
	}
	for index, leftEntry := range left.FileList {
		if !compareFiles(leftEntry, right.FileList[index], logWriter) {
			return false
		}
	}
	for index, leftEntry := range left.DirectoryList {
		if !compareDirectories(leftEntry, right.DirectoryList[index],
			logWriter) {
			return false
		}
	}
	return true
}

func compareRegularFiles(left, right *RegularFile, logWriter io.Writer) bool {
	if left.Name != right.Name {
		fmt.Fprintf(logWriter, "filename: left vs. right: %s vs. %s\n",
			left.Name, right.Name)
		return false
	}
	if !compareRegularInodes(left.inode, right.inode, logWriter) {
		return false
	}
	return true
}

func compareRegularInodes(left, right *RegularInode, logWriter io.Writer) bool {
	if left.Mode != right.Mode {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "Mode: left vs. right: %x vs. %x\n",
				left.Mode, right.Mode)
		}
		return false
	}
	if left.Uid != right.Uid {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "Uid: left vs. right: %d vs. %d\n",
				left.Uid, right.Uid)
		}
		return false
	}
	if left.Gid != right.Gid {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "Gid: left vs. right: %d vs. %d\n",
				left.Gid, right.Gid)
		}
		return false
	}
	if left.Size != right.Size {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "Size: left vs. right: %d vs. %d\n",
				left.Size, right.Size)
		}
		return false
	}
	if left.Mode&syscall.S_IFMT != syscall.S_IFLNK {
		if left.Mtime != right.Mtime {
			if logWriter != nil {
				fmt.Fprintf(logWriter, "Mtime: left vs. right: %d vs. %d\n",
					left.Mtime, right.Mtime)
			}
			return false
		}
	}
	if bytes.Compare(left.Hash[:], right.Hash[:]) != 0 {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "hash: left vs. right: %x vs. %x\n",
				left.Hash, right.Hash)
		}
		return false
	}
	return true
}

func compareFiles(left, right *File, logWriter io.Writer) bool {
	if left.Name != right.Name {
		fmt.Fprintf(logWriter, "filename: left vs. right: %s vs. %s\n",
			left.Name, right.Name)
		return false
	}
	if !compareInodes(left.inode, right.inode, logWriter) {
		return false
	}
	return true
}

func compareInodes(left, right *Inode, logWriter io.Writer) bool {
	if left.Mode != right.Mode {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "Mode: left vs. right: %x vs. %x\n",
				left.Mode, right.Mode)
		}
		return false
	}
	if left.Uid != right.Uid {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "Uid: left vs. right: %d vs. %d\n",
				left.Uid, right.Uid)
		}
		return false
	}
	if left.Gid != right.Gid {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "Gid: left vs. right: %d vs. %d\n",
				left.Gid, right.Gid)
		}
		return false
	}
	if left.Size != right.Size {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "Size: left vs. right: %d vs. %d\n",
				left.Size, right.Size)
		}
		return false
	}
	if left.Mode&syscall.S_IFMT != syscall.S_IFLNK {
		if left.Mtime != right.Mtime {
			if logWriter != nil {
				fmt.Fprintf(logWriter, "Mtime: left vs. right: %d vs. %d\n",
					left.Mtime, right.Mtime)
			}
			return false
		}
	}
	if left.Mode&syscall.S_IFMT == syscall.S_IFBLK ||
		left.Mode&syscall.S_IFMT == syscall.S_IFCHR {
		if left.Rdev != right.Rdev {
			if logWriter != nil {
				fmt.Fprintf(logWriter, "Rdev: left vs. right: %x vs. %x\n",
					left.Rdev, right.Rdev)
			}
			return false
		}
	}
	if left.Mode&syscall.S_IFMT == syscall.S_IFLNK {
		if left.Symlink != right.Symlink {
			if logWriter != nil {
				fmt.Fprintf(logWriter, "symlink: left vs. right: %s vs. %s\n",
					left.Symlink, right.Symlink)
			}
			return false
		}
	}
	return true
}

func compareObjects(left [][]byte, right [][]byte, logWriter io.Writer) bool {
	for index, leftHash := range left {
		if bytes.Compare(leftHash, right[index]) != 0 {
			if logWriter != nil {
				fmt.Fprintf(logWriter, "hash: left vs. right: %x vs. %x\n",
					leftHash, right[index])
			}
			return false
		}
	}
	return true
}
<commit_msg>Fix log message when comparing scanner.Directory.DirectoryInodeList lengths.<commit_after>package scanner

import (
	"bytes"
	"fmt"
	"io"
	"syscall"
)

func compare(left, right *FileSystem, logWriter io.Writer) bool {
	if len(left.RegularInodeTable) != len(right.RegularInodeTable) {
		if logWriter != nil {
			fmt.Fprintf(logWriter,
				"left vs. right: %d vs. %d regular file inodes\n",
				len(left.RegularInodeTable), len(right.RegularInodeTable))
		}
		return false
	}
	if len(left.InodeTable) != len(right.InodeTable) {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "left vs. right: %d vs. %d inodes\n",
				len(left.InodeTable), len(right.InodeTable))
		}
		return false
	}
	if len(left.DirectoryInodeList) != len(right.DirectoryInodeList) {
		if logWriter != nil {
			fmt.Fprintf(logWriter,
				"left vs. right: %d vs. %d directory inodes\n",
				len(left.DirectoryInodeList), len(right.DirectoryInodeList))
		}
		return false
	}
	if !compareDirectories(&left.Directory, &right.Directory, logWriter) {
		return false
	}
	if len(left.ObjectCache) != len(right.ObjectCache) {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "left vs. right: %d vs. %d objects\n",
				len(left.ObjectCache), len(right.ObjectCache))
		}
		return false
	}
	return compareObjects(left.ObjectCache, right.ObjectCache, logWriter)
}

func compareDirectories(left, right *Directory, logWriter io.Writer) bool {
	if left.Name != right.Name {
		fmt.Fprintf(logWriter, "dirname: left vs. right: %s vs. %s\n",
			left.Name, right.Name)
		return false
	}
	if left.Mode != right.Mode {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "Mode: left vs. right: %x vs. %x\n",
				left.Mode, right.Mode)
		}
		return false
	}
	if left.Uid != right.Uid {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "Uid: left vs. right: %d vs. %d\n",
				left.Uid, right.Uid)
		}
		return false
	}
	if left.Gid != right.Gid {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "Gid: left vs. right: %d vs. %d\n",
				left.Gid, right.Gid)
		}
		return false
	}
	if len(left.RegularFileList) != len(right.RegularFileList) {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "left vs. right: %d vs. %d regular files\n",
				len(left.RegularFileList), len(right.RegularFileList))
		}
		return false
	}
	if len(left.FileList) != len(right.FileList) {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "left vs. right: %d vs. %d files\n",
				len(left.FileList), len(right.FileList))
		}
		return false
	}
	if len(left.DirectoryList) != len(right.DirectoryList) {
		fmt.Fprintf(logWriter, "left vs. right: %d vs. %d subdirs\n",
			len(left.DirectoryList), len(right.DirectoryList))
		return false
	}
	for index, leftEntry := range left.RegularFileList {
		if !compareRegularFiles(leftEntry, right.RegularFileList[index],
			logWriter) {
			return false
		}
	}
	for index, leftEntry := range left.FileList {
		if !compareFiles(leftEntry, right.FileList[index], logWriter) {
			return false
		}
	}
	for index, leftEntry := range left.DirectoryList {
		if !compareDirectories(leftEntry, right.DirectoryList[index],
			logWriter) {
			return false
		}
	}
	return true
}

func compareRegularFiles(left, right *RegularFile, logWriter io.Writer) bool {
	if left.Name != right.Name {
		fmt.Fprintf(logWriter, "filename: left vs. right: %s vs. %s\n",
			left.Name, right.Name)
		return false
	}
	if !compareRegularInodes(left.inode, right.inode, logWriter) {
		return false
	}
	return true
}

func compareRegularInodes(left, right *RegularInode, logWriter io.Writer) bool {
	if left.Mode != right.Mode {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "Mode: left vs. right: %x vs. %x\n",
				left.Mode, right.Mode)
		}
		return false
	}
	if left.Uid != right.Uid {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "Uid: left vs. right: %d vs. %d\n",
				left.Uid, right.Uid)
		}
		return false
	}
	if left.Gid != right.Gid {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "Gid: left vs. right: %d vs. %d\n",
				left.Gid, right.Gid)
		}
		return false
	}
	if left.Size != right.Size {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "Size: left vs. right: %d vs. %d\n",
				left.Size, right.Size)
		}
		return false
	}
	if left.Mode&syscall.S_IFMT != syscall.S_IFLNK {
		if left.Mtime != right.Mtime {
			if logWriter != nil {
				fmt.Fprintf(logWriter, "Mtime: left vs. right: %d vs. %d\n",
					left.Mtime, right.Mtime)
			}
			return false
		}
	}
	if bytes.Compare(left.Hash[:], right.Hash[:]) != 0 {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "hash: left vs. right: %x vs. %x\n",
				left.Hash, right.Hash)
		}
		return false
	}
	return true
}

func compareFiles(left, right *File, logWriter io.Writer) bool {
	if left.Name != right.Name {
		fmt.Fprintf(logWriter, "filename: left vs. right: %s vs. %s\n",
			left.Name, right.Name)
		return false
	}
	if !compareInodes(left.inode, right.inode, logWriter) {
		return false
	}
	return true
}

func compareInodes(left, right *Inode, logWriter io.Writer) bool {
	if left.Mode != right.Mode {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "Mode: left vs. right: %x vs. %x\n",
				left.Mode, right.Mode)
		}
		return false
	}
	if left.Uid != right.Uid {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "Uid: left vs. right: %d vs. %d\n",
				left.Uid, right.Uid)
		}
		return false
	}
	if left.Gid != right.Gid {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "Gid: left vs. right: %d vs. %d\n",
				left.Gid, right.Gid)
		}
		return false
	}
	if left.Size != right.Size {
		if logWriter != nil {
			fmt.Fprintf(logWriter, "Size: left vs. right: %d vs. %d\n",
				left.Size, right.Size)
		}
		return false
	}
	if left.Mode&syscall.S_IFMT != syscall.S_IFLNK {
		if left.Mtime != right.Mtime {
			if logWriter != nil {
				fmt.Fprintf(logWriter, "Mtime: left vs. right: %d vs. %d\n",
					left.Mtime, right.Mtime)
			}
			return false
		}
	}
	if left.Mode&syscall.S_IFMT == syscall.S_IFBLK ||
		left.Mode&syscall.S_IFMT == syscall.S_IFCHR {
		if left.Rdev != right.Rdev {
			if logWriter != nil {
				fmt.Fprintf(logWriter, "Rdev: left vs. right: %x vs. %x\n",
					left.Rdev, right.Rdev)
			}
			return false
		}
	}
	if left.Mode&syscall.S_IFMT == syscall.S_IFLNK {
		if left.Symlink != right.Symlink {
			if logWriter != nil {
				fmt.Fprintf(logWriter, "symlink: left vs. right: %s vs. %s\n",
					left.Symlink, right.Symlink)
			}
			return false
		}
	}
	return true
}

func compareObjects(left [][]byte, right [][]byte, logWriter io.Writer) bool {
	for index, leftHash := range left {
		if bytes.Compare(leftHash, right[index]) != 0 {
			if logWriter != nil {
				fmt.Fprintf(logWriter, "hash: left vs. right: %x vs. %x\n",
					leftHash, right[index])
			}
			return false
		}
	}
	return true
}
<|endoftext|>