<commit_before>package service

import (
	"context"
	"fmt"
	"strings"
	"sync"
	"time"

	"github.com/micro/go-micro/v2/auth"
	pb "github.com/micro/go-micro/v2/auth/service/proto"
	"github.com/micro/go-micro/v2/auth/token"
	"github.com/micro/go-micro/v2/auth/token/jwt"
	"github.com/micro/go-micro/v2/client"
	log "github.com/micro/go-micro/v2/logger"
)

// NewAuth returns a new instance of the Auth service
func NewAuth(opts ...auth.Option) auth.Auth {
	svc := new(svc)
	svc.Init(opts...)
	return svc
}

// svc is the service implementation of the Auth interface
type svc struct {
	options auth.Options
	auth    pb.AuthService
	jwt     token.Provider
	rules   []*pb.Rule

	sync.Mutex
}

func (s *svc) String() string {
	return "service"
}

func (s *svc) Init(opts ...auth.Option) {
	for _, o := range opts {
		o(&s.options)
	}

	dc := client.DefaultClient
	s.auth = pb.NewAuthService("go.micro.auth", dc)

	// if we have a JWT public key passed as an option,
	// we can decode tokens with the type "JWT" locally
	// and not have to make an RPC call
	if key := s.options.PublicKey; len(key) > 0 {
		s.jwt = jwt.NewTokenProvider(token.WithPublicKey(key))
	}

	// load rules periodically from the auth service
	timer := time.NewTicker(time.Second * 30)
	go func() {
		for {
			s.loadRules()
			<-timer.C
		}
	}()
}

func (s *svc) Options() auth.Options {
	return s.options
}

// Generate a new account
func (s *svc) Generate(id string, opts ...auth.GenerateOption) (*auth.Account, error) {
	options := auth.NewGenerateOptions(opts...)

	rsp, err := s.auth.Generate(context.TODO(), &pb.GenerateRequest{
		Id:           id,
		Roles:        options.Roles,
		Metadata:     options.Metadata,
		SecretExpiry: int64(options.SecretExpiry.Nanoseconds()),
	})
	if err != nil {
		return nil, err
	}

	return serializeAccount(rsp.Account), nil
}

// Grant access to a resource
func (s *svc) Grant(role string, res *auth.Resource) error {
	_, err := s.auth.Grant(context.TODO(), &pb.GrantRequest{
		Role: role,
		Resource: &pb.Resource{
			Type:     res.Type,
			Name:     res.Name,
			Endpoint: res.Endpoint,
		},
	})
	return err
}

// Revoke access to a resource
func (s *svc) Revoke(role string, res *auth.Resource) error {
	_, err := s.auth.Revoke(context.TODO(), &pb.RevokeRequest{
		Role: role,
		Resource: &pb.Resource{
			Type:     res.Type,
			Name:     res.Name,
			Endpoint: res.Endpoint,
		},
	})
	return err
}

// Verify an account has access to a resource
func (s *svc) Verify(acc *auth.Account, res *auth.Resource) error {
	queries := [][]string{
		{res.Type, "*"},                         // check for wildcard resource type, e.g. service.*
		{res.Type, res.Name, "*"},               // check for wildcard name, e.g. service.foo*
		{res.Type, res.Name, res.Endpoint, "*"}, // check for wildcard endpoints, e.g. service.foo.ListFoo:*
		{res.Type, res.Name, res.Endpoint},      // check for specific role, e.g. service.foo.ListFoo:admin
	}

	// endpoint is a url which can have wildcard excludes, e.g.
	// "/foo/*" will allow "/foo/bar"
	if comps := strings.Split(res.Endpoint, "/"); len(comps) > 1 {
		for i := 1; i < len(comps); i++ {
			wildcard := fmt.Sprintf("%v/*", strings.Join(comps[0:i], "/"))
			queries = append(queries, []string{res.Type, res.Name, wildcard})
		}
	}

	for _, q := range queries {
		for _, rule := range s.listRules(q...) {
			if isValidRule(rule, acc, res) {
				return nil
			}
		}
	}

	return auth.ErrForbidden
}

// Inspect a token
func (s *svc) Inspect(token string) (*auth.Account, error) {
	// try to decode JWT locally and fall back to srv if an error
	// occurs, TODO: find a better way of determining if the token
	// is a JWT, possibly update the interface to take an auth.Token
	// and not just the string
	if len(strings.Split(token, ".")) == 3 && s.jwt != nil {
		if tok, err := s.jwt.Inspect(token); err == nil {
			return &auth.Account{
				ID:       tok.Subject,
				Roles:    tok.Roles,
				Metadata: tok.Metadata,
			}, nil
		}
	}

	rsp, err := s.auth.Inspect(context.TODO(), &pb.InspectRequest{
		Token: token,
	})
	if err != nil {
		return nil, err
	}

	return serializeAccount(rsp.Account), nil
}

// Refresh an account using a secret
func (s *svc) Refresh(secret string, opts ...auth.RefreshOption) (*auth.Token, error) {
	options := auth.NewRefreshOptions(opts...)

	rsp, err := s.auth.Refresh(context.Background(), &pb.RefreshRequest{
		Secret:      secret,
		TokenExpiry: int64(options.TokenExpiry.Seconds()),
	})
	if err != nil {
		return nil, err
	}

	return serializeToken(rsp.Token), nil
}

var ruleJoinKey = ":"

// isValidRule returns a bool, indicating if a rule permits access to a
// resource for a given account
func isValidRule(rule *pb.Rule, acc *auth.Account, res *auth.Resource) bool {
	if rule.Role == "*" {
		return true
	}

	for _, role := range acc.Roles {
		if rule.Role == role {
			return true
		}

		// allow user.anything if role is user.*
		if strings.HasSuffix(rule.Role, ".*") && strings.HasPrefix(rule.Role, role+".") {
			return true
		}
	}

	return false
}

// listRules gets all the rules from the store which have an id
// prefix matching the filters
func (s *svc) listRules(filters ...string) []*pb.Rule {
	s.Lock()
	defer s.Unlock()

	prefix := strings.Join(filters, ruleJoinKey)

	var rules []*pb.Rule
	for _, r := range s.rules {
		if strings.HasPrefix(r.Id, prefix) {
			rules = append(rules, r)
		}
	}

	return rules
}

// loadRules retrieves the rules from the auth service
func (s *svc) loadRules() {
	rsp, err := s.auth.ListRules(context.TODO(), &pb.ListRulesRequest{}, client.WithRetries(3))
	s.Lock()
	defer s.Unlock()

	if err != nil {
		log.Errorf("Error listing rules: %v", err)
		s.rules = []*pb.Rule{}
		return
	}

	s.rules = rsp.Rules
}

func serializeToken(t *pb.Token) *auth.Token {
	return &auth.Token{
		Token:    t.Token,
		Type:     t.Type,
		Created:  time.Unix(t.Created, 0),
		Expiry:   time.Unix(t.Expiry, 0),
		Subject:  t.Subject,
		Roles:    t.Roles,
		Metadata: t.Metadata,
	}
}

func serializeAccount(a *pb.Account) *auth.Account {
	var secret *auth.Token
	if a.Secret != nil {
		secret = serializeToken(a.Secret)
	}

	return &auth.Account{
		ID:       a.Id,
		Roles:    a.Roles,
		Metadata: a.Metadata,
		Secret:   secret,
	}
}
<commit_msg>Update auth to pass seconds and not nanoseconds (#1409)<commit_after>package service

import (
	"context"
	"fmt"
	"strings"
	"sync"
	"time"

	"github.com/micro/go-micro/v2/auth"
	pb "github.com/micro/go-micro/v2/auth/service/proto"
	"github.com/micro/go-micro/v2/auth/token"
	"github.com/micro/go-micro/v2/auth/token/jwt"
	"github.com/micro/go-micro/v2/client"
	log "github.com/micro/go-micro/v2/logger"
)

// NewAuth returns a new instance of the Auth service
func NewAuth(opts ...auth.Option) auth.Auth {
	svc := new(svc)
	svc.Init(opts...)
	return svc
}

// svc is the service implementation of the Auth interface
type svc struct {
	options auth.Options
	auth    pb.AuthService
	jwt     token.Provider
	rules   []*pb.Rule

	sync.Mutex
}

func (s *svc) String() string {
	return "service"
}

func (s *svc) Init(opts ...auth.Option) {
	for _, o := range opts {
		o(&s.options)
	}

	dc := client.DefaultClient
	s.auth = pb.NewAuthService("go.micro.auth", dc)

	// if we have a JWT public key passed as an option,
	// we can decode tokens with the type "JWT" locally
	// and not have to make an RPC call
	if key := s.options.PublicKey; len(key) > 0 {
		s.jwt = jwt.NewTokenProvider(token.WithPublicKey(key))
	}

	// load rules periodically from the auth service
	timer := time.NewTicker(time.Second * 30)
	go func() {
		for {
			s.loadRules()
			<-timer.C
		}
	}()
}

func (s *svc) Options() auth.Options {
	return s.options
}

// Generate a new account
func (s *svc) Generate(id string, opts ...auth.GenerateOption) (*auth.Account, error) {
	options := auth.NewGenerateOptions(opts...)

	rsp, err := s.auth.Generate(context.TODO(), &pb.GenerateRequest{
		Id:           id,
		Roles:        options.Roles,
		Metadata:     options.Metadata,
		SecretExpiry: int64(options.SecretExpiry.Seconds()),
	})
	if err != nil {
		return nil, err
	}

	return serializeAccount(rsp.Account), nil
}

// Grant access to a resource
func (s *svc) Grant(role string, res *auth.Resource) error {
	_, err := s.auth.Grant(context.TODO(), &pb.GrantRequest{
		Role: role,
		Resource: &pb.Resource{
			Type:     res.Type,
			Name:     res.Name,
			Endpoint: res.Endpoint,
		},
	})
	return err
}

// Revoke access to a resource
func (s *svc) Revoke(role string, res *auth.Resource) error {
	_, err := s.auth.Revoke(context.TODO(), &pb.RevokeRequest{
		Role: role,
		Resource: &pb.Resource{
			Type:     res.Type,
			Name:     res.Name,
			Endpoint: res.Endpoint,
		},
	})
	return err
}

// Verify an account has access to a resource
func (s *svc) Verify(acc *auth.Account, res *auth.Resource) error {
	queries := [][]string{
		{res.Type, "*"},                         // check for wildcard resource type, e.g. service.*
		{res.Type, res.Name, "*"},               // check for wildcard name, e.g. service.foo*
		{res.Type, res.Name, res.Endpoint, "*"}, // check for wildcard endpoints, e.g. service.foo.ListFoo:*
		{res.Type, res.Name, res.Endpoint},      // check for specific role, e.g. service.foo.ListFoo:admin
	}

	// endpoint is a url which can have wildcard excludes, e.g.
	// "/foo/*" will allow "/foo/bar"
	if comps := strings.Split(res.Endpoint, "/"); len(comps) > 1 {
		for i := 1; i < len(comps); i++ {
			wildcard := fmt.Sprintf("%v/*", strings.Join(comps[0:i], "/"))
			queries = append(queries, []string{res.Type, res.Name, wildcard})
		}
	}

	for _, q := range queries {
		for _, rule := range s.listRules(q...) {
			if isValidRule(rule, acc, res) {
				return nil
			}
		}
	}

	return auth.ErrForbidden
}

// Inspect a token
func (s *svc) Inspect(token string) (*auth.Account, error) {
	// try to decode JWT locally and fall back to srv if an error
	// occurs, TODO: find a better way of determining if the token
	// is a JWT, possibly update the interface to take an auth.Token
	// and not just the string
	if len(strings.Split(token, ".")) == 3 && s.jwt != nil {
		if tok, err := s.jwt.Inspect(token); err == nil {
			return &auth.Account{
				ID:       tok.Subject,
				Roles:    tok.Roles,
				Metadata: tok.Metadata,
			}, nil
		}
	}

	rsp, err := s.auth.Inspect(context.TODO(), &pb.InspectRequest{
		Token: token,
	})
	if err != nil {
		return nil, err
	}

	return serializeAccount(rsp.Account), nil
}

// Refresh an account using a secret
func (s *svc) Refresh(secret string, opts ...auth.RefreshOption) (*auth.Token, error) {
	options := auth.NewRefreshOptions(opts...)

	rsp, err := s.auth.Refresh(context.Background(), &pb.RefreshRequest{
		Secret:      secret,
		TokenExpiry: int64(options.TokenExpiry.Seconds()),
	})
	if err != nil {
		return nil, err
	}

	return serializeToken(rsp.Token), nil
}

var ruleJoinKey = ":"

// isValidRule returns a bool, indicating if a rule permits access to a
// resource for a given account
func isValidRule(rule *pb.Rule, acc *auth.Account, res *auth.Resource) bool {
	if rule.Role == "*" {
		return true
	}

	for _, role := range acc.Roles {
		if rule.Role == role {
			return true
		}

		// allow user.anything if role is user.*
		if strings.HasSuffix(rule.Role, ".*") && strings.HasPrefix(rule.Role, role+".") {
			return true
		}
	}

	return false
}

// listRules gets all the rules from the store which have an id
// prefix matching the filters
func (s *svc) listRules(filters ...string) []*pb.Rule {
	s.Lock()
	defer s.Unlock()

	prefix := strings.Join(filters, ruleJoinKey)

	var rules []*pb.Rule
	for _, r := range s.rules {
		if strings.HasPrefix(r.Id, prefix) {
			rules = append(rules, r)
		}
	}

	return rules
}

// loadRules retrieves the rules from the auth service
func (s *svc) loadRules() {
	rsp, err := s.auth.ListRules(context.TODO(), &pb.ListRulesRequest{}, client.WithRetries(3))
	s.Lock()
	defer s.Unlock()

	if err != nil {
		log.Errorf("Error listing rules: %v", err)
		s.rules = []*pb.Rule{}
		return
	}

	s.rules = rsp.Rules
}

func serializeToken(t *pb.Token) *auth.Token {
	return &auth.Token{
		Token:    t.Token,
		Type:     t.Type,
		Created:  time.Unix(t.Created, 0),
		Expiry:   time.Unix(t.Expiry, 0),
		Subject:  t.Subject,
		Roles:    t.Roles,
		Metadata: t.Metadata,
	}
}

func serializeAccount(a *pb.Account) *auth.Account {
	var secret *auth.Token
	if a.Secret != nil {
		secret = serializeToken(a.Secret)
	}

	return &auth.Account{
		ID:       a.Id,
		Roles:    a.Roles,
		Metadata: a.Metadata,
		Secret:   secret,
	}
}
<|endoftext|>


<commit_before>// Copyright 2014 The Serviced 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 commons

import (
	"bufio"
	"errors"
	"fmt"
	"reflect"
	"regexp"
	"strconv"
	"strings"
	"unicode"
	"unicode/utf8"

	"github.com/docker/docker/pkg/parsers"
)

// states that the parser can be in as it scans
const (
	scanningHostOrRepoName = iota
	scanningHost
	scanningPortOrTag
	scanningPort
	scanningRepoNameOrRepo
	scanningRepoName
	scanningRepo
	scanningTag
)

// runes that we have to check for as we scan
var (
	colon      rune
	dash       rune
	period     rune
	slash      rune
	underscore rune
)

// ImageID represents a Docker Image identifier.
type ImageID struct {
	Host string
	Port int
	User string
	Repo string
	Tag  string
}

func init() {
	// setup the required runes
	colon, _ = utf8.DecodeRune([]byte(":"))
	dash, _ = utf8.DecodeRune([]byte("-"))
	period, _ = utf8.DecodeRune([]byte("."))
	slash, _ = utf8.DecodeRune([]byte("/"))
	underscore, _ = utf8.DecodeRune([]byte("_"))
}

func RenameImageID(dockerRegistry, tenantId string, imgID string, tag string) (*ImageID, error) {
	repo, _ := parsers.ParseRepositoryTag(imgID)
	re := regexp.MustCompile("/?([^/]+)\\z")
	matches := re.FindStringSubmatch(repo)
	if matches == nil {
		return nil, errors.New("malformed imageid")
	}
	name := matches[1]
	newImageID := fmt.Sprintf("%s/%s/%s:%s", dockerRegistry, tenantId, name, tag)
	return ParseImageID(newImageID)
}

// ParseImageID parses the string representation of a Docker image ID into an ImageID structure.
// The grammar used by the parser is:
// image id = [host(':'port|'/')]reponame[':'tag]
// host     = {alpha|digit|'.'|'-'}+
// port     = {digit}+
// reponame = [user'/']repo
// user     = {alpha|digit|'-'|'_'}+
// repo     = {alpha|digit|'-'|'_'}+
// tag      = {alpha|digit|'-'|'_'|'.'}+
// The grammar is ambiguous so the parser is a little messy in places.
func ParseImageID(iid string) (*ImageID, error) {
	scanner := bufio.NewScanner(strings.NewReader(iid))
	scanner.Split(bufio.ScanRunes)
	result := &ImageID{}

	scanned := []string{}
	tokbuf := []byte{}

	state := scanningHostOrRepoName

	for scanner.Scan() {
		rune, _ := utf8.DecodeRune([]byte(scanner.Text()))
		switch state {
		case scanningHostOrRepoName:
			switch {
			case unicode.IsLetter(rune), unicode.IsDigit(rune), rune == dash:
				tokbuf = append(tokbuf, byte(rune))
			case rune == period:
				tokbuf = append(tokbuf, byte(rune))
				state = scanningHost
			case rune == underscore:
				tokbuf = append(tokbuf, byte(rune))
				state = scanningRepoName
			case rune == slash:
				scanned = append(scanned, string(tokbuf))
				tokbuf = []byte{}
				state = scanningRepoNameOrRepo
			case rune == colon:
				scanned = append(scanned, string(tokbuf))
				tokbuf = []byte{}
				state = scanningPortOrTag
			default:
				return nil, fmt.Errorf("invalid ImageID %s: bad host or name", iid)
			}
		case scanningHost:
			switch {
			case unicode.IsLetter(rune), unicode.IsDigit(rune), rune == period, rune == dash:
				tokbuf = append(tokbuf, byte(rune))
			case rune == colon:
				result.Host = string(tokbuf)
				tokbuf = []byte{}
				state = scanningPort
			case rune == slash:
				result.Host = string(tokbuf)
				tokbuf = []byte{}
				state = scanningRepoName
			default:
				return nil, fmt.Errorf("invalid ImageID %s: bad hostname", iid)
			}
		case scanningRepoNameOrRepo:
			switch {
			case unicode.IsLetter(rune), unicode.IsDigit(rune), rune == dash, rune == underscore:
				tokbuf = append(tokbuf, byte(rune))
			case rune == colon:
				result.User = scanned[0]
				scanned = []string{}
				result.Repo = string(tokbuf)
				tokbuf = []byte{}
				state = scanningTag
			case rune == slash:
				result.Host = scanned[0]
				scanned = []string{}
				result.User = string(tokbuf)
				tokbuf = []byte{}
				state = scanningRepo
			default:
				return nil, fmt.Errorf("invalid ImageID %s: bad host or repo name", iid)
			}
		case scanningPort:
			switch {
			case unicode.IsDigit(rune):
				tokbuf = append(tokbuf, byte(rune))
			case rune == slash:
				portno, err := strconv.Atoi(string(tokbuf))
				if err != nil {
					return nil, fmt.Errorf("invalid ImageID %s: %v", iid, err)
				}
				result.Port = portno
				tokbuf = []byte{}
				state = scanningRepoName
			default:
				return nil, fmt.Errorf("invalid ImageID %s: bad port number", iid)
			}
		case scanningRepoName:
			switch {
			case unicode.IsLetter(rune), unicode.IsDigit(rune), rune == dash, rune == underscore:
				tokbuf = append(tokbuf, byte(rune))
			case rune == slash:
				result.User = string(tokbuf)
				tokbuf = []byte{}
				state = scanningRepo
			case rune == colon:
				result.Repo = string(tokbuf)
				tokbuf = []byte{}
				state = scanningTag
			default:
				return nil, fmt.Errorf("invalid ImageID %s: bad repo name", iid)
			}
		case scanningRepo:
			switch {
			case unicode.IsLetter(rune), unicode.IsDigit(rune), rune == dash, rune == underscore:
				tokbuf = append(tokbuf, byte(rune))
			case rune == colon:
				result.Repo = string(tokbuf)
				tokbuf = []byte{}
				state = scanningTag
			default:
				return nil, fmt.Errorf("invalid ImageID %s: bad repo", iid)
			}
		case scanningTag:
			switch {
			case unicode.IsLetter(rune), unicode.IsDigit(rune), rune == dash, rune == underscore, rune == period:
				tokbuf = append(tokbuf, byte(rune))
			default:
				return nil, fmt.Errorf("invalid ImageID %s: bad tag (rune:'%c')", iid, rune)
			}
		case scanningPortOrTag:
			switch {
			case unicode.IsDigit(rune):
				tokbuf = append(tokbuf, byte(rune))
			case unicode.IsLetter(rune), rune == dash, rune == period:
				tokbuf = append(tokbuf, byte(rune))
				result.Repo = scanned[0]
				scanned = []string{}
				state = scanningTag
			case rune == slash:
				result.Host = scanned[0]
				scanned = []string{}

				portno, err := strconv.Atoi(string(tokbuf))
				if err != nil {
					return nil, fmt.Errorf("invalid ImageID %s: %v", iid, err)
				}
				result.Port = portno

				tokbuf = []byte{}
				state = scanningRepoName
			default:
				return nil, fmt.Errorf("invalid ImageID %s: bad port or tag", iid)
			}
		}
	}

	switch state {
	case scanningHostOrRepoName, scanningRepoName, scanningRepo:
		result.Repo = string(tokbuf)
	case scanningRepoNameOrRepo:
		result.User = scanned[0]
		result.Repo = string(tokbuf)
	case scanningPort, scanningHost:
		return nil, fmt.Errorf("incomplete ImageID %s", iid)
	case scanningPortOrTag:
		result.Repo = scanned[0]
		result.Tag = string(tokbuf)
	case scanningTag:
		result.Tag = string(tokbuf)
	}

	return result, nil
}

// JoinRepoTag joins an image repo with the tag
func JoinRepoTag(repo, tag string) string {
	return fmt.Sprintf("%s:%s", repo, tag)
}

// Equals compares to ImageID objects to verify they are the same
func (iid ImageID) Equals(iid2 ImageID) bool {
	if iid.BaseName() != iid2.BaseName() {
		return false
	}

	return iid.Tag == iid2.Tag || (iid.IsLatest() && iid2.IsLatest())
}

// String returns a string representation of the ImageID structure
func (iid ImageID) String() string {
	name := iid.BaseName()

	if iid.Tag != "" {
		name = name + ":" + iid.Tag
	}

	return name
}

// BaseName returns a string representation of the ImageID structure sans tag
func (iid ImageID) BaseName() string {
	s := []string{}

	if iid.Host != "" {
		s = append(s, iid.Host)
		if iid.Port != 0 {
			s = append(s, ":", strconv.Itoa(iid.Port))
		}
		s = append(s, "/")
	}

	if iid.User != "" {
		s = append(s, iid.User, "/")
	}

	s = append(s, iid.Repo)

	return strings.Join(s, "")
}

// Registry returns registry component of the ImageID as a string with the form: hostname:port
func (iid ImageID) Registry() string {
	s := []string{}

	if len(iid.Host) == 0 {
		return ""
	}

	s = append(s, iid.Host)

	if iid.Port != 0 {
		s = append(s, ":", strconv.Itoa(iid.Port))
	}

	return strings.Join(s, "")
}

// IsLatest returns a boolean that indicates that the image ID is the latest
func (iid ImageID) IsLatest() bool {
	switch iid.Tag {
	case "", "latest":
		return true
	}
	return false
}

// Validate returns true if the ImageID structure is valid.
func (iid *ImageID) Validate() bool {
	piid, err := ParseImageID(iid.String())
	if err != nil {
		return false
	}

	return reflect.DeepEqual(piid, iid)
}
<commit_msg>Review feedback, comment on Image parsing method<commit_after>// Copyright 2014 The Serviced 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 commons

import (
	"bufio"
	"errors"
	"fmt"
	"reflect"
	"regexp"
	"strconv"
	"strings"
	"unicode"
	"unicode/utf8"

	"github.com/docker/docker/pkg/parsers"
)

// states that the parser can be in as it scans
const (
	scanningHostOrRepoName = iota
	scanningHost
	scanningPortOrTag
	scanningPort
	scanningRepoNameOrRepo
	scanningRepoName
	scanningRepo
	scanningTag
)

// runes that we have to check for as we scan
var (
	colon      rune
	dash       rune
	period     rune
	slash      rune
	underscore rune
)

// ImageID represents a Docker Image identifier.
type ImageID struct {
	Host string
	Port int
	User string
	Repo string
	Tag  string
}

func init() {
	// setup the required runes
	colon, _ = utf8.DecodeRune([]byte(":"))
	dash, _ = utf8.DecodeRune([]byte("-"))
	period, _ = utf8.DecodeRune([]byte("."))
	slash, _ = utf8.DecodeRune([]byte("/"))
	underscore, _ = utf8.DecodeRune([]byte("_"))
}

// Return an Image object from all the normal parts of a serviced-managed
// image name (registry, tenant ID, image, and tag)
func RenameImageID(dockerRegistry, tenantId string, imgID string, tag string) (*ImageID, error) {
	repo, _ := parsers.ParseRepositoryTag(imgID)
	re := regexp.MustCompile("/?([^/]+)\\z")
	matches := re.FindStringSubmatch(repo)
	if matches == nil {
		return nil, errors.New("malformed imageid")
	}
	name := matches[1]
	newImageID := fmt.Sprintf("%s/%s/%s:%s", dockerRegistry, tenantId, name, tag)
	return ParseImageID(newImageID)
}

// ParseImageID parses the string representation of a Docker image ID into an ImageID structure.
// The grammar used by the parser is:
// image id = [host(':'port|'/')]reponame[':'tag]
// host     = {alpha|digit|'.'|'-'}+
// port     = {digit}+
// reponame = [user'/']repo
// user     = {alpha|digit|'-'|'_'}+
// repo     = {alpha|digit|'-'|'_'}+
// tag      = {alpha|digit|'-'|'_'|'.'}+
// The grammar is ambiguous so the parser is a little messy in places.
func ParseImageID(iid string) (*ImageID, error) {
	scanner := bufio.NewScanner(strings.NewReader(iid))
	scanner.Split(bufio.ScanRunes)
	result := &ImageID{}

	scanned := []string{}
	tokbuf := []byte{}

	state := scanningHostOrRepoName

	for scanner.Scan() {
		rune, _ := utf8.DecodeRune([]byte(scanner.Text()))
		switch state {
		case scanningHostOrRepoName:
			switch {
			case unicode.IsLetter(rune), unicode.IsDigit(rune), rune == dash:
				tokbuf = append(tokbuf, byte(rune))
			case rune == period:
				tokbuf = append(tokbuf, byte(rune))
				state = scanningHost
			case rune == underscore:
				tokbuf = append(tokbuf, byte(rune))
				state = scanningRepoName
			case rune == slash:
				scanned = append(scanned, string(tokbuf))
				tokbuf = []byte{}
				state = scanningRepoNameOrRepo
			case rune == colon:
				scanned = append(scanned, string(tokbuf))
				tokbuf = []byte{}
				state = scanningPortOrTag
			default:
				return nil, fmt.Errorf("invalid ImageID %s: bad host or name", iid)
			}
		case scanningHost:
			switch {
			case unicode.IsLetter(rune), unicode.IsDigit(rune), rune == period, rune == dash:
				tokbuf = append(tokbuf, byte(rune))
			case rune == colon:
				result.Host = string(tokbuf)
				tokbuf = []byte{}
				state = scanningPort
			case rune == slash:
				result.Host = string(tokbuf)
				tokbuf = []byte{}
				state = scanningRepoName
			default:
				return nil, fmt.Errorf("invalid ImageID %s: bad hostname", iid)
			}
		case scanningRepoNameOrRepo:
			switch {
			case unicode.IsLetter(rune), unicode.IsDigit(rune), rune == dash, rune == underscore:
				tokbuf = append(tokbuf, byte(rune))
			case rune == colon:
				result.User = scanned[0]
				scanned = []string{}
				result.Repo = string(tokbuf)
				tokbuf = []byte{}
				state = scanningTag
			case rune == slash:
				result.Host = scanned[0]
				scanned = []string{}
				result.User = string(tokbuf)
				tokbuf = []byte{}
				state = scanningRepo
			default:
				return nil, fmt.Errorf("invalid ImageID %s: bad host or repo name", iid)
			}
		case scanningPort:
			switch {
			case unicode.IsDigit(rune):
				tokbuf = append(tokbuf, byte(rune))
			case rune == slash:
				portno, err := strconv.Atoi(string(tokbuf))
				if err != nil {
					return nil, fmt.Errorf("invalid ImageID %s: %v", iid, err)
				}
				result.Port = portno
				tokbuf = []byte{}
				state = scanningRepoName
			default:
				return nil, fmt.Errorf("invalid ImageID %s: bad port number", iid)
			}
		case scanningRepoName:
			switch {
			case unicode.IsLetter(rune), unicode.IsDigit(rune), rune == dash, rune == underscore:
				tokbuf = append(tokbuf, byte(rune))
			case rune == slash:
				result.User = string(tokbuf)
				tokbuf = []byte{}
				state = scanningRepo
			case rune == colon:
				result.Repo = string(tokbuf)
				tokbuf = []byte{}
				state = scanningTag
			default:
				return nil, fmt.Errorf("invalid ImageID %s: bad repo name", iid)
			}
		case scanningRepo:
			switch {
			case unicode.IsLetter(rune), unicode.IsDigit(rune), rune == dash, rune == underscore:
				tokbuf = append(tokbuf, byte(rune))
			case rune == colon:
				result.Repo = string(tokbuf)
				tokbuf = []byte{}
				state = scanningTag
			default:
				return nil, fmt.Errorf("invalid ImageID %s: bad repo", iid)
			}
		case scanningTag:
			switch {
			case unicode.IsLetter(rune), unicode.IsDigit(rune), rune == dash, rune == underscore, rune == period:
				tokbuf = append(tokbuf, byte(rune))
			default:
				return nil, fmt.Errorf("invalid ImageID %s: bad tag (rune:'%c')", iid, rune)
			}
		case scanningPortOrTag:
			switch {
			case unicode.IsDigit(rune):
				tokbuf = append(tokbuf, byte(rune))
			case unicode.IsLetter(rune), rune == dash, rune == period:
				tokbuf = append(tokbuf, byte(rune))
				result.Repo = scanned[0]
				scanned = []string{}
				state = scanningTag
			case rune == slash:
				result.Host = scanned[0]
				scanned = []string{}

				portno, err := strconv.Atoi(string(tokbuf))
				if err != nil {
					return nil, fmt.Errorf("invalid ImageID %s: %v", iid, err)
				}
				result.Port = portno

				tokbuf = []byte{}
				state = scanningRepoName
			default:
				return nil, fmt.Errorf("invalid ImageID %s: bad port or tag", iid)
			}
		}
	}

	switch state {
	case scanningHostOrRepoName, scanningRepoName, scanningRepo:
		result.Repo = string(tokbuf)
	case scanningRepoNameOrRepo:
		result.User = scanned[0]
		result.Repo = string(tokbuf)
	case scanningPort, scanningHost:
		return nil, fmt.Errorf("incomplete ImageID %s", iid)
	case scanningPortOrTag:
		result.Repo = scanned[0]
		result.Tag = string(tokbuf)
	case scanningTag:
		result.Tag = string(tokbuf)
	}

	return result, nil
}

// JoinRepoTag joins an image repo with the tag
func JoinRepoTag(repo, tag string) string {
	return fmt.Sprintf("%s:%s", repo, tag)
}

// Equals compares to ImageID objects to verify they are the same
func (iid ImageID) Equals(iid2 ImageID) bool {
	if iid.BaseName() != iid2.BaseName() {
		return false
	}

	return iid.Tag == iid2.Tag || (iid.IsLatest() && iid2.IsLatest())
}

// String returns a string representation of the ImageID structure
func (iid ImageID) String() string {
	name := iid.BaseName()

	if iid.Tag != "" {
		name = name + ":" + iid.Tag
	}

	return name
}

// BaseName returns a string representation of the ImageID structure sans tag
func (iid ImageID) BaseName() string {
	s := []string{}

	if iid.Host != "" {
		s = append(s, iid.Host)
		if iid.Port != 0 {
			s = append(s, ":", strconv.Itoa(iid.Port))
		}
		s = append(s, "/")
	}

	if iid.User != "" {
		s = append(s, iid.User, "/")
	}

	s = append(s, iid.Repo)

	return strings.Join(s, "")
}

// Registry returns registry component of the ImageID as a string with the form: hostname:port
func (iid ImageID) Registry() string {
	s := []string{}

	if len(iid.Host) == 0 {
		return ""
	}

	s = append(s, iid.Host)

	if iid.Port != 0 {
		s = append(s, ":", strconv.Itoa(iid.Port))
	}

	return strings.Join(s, "")
}

// IsLatest returns a boolean that indicates that the image ID is the latest
func (iid ImageID) IsLatest() bool {
	switch iid.Tag {
	case "", "latest":
		return true
	}
	return false
}

// Validate returns true if the ImageID structure is valid.
func (iid *ImageID) Validate() bool {
	piid, err := ParseImageID(iid.String())
	if err != nil {
		return false
	}

	return reflect.DeepEqual(piid, iid)
}
<|endoftext|>


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

package fs_test

import (
	"fmt"
	"io/ioutil"
	"math/rand"
	"os"
	"path"
	"runtime"
	"sync"
	"time"

	"github.com/jacobsa/fuse/fusetesting"
	. "github.com/jacobsa/ogletest"
)

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

// Run the supplied function for each name, with parallelism.
func forEachName(names []string, f func(string)) {
	const parallelism = 8

	// Fill a channel.
	c := make(chan string, len(names))
	for _, n := range names {
		c <- n
	}
	close(c)

	// Run workers.
	var wg sync.WaitGroup
	for i := 0; i < parallelism; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for n := range c {
				f(n)
			}
		}()
	}

	wg.Wait()
}

////////////////////////////////////////////////////////////////////////
// Stress testing
////////////////////////////////////////////////////////////////////////

type StressTest struct {
	fsTest
}

func init() { RegisterTestSuite(&StressTest{}) }

func (t *StressTest) CreateAndReadManyFilesInParallel() {
	// Ensure that we get parallelism for this test.
	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(runtime.NumCPU()))

	// Choose a bunch of file names.
	const numFiles = 32

	var names []string
	for i := 0; i < numFiles; i++ {
		names = append(names, fmt.Sprintf("%d", i))
	}

	// Create a file for each name with concurrent workers.
	forEachName(
		names,
		func(n string) {
			err := ioutil.WriteFile(path.Join(t.Dir, n), []byte(n), 0400)
			AssertEq(nil, err)
		})

	// Read each back.
	forEachName(
		names,
		func(n string) {
			contents, err := ioutil.ReadFile(path.Join(t.Dir, n))
			AssertEq(nil, err)
			AssertEq(n, string(contents))
		})
}

func (t *StressTest) TruncateFileManyTimesInParallel() {
	// Ensure that we get parallelism for this test.
	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(runtime.NumCPU()))

	// Create a file.
	f, err := os.Create(path.Join(t.Dir, "foo"))
	AssertEq(nil, err)
	defer f.Close()

	// Set up a function that repeatedly truncates the file to random lengths,
	// writing the final size to a channel.
	worker := func(finalSize chan<- int64) {
		const desiredDuration = 500 * time.Millisecond

		var size int64
		startTime := time.Now()
		for time.Since(startTime) < desiredDuration {
			for i := 0; i < 10; i++ {
				size = rand.Int63n(1 << 14)
				err := f.Truncate(size)
				AssertEq(nil, err)
			}
		}

		finalSize <- size
	}

	// Run several workers.
	const numWorkers = 16
	finalSizes := make(chan int64, numWorkers)

	var wg sync.WaitGroup
	for i := 0; i < numWorkers; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			worker(finalSizes)
		}()
	}

	wg.Wait()
	close(finalSizes)

	// The final size should be consistent.
	fi, err := f.Stat()
	AssertEq(nil, err)

	var found = false
	for s := range finalSizes {
		if s == fi.Size() {
			found = true
			break
		}
	}

	ExpectTrue(found, "Unexpected size: %d", fi.Size())
}

func (t *StressTest) CreateInParallel_NoTruncate() {
	fusetesting.RunCreateInParallelTest_NoTruncate(t.ctx, t.Dir)
}

func (t *StressTest) CreateInParallel_Truncate() {
	fusetesting.RunCreateInParallelTest_Truncate(t.ctx, t.Dir)
}

func (t *StressTest) CreateInParallel_Exclusive() {
	fusetesting.RunCreateInParallelTest_Exclusive(t.ctx, t.Dir)
}

func (t *StressTest) MkdirInParallel() {
	fusetesting.RunMkdirInParallelTest(t.ctx, t.Dir)
}

func (t *StressTest) SymlinkInParallel() {
	fusetesting.RunSymlinkInParallelTest(t.ctx, t.Dir)
}
<commit_msg>Fixed AssertThat errors off of the test goroutine.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package fs_test

import (
	"fmt"
	"io/ioutil"
	"math/rand"
	"os"
	"path"
	"runtime"
	"sync"
	"time"

	"golang.org/x/net/context"

	"github.com/jacobsa/fuse/fusetesting"
	. "github.com/jacobsa/ogletest"
	"github.com/jacobsa/syncutil"
)

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

// Run the supplied function for each name, with parallelism. Return an error
// if any invocation does.
func forEachName(names []string, f func(string) error) (err error) {
	const parallelism = 8

	// Fill a channel.
	c := make(chan string, len(names))
	for _, n := range names {
		c <- n
	}
	close(c)

	// Run workers.
	firstErr := make(chan error, 1)

	var wg sync.WaitGroup
	for i := 0; i < parallelism; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for n := range c {
				err := f(n)
				if err != nil {
					select {
					case firstErr <- err:
					default:
					}
				}
			}
		}()
	}

	wg.Wait()

	err, _ = <-firstErr
	return
}

////////////////////////////////////////////////////////////////////////
// Stress testing
////////////////////////////////////////////////////////////////////////

type StressTest struct {
	fsTest
}

func init() { RegisterTestSuite(&StressTest{}) }

func (t *StressTest) CreateAndReadManyFilesInParallel() {
	var err error

	// Ensure that we get parallelism for this test.
	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(runtime.NumCPU()))

	// Choose a bunch of file names.
	const numFiles = 32

	var names []string
	for i := 0; i < numFiles; i++ {
		names = append(names, fmt.Sprintf("%d", i))
	}

	// Create a file for each name with concurrent workers.
	err = forEachName(
		names,
		func(n string) (err error) {
			err = ioutil.WriteFile(path.Join(t.Dir, n), []byte(n), 0400)
			return
		})

	AssertEq(nil, err)

	// Read each back.
	err = forEachName(
		names,
		func(n string) (err error) {
			contents, err := ioutil.ReadFile(path.Join(t.Dir, n))
			if err != nil {
				err = fmt.Errorf("ReadFile: %v", err)
				return
			}

			if string(contents) != n {
				err = fmt.Errorf("Contents mismatch: %q vs. %q", contents, n)
				return
			}

			return
		})

	AssertEq(nil, err)
}

func (t *StressTest) TruncateFileManyTimesInParallel() {
	// Ensure that we get parallelism for this test.
	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(runtime.NumCPU()))

	// Create a file.
	f, err := os.Create(path.Join(t.Dir, "foo"))
	AssertEq(nil, err)
	defer f.Close()

	// Set up a function that repeatedly truncates the file to random lengths,
	// writing the final size to a channel.
	worker := func(finalSize chan<- int64) (err error) {
		const desiredDuration = 500 * time.Millisecond

		var size int64
		startTime := time.Now()
		for time.Since(startTime) < desiredDuration {
			for i := 0; i < 10; i++ {
				size = rand.Int63n(1 << 14)
				err = f.Truncate(size)
				if err != nil {
					return
				}
			}
		}

		finalSize <- size
		return
	}

	// Run several workers.
	b := syncutil.NewBundle(t.ctx)

	const numWorkers = 16
	finalSizes := make(chan int64, numWorkers)

	for i := 0; i < numWorkers; i++ {
		b.Add(func(ctx context.Context) (err error) {
			err = worker(finalSizes)
			return
		})
	}

	err = b.Join()
	AssertEq(nil, err)

	close(finalSizes)

	// The final size should be consistent.
	fi, err := f.Stat()
	AssertEq(nil, err)

	var found = false
	for s := range finalSizes {
		if s == fi.Size() {
			found = true
			break
		}
	}

	ExpectTrue(found, "Unexpected size: %d", fi.Size())
}

func (t *StressTest) CreateInParallel_NoTruncate() {
	fusetesting.RunCreateInParallelTest_NoTruncate(t.ctx, t.Dir)
}

func (t *StressTest) CreateInParallel_Truncate() {
	fusetesting.RunCreateInParallelTest_Truncate(t.ctx, t.Dir)
}

func (t *StressTest) CreateInParallel_Exclusive() {
	fusetesting.RunCreateInParallelTest_Exclusive(t.ctx, t.Dir)
}

func (t *StressTest) MkdirInParallel() {
	fusetesting.RunMkdirInParallelTest(t.ctx, t.Dir)
}

func (t *StressTest) SymlinkInParallel() {
	fusetesting.RunSymlinkInParallelTest(t.ctx, t.Dir)
}
<|endoftext|>


<commit_before>package executor

import (
	. "github.com/smartystreets/goconvey/convey"
	"strings"
	"testing"
	"time"
)

func TestTester(t *testing.T) {
	var (
		tester       *ConcurrentTester
		shell        *TimedShell
		results      []string
		compilations []*ShellCommand
		executions   []*ShellCommand
	)

	Convey("Subject: Test controlled execution of tests", t, func() {
		shell = NewTimedShell()
		tester = NewConcurrentTester(shell)
		folders := []string{"a", "b", "c"}

		Convey("When packages are executed synchronously", func() {
			results = tester.TestAll(folders)
			compilations = shell.Compilations()
			executions = shell.Executions()

			Convey("The tester should build all dependencies of input folders", func() {
				for i, input := range folders {
					So(compilations[i].Command, ShouldEqual, "go test -i "+input)
				}
			})

			Convey("The tester should execute the tests in each folder with the correct arguments", func() {
				for i, input := range folders {
					So(executions[i].Command, ShouldEqual, "go test -v -timeout=-42s "+input)
				}
			})

			Convey("Each package should be run after the other in the given order", func() {
				for i := 0; i < len(executions)-1; i++ {
					current := executions[i]
					next := executions[i+1]
					So(current.Started, ShouldHappenBefore, next.Started)
					So(current.Ended, ShouldHappenOnOrBefore, next.Started)
				}
			})

			Convey("There should be a test output result for each input folder", func() {
				So(len(results), ShouldEqual, len(folders))

			})

			Convey("The output should be as expected", func() {
				for i, _ := range folders {
					So(results[i], ShouldEqual, executions[i].Command)
				}
			})
		})

		Convey("When packages are tested in batches", func() {
			Convey("packages should be tested in batches while maintaining the given order", nil)
		})
	})
}

type ShellCommand struct {
	Command string
	Started time.Time
	Ended   time.Time
}

type TimedShell struct {
	executions   []*ShellCommand
	compilations []*ShellCommand
}

func (self *TimedShell) Compilations() []*ShellCommand {
	return self.compilations
}

func (self *TimedShell) Executions() []*ShellCommand {
	return self.executions
}

func (self *TimedShell) Execute(name string, args ...string) (output string, err error) {
	output = name + " " + strings.Join(args, " ")
	start := time.Now()
	nap, err := time.ParseDuration("10ms")
	if err != nil {
		panic(err)
	}
	time.Sleep(nap)
	end := time.Now()
	command := &ShellCommand{output, start, end}
	if strings.Contains(output, " -i ") {
		self.compilations = append(self.compilations, command)
	} else {
		self.executions = append(self.executions, command)
	}
	return
}
func (self *TimedShell) Getenv(key string) string {
	panic("NOT SUPPORTED")
}
func (self *TimedShell) Setenv(key, value string) error {
	panic("NOT SUPPORTED")
}

func NewTimedShell() *TimedShell {
	self := &TimedShell{}
	self.executions = []*ShellCommand{}
	self.compilations = []*ShellCommand{}
	return self
}
<commit_msg>Much cleaner.<commit_after>package executor

import (
	"fmt"
	. "github.com/smartystreets/goconvey/convey"
	"strings"
	"testing"
	"time"
)

func TestConcurrentTester(t *testing.T) {
	var fixture *TesterFixture

	Convey("Subject: Controlled (and concurrent) execution of test packages", t, func() {
		fixture = NewTesterFixture()

		Convey("When tests for each package are executed", func() {
			fixture.RunTests()

			Convey("The tester should build all dependencies of input packages",
				fixture.ShouldHaveRecordOfCompilationCommands)

			Convey("The tester should execute the tests in each package with the correct arguments",
				fixture.ShouldHaveRecordOfExecutionCommands)

			Convey("There should be a test output result for each package",
				fixture.ShouldHaveOneOutputPerInput)

			Convey("The output should be as expected",
				fixture.OutputShouldBeAsExpected)
		})

		Convey("When the tests for each package are executed synchronously", func() {
			fixture.RunTests()

			Convey("Each package should be run synchronously and in the given order",
				fixture.CheckContiguousExecution)
		})

		Convey("When packages are tested in batches", func() {
			Convey("packages should be tested in batches while maintaining the given order", nil)
		})
	})
}

type TesterFixture struct {
	tester       *ConcurrentTester
	shell        *TimedShell
	results      []string
	compilations []*ShellCommand
	executions   []*ShellCommand
	packages     []string
}

func NewTesterFixture() *TesterFixture {
	self := &TesterFixture{}
	self.shell = NewTimedShell()
	self.tester = NewConcurrentTester(self.shell)
	self.packages = []string{"a", "b", "c", "d"}
	return self
}

func (self *TesterFixture) RunTests() {
	self.results = self.tester.TestAll(self.packages)
	self.compilations = self.shell.Compilations()
	self.executions = self.shell.Executions()
}

func (self *TesterFixture) ShouldHaveRecordOfCompilationCommands() {
	for i, pkg := range self.packages {
		command := self.compilations[i].Command
		So(command, ShouldEqual, "go test -i "+pkg)
	}
}

func (self *TesterFixture) ShouldHaveRecordOfExecutionCommands() {
	for i, pkg := range self.packages {
		So(self.executions[i].Command, ShouldEqual, "go test -v -timeout=-42s "+pkg)
	}
}

func (self *TesterFixture) ShouldHaveOneOutputPerInput() {
	So(len(self.results), ShouldEqual, len(self.packages))
}

func (self *TesterFixture) OutputShouldBeAsExpected() {
	for i, _ := range self.packages {
		So(self.results[i], ShouldEqual, self.executions[i].Command)
	}
}

func (self *TesterFixture) CheckContiguousExecution() {
	for i := 0; i < len(self.executions)-1; i++ {
		current := self.executions[i]
		next := self.executions[i+1]
		So(current.Started, ShouldHappenBefore, next.Started)
		So(current.Ended, ShouldHappenOnOrBefore, next.Started)
	}
}

/**** Fakes ****/

type ShellCommand struct {
	Command string
	Started time.Time
	Ended   time.Time
}

type TimedShell struct {
	executions   []*ShellCommand
	compilations []*ShellCommand
}

func (self *TimedShell) Compilations() []*ShellCommand {
	return self.compilations
}

func (self *TimedShell) Executions() []*ShellCommand {
	return self.executions
}

func (self *TimedShell) Execute(name string, args ...string) (output string, err error) {
	command := self.composeCommand(name + " " + strings.Join(args, " "))
	output = command.Command

	if strings.Contains(command.Command, " -i ") {
		self.compilations = append(self.compilations, command)
	} else {
		self.executions = append(self.executions, command)
	}
	return
}
func (self *TimedShell) composeCommand(commandText string) *ShellCommand {
	start := time.Now()
	time.Sleep(nap)
	end := time.Now()
	return &ShellCommand{commandText, start, end}
}
func (self *TimedShell) Getenv(key string) string {
	panic("NOT SUPPORTED")
}
func (self *TimedShell) Setenv(key, value string) error {
	panic("NOT SUPPORTED")
}

func NewTimedShell() *TimedShell {
	self := &TimedShell{}
	self.executions = []*ShellCommand{}
	self.compilations = []*ShellCommand{}
	return self
}

var nap, _ = time.ParseDuration("10ms")
var _ = fmt.Sprintf("fmt")
<|endoftext|>


<commit_before>package compose

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strings"
	"text/tabwriter"

	"gopkg.in/src-d/go-git.v4"
	yaml "gopkg.in/yaml.v2"
)

type Command []string

type Commands map[string]Command

type VariableFile struct {
	File        string
	Environment Environment
}

// Service ...
type Service struct {
	Abstract    bool
	Parent      string
	Path        string
	Commands    Commands
	Command     Command
	Environment Environment
	Variables   map[string]VariableFile
}

type Environment []string

// Compose ... composed infrastructure
type Compose struct {
	Version    string
	projectDir string
	Services   map[string]Service
	//	Environments map[string]Environment
	Environment Environment

	DryRun bool
}

type execResult struct {
	//	environmentID string
	serviceID string
	commandID string
	command   Command
	execError error
}

type execResults struct {
	execResultList []*execResult
}

func (c *execResults) add(execResult execResult) {
	c.execResultList = append(c.execResultList, &execResult)
}

func newExecResults() *execResults {
	r := &execResults{}
	r.execResultList = make([]*execResult, 0)
	return r
}

// Exec ...
func (c *Compose) Exec(args []string) error {
	execResults := newExecResults()
	//serviceCmdAlias := args[0]

	// cmds, present := c.Commands[serviceCmdAlias]
	// var err error
	// if present {
	// 	for _, cmd := range cmds {
	// 		res := c.execServiceCmd(cmd)
	// 		execResults = append(execResults, res)
	// 		err = res.execError
	// 		if res.execError != nil {
	// 			break
	// 		}
	// 	}
	// } else {
	results, err := c.execServiceCmds(args, execResults)
	//execResults = append(execResults, res)
	//	err := res.execError
	//}

	dumpExecResults(results)

	return err
}

// List ... List all available command
func (c *Compose) List(args []string) error {
	const padding = 8
	w := tabwriter.NewWriter(os.Stdout, 0, 0, padding, ' ', 0)
	fmt.Fprintln(w, "SERVICE\tCOMMAND\tSUB-COMMAND\t")

	var srvKeys []string
	for k, srv := range c.Services {
		if !srv.Abstract {
			srvKeys = append(srvKeys, k)
		}
	}
	sort.Strings(srvKeys)
	for _, srv := range srvKeys {
		service := c.Services[srv]

		if len(service.Command) > 0 {
			dumpCommandList(w, srv, "", service.Command)
		} else {
			commands := Commands{}

			for cmdKey, cmd := range service.Commands {
				commands[cmdKey] = cmd
			}

			dumpCommand(w, srv, commands)

		}

	}

	w.Flush()

	return nil
}

func (c *Compose) findServiceCommand(service Service) {

}

func dumpCommand(w *tabwriter.Writer, serviceName string, commands Commands) {
	// sort command
	var keys []string
	for k := range commands {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	for _, cmd := range keys {
		subCommands := commands[cmd]

		dumpCommandList(w, serviceName, cmd, subCommands)
	}
}

func dumpCommandList(w *tabwriter.Writer, serviceName string, command string, subCommands []string) {
	commandList := ellipsis(40, strings.Join(subCommands, " | "))

	fmt.Fprintf(w, "%s\t%s\t%s\t\n", serviceName, command, commandList)
}

func ellipsis(length int, text string) string {
	r := []rune(text)
	if len(r) > length {
		return string(r[0:length]) + "..."
	}
	return text
}

func dumpExecResults(execResults *execResults) {
	fmt.Println("Execution summary")
	fmt.Println()
	const padding = 4
	w := tabwriter.NewWriter(os.Stdout, 0, 0, padding, ' ', 0)
	fmt.Fprintln(w, "SERVICE\tCOMMAND\tSTATUS\t")

	if execResults != nil {
		for _, res := range execResults.execResultList {
			status := "Success"
			if res.execError != nil {
				status = "Error"
			}
			fmt.Fprintf(w, "%s\t%s\t%s\t\n",
				res.serviceID, res.commandID, status)
		}

	}

	w.Flush()
	fmt.Println()
}

func (c *Compose) execServiceCmd(args string, execResults *execResults) (*execResults, error) {
	return c.execServiceCmds(strings.Fields(args), execResults)
}

func (c *Compose) execServiceCmds(args []string, execResults *execResults) (*execResults, error) {
	result := execResult{}
	//	fmt.Println("Exec args:" + strings.Join(args, " "))

	var env Environment

	// check if environment is defined
	//envID := args[0]
	//	envConf, present := c.Environments[envID]
	//	if present {
	//	env = envConf
	//args = args[1:]
	//result.environmentID = envID
	//	}

	serviceName := args[0]
	result.serviceID = serviceName
	service, present := c.Services[serviceName]
	if !present {
		return nil, errors.New("Invalid service name")
	}

	var command string
	var commandArgs []string
	if len(args) > 1 {
		command = args[1]
		os.Setenv("arg.0", command)
		if len(args) > 2 {
			commandArgs = args[2:]
			os.Setenv("arg.1", commandArgs[0])
			os.Setenv("arg.1.upper", strings.ToUpper(commandArgs[0]))
		}
	}

	servicePath := filepath.Join(c.projectDir, service.Path)

	servicePath = os.ExpandEnv(servicePath)

	err := os.Chdir(servicePath)
	if err != nil {
		return nil, err
	}

	servicePathEnv := os.ExpandEnv(service.Path)
	os.Setenv("service.path", servicePathEnv)

	// Merge service environment
	env = appendEnv(service.Environment, env)

	// Find git branch name
	// TODO extract in util
	gitDir, gitErr := findGitRepo(servicePath)
	if gitErr == nil {
		repo, repoErr := git.PlainOpen(gitDir)
		if repoErr == nil {
			ref, headErr := repo.Head()
			if headErr == nil {
				branch := ref.Name().Short()

				branchSplit := strings.Split(branch, "/")

				branchFirst := branchSplit[0]
				branchLast := branchSplit[len(branchSplit)-1]

				os.Setenv("branch", branch)
				os.Setenv("branch.first", branchFirst)
				os.Setenv("branch.last", branchLast)

			}
		}
	}

	if !c.DryRun {
		// create variables files
		for _, variableFile := range service.Variables {
			//			fmt.Println("Var file  : " + variableFile.File)
			absProjectDir, _ := filepath.Abs(variableFile.File)
			parentDir := filepath.Dir(absProjectDir)

			//			fmt.Println("MkDir  : " + parentDir)
			os.MkdirAll(parentDir, 0755)

			outputVars := ""
			for _, variable := range variableFile.Environment {
				outputVars += os.ExpandEnv(variable) + "\n"
			}

			ioutil.WriteFile(variableFile.File, []byte(outputVars), 0644)
		}
	}

	if len(service.Command) > 0 {

		for _, commands := range service.Command {

			commands = os.ExpandEnv(commands)

			commandsSplit := strings.Fields(commands)

			cmd := commandsSplit[0]
			if strings.HasPrefix(cmd, "_") {
				newArgs := []string{cmd[1:]}
				args := commandsSplit[1:]
				newArgs = append(newArgs, args...)

				if len(newArgs) > 1 {
					os.Setenv("arg.0", newArgs[1])
				}

				execResults, err := c.execServiceCmds(newArgs, execResults)
				if err != nil {
					return execResults, err
				}
			} else {
				result.commandID = cmd
				err = c.executeCommand(cmd, commandsSplit[1:], servicePath, env, service)
				if err != nil {
					result.execError = err
					execResults.add(result)
					return execResults, err
				}
				execResults.add(result)
			}

		}

		//		results := append(*execResults, result)
		return execResults, nil
	}

	// search if a command is defined
	commandList, present := service.Commands[command]
	if present {
		result.commandID = command
		for _, commands := range commandList {
			commandsSplit := strings.Fields(commands)
			cmd := commandsSplit[0]
			if strings.HasPrefix(cmd, "_") {
				newArgs := []string{cmd[1:]}
				args := commandsSplit[1:]
				newArgs = append(newArgs, args...)
				execResults, err := c.execServiceCmds(newArgs, execResults)
				if err != nil {
					return execResults, err
				}
			} else {
				err = c.executeCommand(cmd, commandsSplit[1:], servicePath, env, service)
				if err != nil {
					result.execError = err
					execResults.add(result)
					return execResults, err
				}
				execResults.add(result)
			}
		}
		return execResults, err
	}

	// Execute command in service directory
	result.commandID = "-"
	result.execError = c.executeCommand(command, commandArgs, servicePath, env, service)
	execResults.add(result)
	return execResults, err
}

func (c *Compose) executeCommand(name string, args []string, dir string, env Environment, service Service) error {
	os.Setenv("service.home", dir)

	argsExpandedEnv := []string{}
	for _, arg := range args {
		argsExpandedEnv = append(argsExpandedEnv, os.ExpandEnv(arg))
	}

	envExpandedEnv := []string{}
	for _, e := range env {
		envExpandedEnv = append(envExpandedEnv, os.ExpandEnv(e))
	}

	if c.DryRun {
		//		fmt.Println("Plan to Execute ")
		fmt.Println("Exec : " + name + " " + strings.Join(argsExpandedEnv, " "))
		fmt.Println("Dir  : " + dir)
		fmt.Println("Env  : " + strings.Join(envExpandedEnv, " "))
		fmt.Println("")
		return nil
	}

	cmd := exec.Command(name, argsExpandedEnv...)
	cmd.Dir = dir
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Stdin = os.Stdin

	fullEnv := appendEnv(envExpandedEnv, os.Environ())
	cmd.Env = fullEnv

	err := cmd.Run()

	fmt.Println("State: " + cmd.ProcessState.String())

	if err != nil {
		return errors.New("Execute command error. " + err.Error())
	}

	return nil
}

// Load ...
func (c *Compose) Load(file string, projectDir string) error {
	validComposeFile, err := findComposeFile(file, projectDir)
	if err != nil {
		return err
	}

	err = c.loadCompose(validComposeFile)

	c.init()

	return err
}

func (c *Compose) mergeParent(service *Service, currentService Service) {
	if currentService.Parent != "" {
		parentService := c.Services[currentService.Parent]

		c.mergeParent(service, parentService)

		if parentService.Path != "" {
			service.Path = parentService.Path
		}

		for cmdKey, cmd := range parentService.Commands {
			if service.Commands == nil {
				service.Commands = make(Commands)
			}
			_, present := service.Commands[cmdKey]
			if !present {
				service.Commands[cmdKey] = cmd
			}
		}

		for varKey, variable := range parentService.Variables {
			if service.Variables == nil {
				service.Variables = make(map[string]VariableFile)
			}

			currentVariable, present := service.Variables[varKey]
			if present {
				for _, env := range variable.Environment {
					currentVariable.Environment = append(currentVariable.Environment, env)
				}

				if currentVariable.File == "" {
					currentVariable.File = variable.File
				}

				service.Variables[varKey] = currentVariable
			} else {
				service.Variables[varKey] = variable
			}
		}

		for _, env := range parentService.Environment {
			if service.Environment == nil {
				service.Environment = Environment{}
			}
			service.Environment = append(service.Environment, env)
		}

	}

}

func (c *Compose) init() {
	services := make(map[string]Service)
	for serviceKey, service := range c.Services {
		if !service.Abstract {
			c.mergeParent(&service, service)
			for _, env := range c.Environment {
				if service.Environment == nil {
					service.Environment = Environment{}
				}
				service.Environment = append(service.Environment, env)
			}

			services[serviceKey] = service
		}
	}

	c.Services = services
}

func (c *Compose) loadCompose(composeFile string) error {
	source, err := ioutil.ReadFile(composeFile)
	if err != nil {
		return err
	}

	composeStr := string(source)

	//	os.Setenv("branch.first", "prod")
	//	os.Setenv("branch.last", "prod")

	//	composeParsed := os.ExpandEnv(composeStr)

	err = yaml.Unmarshal([]byte(composeStr), &c)
	if err != nil {
		return err
	}

	absFileName, _ := filepath.Abs(composeFile)
	c.projectDir = filepath.Dir(absFileName)

	return nil
}

func findGitRepo(dir string) (string, error) {
	gitDir := dir + "/.git"

	_, err := os.Stat(gitDir)
	if err != nil {
		// if not root path find in parent
		absProjectDir, _ := filepath.Abs(dir)
		parentDir := filepath.Dir(absProjectDir)

		if absProjectDir == "/" {
			return "", errors.New("Git repo not found")
		}

		return findGitRepo(parentDir)
	}

	return dir, nil
}

func findComposeFile(file string, projectDir string) (string, error) {
	if projectDir != "" {
		err := os.Chdir(projectDir)
		if err != nil {
			return "", err
		}
	}

	_, err := os.Stat(file)
	if err != nil {

		// if not root path find in parent
		absProjectDir, _ := filepath.Abs(projectDir)
		parentDir := filepath.Dir(absProjectDir)

		if absProjectDir == "/" {
			return "", errors.New("Compose file not found")
		}

		return findComposeFile(file, parentDir)
	}

	return file, nil
}
<commit_msg>parce variable with service environment value<commit_after>package compose

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strings"
	"text/tabwriter"

	"gopkg.in/src-d/go-git.v4"
	yaml "gopkg.in/yaml.v2"
)

type Command []string

type Commands map[string]Command

type VariableFile struct {
	File        string
	Environment Environment
}

// Service ...
type Service struct {
	Abstract    bool
	Parent      string
	Path        string
	Commands    Commands
	Command     Command
	Environment Environment
	Variables   map[string]VariableFile
}

type Environment []string

// Compose ... composed infrastructure
type Compose struct {
	Version    string
	projectDir string
	Services   map[string]Service
	//	Environments map[string]Environment
	Environment Environment

	DryRun bool
}

type execResult struct {
	//	environmentID string
	serviceID string
	commandID string
	command   Command
	execError error
}

type execResults struct {
	execResultList []*execResult
}

func (c *execResults) add(execResult execResult) {
	c.execResultList = append(c.execResultList, &execResult)
}

func newExecResults() *execResults {
	r := &execResults{}
	r.execResultList = make([]*execResult, 0)
	return r
}

// Exec ...
func (c *Compose) Exec(args []string) error {
	execResults := newExecResults()
	//serviceCmdAlias := args[0]

	// cmds, present := c.Commands[serviceCmdAlias]
	// var err error
	// if present {
	// 	for _, cmd := range cmds {
	// 		res := c.execServiceCmd(cmd)
	// 		execResults = append(execResults, res)
	// 		err = res.execError
	// 		if res.execError != nil {
	// 			break
	// 		}
	// 	}
	// } else {
	results, err := c.execServiceCmds(args, execResults)
	//execResults = append(execResults, res)
	//	err := res.execError
	//}

	dumpExecResults(results)

	return err
}

// List ... List all available command
func (c *Compose) List(args []string) error {
	const padding = 8
	w := tabwriter.NewWriter(os.Stdout, 0, 0, padding, ' ', 0)
	fmt.Fprintln(w, "SERVICE\tCOMMAND\tSUB-COMMAND\t")

	var srvKeys []string
	for k, srv := range c.Services {
		if !srv.Abstract {
			srvKeys = append(srvKeys, k)
		}
	}
	sort.Strings(srvKeys)
	for _, srv := range srvKeys {
		service := c.Services[srv]

		if len(service.Command) > 0 {
			dumpCommandList(w, srv, "", service.Command)
		} else {
			commands := Commands{}

			for cmdKey, cmd := range service.Commands {
				commands[cmdKey] = cmd
			}

			dumpCommand(w, srv, commands)

		}

	}

	w.Flush()

	return nil
}

func (c *Compose) findServiceCommand(service Service) {

}

func dumpCommand(w *tabwriter.Writer, serviceName string, commands Commands) {
	// sort command
	var keys []string
	for k := range commands {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	for _, cmd := range keys {
		subCommands := commands[cmd]

		dumpCommandList(w, serviceName, cmd, subCommands)
	}
}

func dumpCommandList(w *tabwriter.Writer, serviceName string, command string, subCommands []string) {
	commandList := ellipsis(40, strings.Join(subCommands, " | "))

	fmt.Fprintf(w, "%s\t%s\t%s\t\n", serviceName, command, commandList)
}

func ellipsis(length int, text string) string {
	r := []rune(text)
	if len(r) > length {
		return string(r[0:length]) + "..."
	}
	return text
}

func dumpExecResults(execResults *execResults) {
	fmt.Println("Execution summary")
	fmt.Println()
	const padding = 4
	w := tabwriter.NewWriter(os.Stdout, 0, 0, padding, ' ', 0)
	fmt.Fprintln(w, "SERVICE\tCOMMAND\tSTATUS\t")

	if execResults != nil {
		for _, res := range execResults.execResultList {
			status := "Success"
			if res.execError != nil {
				status = "Error"
			}
			fmt.Fprintf(w, "%s\t%s\t%s\t\n",
				res.serviceID, res.commandID, status)
		}

	}

	w.Flush()
	fmt.Println()
}

func (c *Compose) execServiceCmd(args string, execResults *execResults) (*execResults, error) {
	return c.execServiceCmds(strings.Fields(args), execResults)
}

func (c *Compose) execServiceCmds(args []string, execResults *execResults) (*execResults, error) {
	result := execResult{}
	//	fmt.Println("Exec args:" + strings.Join(args, " "))

	var env Environment

	// check if environment is defined
	//envID := args[0]
	//	envConf, present := c.Environments[envID]
	//	if present {
	//	env = envConf
	//args = args[1:]
	//result.environmentID = envID
	//	}

	serviceName := args[0]
	result.serviceID = serviceName
	service, present := c.Services[serviceName]
	if !present {
		return nil, errors.New("Invalid service name")
	}

	for _, value := range service.Environment {
		variableSplit := strings.Split(value, "=")
		if len(variableSplit) == 2 {
			os.Setenv(variableSplit[0], os.ExpandEnv(variableSplit[1]))
		}
	}

	var command string
	var commandArgs []string
	if len(args) > 1 {
		command = args[1]
		os.Setenv("arg.0", command)
		if len(args) > 2 {
			commandArgs = args[2:]
			os.Setenv("arg.1", commandArgs[0])
			os.Setenv("arg.1.upper", strings.ToUpper(commandArgs[0]))
		}
	}

	servicePath := filepath.Join(c.projectDir, service.Path)

	servicePath = os.ExpandEnv(servicePath)

	err := os.Chdir(servicePath)
	if err != nil {
		return nil, err
	}

	servicePathEnv := os.ExpandEnv(service.Path)
	os.Setenv("service.path", servicePathEnv)

	// Merge service environment
	env = appendEnv(service.Environment, env)

	// Find git branch name
	// TODO extract in util
	gitDir, gitErr := findGitRepo(servicePath)
	if gitErr == nil {
		repo, repoErr := git.PlainOpen(gitDir)
		if repoErr == nil {
			ref, headErr := repo.Head()
			if headErr == nil {
				branch := ref.Name().Short()

				branchSplit := strings.Split(branch, "/")

				branchFirst := branchSplit[0]
				branchLast := branchSplit[len(branchSplit)-1]

				os.Setenv("branch", branch)
				os.Setenv("branch.first", branchFirst)
				os.Setenv("branch.last", branchLast)

			}
		}
	}

	if !c.DryRun {
		// create variables files
		for _, variableFile := range service.Variables {
			//			fmt.Println("Var file  : " + variableFile.File)
			absProjectDir, _ := filepath.Abs(variableFile.File)
			parentDir := filepath.Dir(absProjectDir)

			//			fmt.Println("MkDir  : " + parentDir)
			os.MkdirAll(parentDir, 0755)

			outputVars := ""
			for _, variable := range variableFile.Environment {
				outputVars += os.ExpandEnv(variable) + "\n"
			}

			ioutil.WriteFile(variableFile.File, []byte(outputVars), 0644)
		}
	}

	if len(service.Command) > 0 {

		for _, commands := range service.Command {

			commands = os.ExpandEnv(commands)

			commandsSplit := strings.Fields(commands)

			cmd := commandsSplit[0]
			if strings.HasPrefix(cmd, "_") {
				newArgs := []string{cmd[1:]}
				args := commandsSplit[1:]
				newArgs = append(newArgs, args...)

				if len(newArgs) > 1 {
					os.Setenv("arg.0", newArgs[1])
				}

				execResults, err := c.execServiceCmds(newArgs, execResults)
				if err != nil {
					return execResults, err
				}
			} else {
				result.commandID = cmd
				err = c.executeCommand(cmd, commandsSplit[1:], servicePath, env, service)
				if err != nil {
					result.execError = err
					execResults.add(result)
					return execResults, err
				}
				execResults.add(result)
			}

		}

		//		results := append(*execResults, result)
		return execResults, nil
	}

	// search if a command is defined
	commandList, present := service.Commands[command]
	if present {
		result.commandID = command
		for _, commands := range commandList {
			commandsSplit := strings.Fields(commands)
			cmd := commandsSplit[0]
			if strings.HasPrefix(cmd, "_") {
				newArgs := []string{cmd[1:]}
				args := commandsSplit[1:]
				newArgs = append(newArgs, args...)
				execResults, err := c.execServiceCmds(newArgs, execResults)
				if err != nil {
					return execResults, err
				}
			} else {
				err = c.executeCommand(cmd, commandsSplit[1:], servicePath, env, service)
				if err != nil {
					result.execError = err
					execResults.add(result)
					return execResults, err
				}
				execResults.add(result)
			}
		}
		return execResults, err
	}

	// Execute command in service directory
	result.commandID = "-"
	result.execError = c.executeCommand(command, commandArgs, servicePath, env, service)
	execResults.add(result)
	return execResults, err
}

func (c *Compose) executeCommand(name string, args []string, dir string, env Environment, service Service) error {
	os.Setenv("service.home", dir)

	argsExpandedEnv := []string{}
	for _, arg := range args {
		argsExpandedEnv = append(argsExpandedEnv, os.ExpandEnv(arg))
	}

	envExpandedEnv := []string{}
	for _, e := range env {
		envExpandedEnv = append(envExpandedEnv, os.ExpandEnv(e))
	}

	if c.DryRun {
		//		fmt.Println("Plan to Execute ")
		fmt.Println("Exec : " + name + " " + strings.Join(argsExpandedEnv, " "))
		fmt.Println("Dir  : " + dir)
		fmt.Println("Env  : " + strings.Join(envExpandedEnv, " "))
		fmt.Println("")
		return nil
	}

	cmd := exec.Command(name, argsExpandedEnv...)
	cmd.Dir = dir
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Stdin = os.Stdin

	fullEnv := appendEnv(envExpandedEnv, os.Environ())
	cmd.Env = fullEnv

	err := cmd.Run()

	fmt.Println("State: " + cmd.ProcessState.String())

	if err != nil {
		return errors.New("Execute command error. " + err.Error())
	}

	return nil
}

// Load ...
func (c *Compose) Load(file string, projectDir string) error {
	validComposeFile, err := findComposeFile(file, projectDir)
	if err != nil {
		return err
	}

	err = c.loadCompose(validComposeFile)

	c.init()

	return err
}

func (c *Compose) mergeParent(service *Service, currentService Service) {
	if currentService.Parent != "" {
		parentService := c.Services[currentService.Parent]

		c.mergeParent(service, parentService)

		if parentService.Path != "" {
			service.Path = parentService.Path
		}

		for cmdKey, cmd := range parentService.Commands {
			if service.Commands == nil {
				service.Commands = make(Commands)
			}
			_, present := service.Commands[cmdKey]
			if !present {
				service.Commands[cmdKey] = cmd
			}
		}

		for varKey, variable := range parentService.Variables {
			if service.Variables == nil {
				service.Variables = make(map[string]VariableFile)
			}

			currentVariable, present := service.Variables[varKey]
			if present {
				for _, env := range variable.Environment {
					currentVariable.Environment = append(currentVariable.Environment, env)
				}

				if currentVariable.File == "" {
					currentVariable.File = variable.File
				}

				service.Variables[varKey] = currentVariable
			} else {
				service.Variables[varKey] = variable
			}
		}

		for _, env := range parentService.Environment {
			if service.Environment == nil {
				service.Environment = Environment{}
			}
			service.Environment = append(service.Environment, env)
		}

	}

}

func (c *Compose) init() {
	services := make(map[string]Service)
	for serviceKey, service := range c.Services {
		if !service.Abstract {
			c.mergeParent(&service, service)
			for _, env := range c.Environment {
				if service.Environment == nil {
					service.Environment = Environment{}
				}
				service.Environment = append(service.Environment, env)
			}

			services[serviceKey] = service
		}
	}

	c.Services = services
}

func (c *Compose) loadCompose(composeFile string) error {
	source, err := ioutil.ReadFile(composeFile)
	if err != nil {
		return err
	}

	composeStr := string(source)

	//	os.Setenv("branch.first", "prod")
	//	os.Setenv("branch.last", "prod")

	//	composeParsed := os.ExpandEnv(composeStr)

	err = yaml.Unmarshal([]byte(composeStr), &c)
	if err != nil {
		return err
	}

	absFileName, _ := filepath.Abs(composeFile)
	c.projectDir = filepath.Dir(absFileName)

	return nil
}

func findGitRepo(dir string) (string, error) {
	gitDir := dir + "/.git"

	_, err := os.Stat(gitDir)
	if err != nil {
		// if not root path find in parent
		absProjectDir, _ := filepath.Abs(dir)
		parentDir := filepath.Dir(absProjectDir)

		if absProjectDir == "/" {
			return "", errors.New("Git repo not found")
		}

		return findGitRepo(parentDir)
	}

	return dir, nil
}

func findComposeFile(file string, projectDir string) (string, error) {
	if projectDir != "" {
		err := os.Chdir(projectDir)
		if err != nil {
			return "", err
		}
	}

	_, err := os.Stat(file)
	if err != nil {

		// if not root path find in parent
		absProjectDir, _ := filepath.Abs(projectDir)
		parentDir := filepath.Dir(absProjectDir)

		if absProjectDir == "/" {
			return "", errors.New("Compose file not found")
		}

		return findComposeFile(file, parentDir)
	}

	return file, nil
}
<|endoftext|>


<commit_before>// Go wrapper around Docker Compose, useful for integration testing.
package compose

import (
	"fmt"
	"os"
	"regexp"
	"strings"
	"log"
)

// Main type exported by the package, used to interact with a running Docker Compose configuration.
type Compose struct {
	FileName   string
	Containers map[string]*Container
}

var (
	logger = log.New(os.Stdout, "go-compose: ", log.LstdFlags)
	replaceEnvRegexp = regexp.MustCompile("\\$\\{[^\\}]+\\}")
	composeUpRegexp  = regexp.MustCompile("(?m:^docker start <- \\(u'(.*)'\\)$)")
)

// Starts a Docker Compose configuration.
// If forcePull is true, it attempts do pull newer versions of the images.
// If rmFirst is true, it attempts to kill and delete containers before starting new ones.
func Start(dockerComposeYML string, forcePull, rmFirst bool) (*Compose, error) {
	logger.Println("initializing...")
	dockerComposeYML = replaceEnv(dockerComposeYML)

	fName, err := writeTmp(dockerComposeYML)
	if err != nil {
		return nil, err
	}

	ids, err := startCompose(fName, forcePull, rmFirst)
	if err != nil {
		return nil, err
	}

	containers := make(map[string]*Container)

	for _, id := range ids {
		container, err := Inspect(id)
		if err != nil {
			return nil, err
		}
		if !container.State.Running {
			return nil, fmt.Errorf("compose: container '%v' is not running", container.Name)
		}
		containers[container.Name[1:]] = container
	}

	return &Compose{FileName: fName, Containers: containers}, nil
}

// Like Start, but panics on error.
func MustStart(dockerComposeYML string, forcePull, killFirst bool) *Compose {
	compose, err := Start(dockerComposeYML, forcePull, killFirst)
	if err != nil {
		panic(err)
	}
	return compose
}

// Kills any running containers for the current configuration.
func (c *Compose) Kill() error {
	logger.Println("killing containers...")
	if _, _, err := runCmd("docker-compose", "-f", c.FileName, "kill"); err == nil {
		logger.Println("containers killed")
		return nil
	} else {
		return fmt.Errorf("compose: error killing containers: %v", err)
	}
}

// Like Kill, but panics on error.
func (c *Compose) MustKill() {
	if err := c.Kill(); err != nil {
		panic(err)
	}
}

func replaceEnv(dockerComposeYML string) string {
	return replaceEnvRegexp.ReplaceAllStringFunc(dockerComposeYML, replaceEnvFunc)
}

func replaceEnvFunc(s string) string {
	return os.Getenv(strings.TrimSpace(s[2 : len(s)-1]))
}

func startCompose(fName string, forcePull, rmFirst bool) ([]string, error) {
	if forcePull {
		logger.Println("pulling images...")
		if _, _, err := runCmd("docker-compose", "-f", fName, "pull"); err != nil {
			return nil, fmt.Errorf("compose: error pulling images: %v", err)
		}
	}

	if rmFirst {
		logger.Println("removing stale containers...")
		_, _, err := runCmd("docker-compose", "-f", fName, "rm", "--force")
		if err != nil {
			return nil, fmt.Errorf("compose: error killing stale containers: %v", err)
		}
	}

	logger.Println("starting containers...")
	_, stderr, err := runCmd("docker-compose", "--verbose", "-f", fName, "up", "-d")
	if err != nil {
		return nil, fmt.Errorf("compose: error starting containers: %v", err)
	}
	logger.Println("containers started")

	matches := composeUpRegexp.FindAllStringSubmatch(stderr, -1)
	ids := make([]string, 0, len(matches))
	for _, match := range matches {
		ids = append(ids, match[1])
	}

	return ids, nil
}
<commit_msg>Improve documentation.<commit_after>/* Go wrapper around Docker Compose, useful for integration testing.

	var composeYML =`
	test_mockserver:
	  container_name: ms
	  image: jamesdbloom/mockserver
	  ports:
	    - "10000:1080"
	    - "1090"
	test_postgres:
	  container_name: pg
	  image: postgres
	  ports:
	    - "5432"

	compose, err := compose.Start(composeYML, true, true)
	if err != nil {
		panic(err)
	}
	defer compose.Kill()
*/
package compose

import (
	"fmt"
	"os"
	"regexp"
	"strings"
	"log"
)

// Main type exported by the package, used to interact with a running Docker Compose configuration.
type Compose struct {
	FileName   string
	Containers map[string]*Container
}

var (
	logger = log.New(os.Stdout, "go-compose: ", log.LstdFlags)
	replaceEnvRegexp = regexp.MustCompile("\\$\\{[^\\}]+\\}")
	composeUpRegexp  = regexp.MustCompile("(?m:^docker start <- \\(u'(.*)'\\)$)")
)

// Starts a Docker Compose configuration.
// If forcePull is true, it attempts do pull newer versions of the images.
// If rmFirst is true, it attempts to kill and delete containers before starting new ones.
func Start(dockerComposeYML string, forcePull, rmFirst bool) (*Compose, error) {
	logger.Println("initializing...")
	dockerComposeYML = replaceEnv(dockerComposeYML)

	fName, err := writeTmp(dockerComposeYML)
	if err != nil {
		return nil, err
	}

	ids, err := startCompose(fName, forcePull, rmFirst)
	if err != nil {
		return nil, err
	}

	containers := make(map[string]*Container)

	for _, id := range ids {
		container, err := Inspect(id)
		if err != nil {
			return nil, err
		}
		if !container.State.Running {
			return nil, fmt.Errorf("compose: container '%v' is not running", container.Name)
		}
		containers[container.Name[1:]] = container
	}

	return &Compose{FileName: fName, Containers: containers}, nil
}

// Like Start, but panics on error.
func MustStart(dockerComposeYML string, forcePull, killFirst bool) *Compose {
	compose, err := Start(dockerComposeYML, forcePull, killFirst)
	if err != nil {
		panic(err)
	}
	return compose
}

// Kills any running containers for the current configuration.
func (c *Compose) Kill() error {
	logger.Println("killing containers...")
	if _, _, err := runCmd("docker-compose", "-f", c.FileName, "kill"); err == nil {
		logger.Println("containers killed")
		return nil
	} else {
		return fmt.Errorf("compose: error killing containers: %v", err)
	}
}

// Like Kill, but panics on error.
func (c *Compose) MustKill() {
	if err := c.Kill(); err != nil {
		panic(err)
	}
}

func replaceEnv(dockerComposeYML string) string {
	return replaceEnvRegexp.ReplaceAllStringFunc(dockerComposeYML, replaceEnvFunc)
}

func replaceEnvFunc(s string) string {
	return os.Getenv(strings.TrimSpace(s[2 : len(s)-1]))
}

func startCompose(fName string, forcePull, rmFirst bool) ([]string, error) {
	if forcePull {
		logger.Println("pulling images...")
		if _, _, err := runCmd("docker-compose", "-f", fName, "pull"); err != nil {
			return nil, fmt.Errorf("compose: error pulling images: %v", err)
		}
	}

	if rmFirst {
		logger.Println("removing stale containers...")
		_, _, err := runCmd("docker-compose", "-f", fName, "rm", "--force")
		if err != nil {
			return nil, fmt.Errorf("compose: error killing stale containers: %v", err)
		}
	}

	logger.Println("starting containers...")
	_, stderr, err := runCmd("docker-compose", "--verbose", "-f", fName, "up", "-d")
	if err != nil {
		return nil, fmt.Errorf("compose: error starting containers: %v", err)
	}
	logger.Println("containers started")

	matches := composeUpRegexp.FindAllStringSubmatch(stderr, -1)
	ids := make([]string, 0, len(matches))
	for _, match := range matches {
		ids = append(ids, match[1])
	}

	return ids, nil
}
<|endoftext|>


<commit_before>package create

import (
	"fmt"

	microerror "github.com/giantswarm/microkit/error"

	awsutil "github.com/giantswarm/aws-operator/client/aws"
	"github.com/giantswarm/aws-operator/resources"
	awsresources "github.com/giantswarm/aws-operator/resources/aws"
)

type securityGroupInput struct {
	Clients     awsutil.Clients
	GroupName   string
	PortsToOpen []int
	VPCID       string
}

func (s *Service) createSecurityGroup(input securityGroupInput) (resources.ResourceWithID, error) {

	var securityGroup resources.ResourceWithID
	securityGroup = &awsresources.SecurityGroup{
		Description: input.GroupName,
		GroupName:   input.GroupName,
		VpcID:       input.VPCID,
		PortsToOpen: input.PortsToOpen,
		AWSEntity:   awsresources.AWSEntity{Clients: input.Clients},
	}
	securityGroupCreated, err := securityGroup.CreateIfNotExists()
	if err != nil {
		return nil, microerror.MaskAny(err)
	}
	if securityGroupCreated {
		s.logger.Log("info", fmt.Sprintf("created security group '%s'", input.GroupName))
	} else {
		s.logger.Log("info", fmt.Sprintf("security group '%s' already exists, reusing", input.GroupName))
	}

	return securityGroup, nil
}

func (s *Service) deleteSecurityGroup(input securityGroupInput) error {

	var securityGroup resources.ResourceWithID
	securityGroup = &awsresources.SecurityGroup{
		Description: input.GroupName,
		GroupName:   input.GroupName,
		AWSEntity:   awsresources.AWSEntity{Clients: input.Clients},
	}
	if err := securityGroup.Delete(); err != nil {
		return microerror.MaskAny(err)
	} else {
		s.logger.Log("info", "deleted security group '%s'", input.GroupName)
	}

	return nil
}

func securityGroupName(clusterName string, groupName string) string {
	return fmt.Sprintf("%s-%s", clusterName, groupName)
}
<commit_msg>Kill whitespace<commit_after>package create

import (
	"fmt"

	microerror "github.com/giantswarm/microkit/error"

	awsutil "github.com/giantswarm/aws-operator/client/aws"
	"github.com/giantswarm/aws-operator/resources"
	awsresources "github.com/giantswarm/aws-operator/resources/aws"
)

type securityGroupInput struct {
	Clients     awsutil.Clients
	GroupName   string
	PortsToOpen []int
	VPCID       string
}

func (s *Service) createSecurityGroup(input securityGroupInput) (resources.ResourceWithID, error) {
	var securityGroup resources.ResourceWithID
	securityGroup = &awsresources.SecurityGroup{
		Description: input.GroupName,
		GroupName:   input.GroupName,
		VpcID:       input.VPCID,
		PortsToOpen: input.PortsToOpen,
		AWSEntity:   awsresources.AWSEntity{Clients: input.Clients},
	}
	securityGroupCreated, err := securityGroup.CreateIfNotExists()
	if err != nil {
		return nil, microerror.MaskAny(err)
	}
	if securityGroupCreated {
		s.logger.Log("info", fmt.Sprintf("created security group '%s'", input.GroupName))
	} else {
		s.logger.Log("info", fmt.Sprintf("security group '%s' already exists, reusing", input.GroupName))
	}

	return securityGroup, nil
}

func (s *Service) deleteSecurityGroup(input securityGroupInput) error {
	var securityGroup resources.ResourceWithID
	securityGroup = &awsresources.SecurityGroup{
		Description: input.GroupName,
		GroupName:   input.GroupName,
		AWSEntity:   awsresources.AWSEntity{Clients: input.Clients},
	}
	if err := securityGroup.Delete(); err != nil {
		return microerror.MaskAny(err)
	} else {
		s.logger.Log("info", "deleted security group '%s'", input.GroupName)
	}

	return nil
}

func securityGroupName(clusterName string, groupName string) string {
	return fmt.Sprintf("%s-%s", clusterName, groupName)
}
<|endoftext|>