<commit_before>package collector

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"strings"
	"sync"
	"time"

	"github.com/openconfig/gnmi/proto/gnmi"
	"github.com/openconfig/gnmi/proto/gnmi_ext"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/metadata"
)

const (
	defaultRetryTimer = 10 * time.Second
)

type TargetError struct {
	SubscriptionName string
	Err              error
}

// Target represents a gNMI enabled box
type Target struct {
	Config        *TargetConfig                  `json:"config,omitempty"`
	Subscriptions map[string]*SubscriptionConfig `json:"subscriptions,omitempty"`

	m                  *sync.Mutex
	Client             gnmi.GNMIClient                      `json:"-"`
	SubscribeClients   map[string]gnmi.GNMI_SubscribeClient `json:"-"` // subscription name to subscribeClient
	subscribeCancelFn  map[string]context.CancelFunc
	pollChan           chan string // subscription name to be polled
	subscribeResponses chan *SubscribeResponse
	errors             chan *TargetError
}

// TargetConfig //
type TargetConfig struct {
	Name          string        `mapstructure:"name,omitempty" json:"name,omitempty"`
	Address       string        `mapstructure:"address,omitempty" json:"address,omitempty"`
	Username      *string       `mapstructure:"username,omitempty" json:"username,omitempty"`
	Password      *string       `mapstructure:"password,omitempty" json:"password,omitempty"`
	Timeout       time.Duration `mapstructure:"timeout,omitempty" json:"timeout,omitempty"`
	Insecure      *bool         `mapstructure:"insecure,omitempty" json:"insecure,omitempty"`
	TLSCA         *string       `mapstructure:"tls-ca,omitempty" json:"tls-ca,omitempty"`
	TLSCert       *string       `mapstructure:"tls-cert,omitempty" json:"tls-cert,omitempty"`
	TLSKey        *string       `mapstructure:"tls-key,omitempty" json:"tls-key,omitempty"`
	SkipVerify    *bool         `mapstructure:"skip-verify,omitempty" json:"skip-verify,omitempty"`
	Subscriptions []string      `mapstructure:"subscriptions,omitempty" json:"subscriptions,omitempty"`
	Outputs       []string      `mapstructure:"outputs,omitempty" json:"outputs,omitempty"`
	BufferSize    uint          `mapstructure:"buffer-size,omitempty" json:"buffer-size,omitempty"`
	RetryTimer    time.Duration `mapstructure:"retry,omitempty" json:"retry-timer,omitempty"`
	TLSMinVersion string        `mapstructure:"tls-min-version,omitempty" json:"tls-min-version,omitempty"`
	TLSMaxVersion string        `mapstructure:"tls-max-version,omitempty" json:"tls-max-version,omitempty"`
	TLSVersion    string        `mapstructure:"tls-version,omitempty" json:"tls-version,omitempty"`
}

func (tc *TargetConfig) String() string {
	b, err := json.Marshal(tc)
	if err != nil {
		return ""
	}
	return string(b)
}

// NewTarget //
func NewTarget(c *TargetConfig) *Target {
	t := &Target{
		Config:             c,
		Subscriptions:      make(map[string]*SubscriptionConfig),
		m:                  new(sync.Mutex),
		SubscribeClients:   make(map[string]gnmi.GNMI_SubscribeClient),
		subscribeCancelFn:  make(map[string]context.CancelFunc),
		pollChan:           make(chan string),
		subscribeResponses: make(chan *SubscribeResponse, c.BufferSize),
		errors:             make(chan *TargetError),
	}
	return t
}

// NewTLS //
func (tc *TargetConfig) newTLS() (*tls.Config, error) {
	tlsConfig := &tls.Config{
		Renegotiation:      tls.RenegotiateNever,
		InsecureSkipVerify: *tc.SkipVerify,
		MaxVersion:         tc.getTLSMaxVersion(),
		MinVersion:         tc.getTLSMinVersion(),
	}
	err := loadCerts(tlsConfig, tc)
	if err != nil {
		return nil, err
	}
	return tlsConfig, nil
}

// CreateGNMIClient //
func (t *Target) CreateGNMIClient(ctx context.Context, opts ...grpc.DialOption) error {
	if opts == nil {
		opts = []grpc.DialOption{}
	}
	if *t.Config.Insecure {
		opts = append(opts, grpc.WithInsecure())
	} else {
		tlsConfig, err := t.Config.newTLS()
		if err != nil {
			return err
		}
		opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)))
	}
	timeoutCtx, cancel := context.WithTimeout(ctx, t.Config.Timeout)
	defer cancel()
	conn, err := grpc.DialContext(timeoutCtx, t.Config.Address, opts...)
	if err != nil {
		return err
	}
	t.Client = gnmi.NewGNMIClient(conn)
	return nil
}

// Capabilities sends a gnmi.CapabilitiesRequest to the target *t and returns a gnmi.CapabilitiesResponse and an error
func (t *Target) Capabilities(ctx context.Context, ext ...*gnmi_ext.Extension) (*gnmi.CapabilityResponse, error) {
	ctx = metadata.AppendToOutgoingContext(ctx, "username", *t.Config.Username, "password", *t.Config.Password)
	response, err := t.Client.Capabilities(ctx, &gnmi.CapabilityRequest{Extension: ext})
	if err != nil {
		return nil, fmt.Errorf("failed sending capabilities request: %v", err)
	}
	return response, nil
}

// Get sends a gnmi.GetRequest to the target *t and returns a gnmi.GetResponse and an error
func (t *Target) Get(ctx context.Context, req *gnmi.GetRequest) (*gnmi.GetResponse, error) {
	ctx = metadata.AppendToOutgoingContext(ctx, "username", *t.Config.Username, "password", *t.Config.Password)
	response, err := t.Client.Get(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("failed sending GetRequest to '%s': %v", t.Config.Address, err)
	}
	return response, nil
}

// Set sends a gnmi.SetRequest to the target *t and returns a gnmi.SetResponse and an error
func (t *Target) Set(ctx context.Context, req *gnmi.SetRequest) (*gnmi.SetResponse, error) {
	ctx = metadata.AppendToOutgoingContext(ctx, "username", *t.Config.Username, "password", *t.Config.Password)
	response, err := t.Client.Set(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("failed sending SetRequest to '%s': %v", t.Config.Address, err)
	}
	return response, nil
}

// Subscribe sends a gnmi.SubscribeRequest to the target *t, responses and error are sent to the target channels
func (t *Target) Subscribe(ctx context.Context, req *gnmi.SubscribeRequest, subscriptionName string) {
SUBSC:
	nctx, cancel := context.WithCancel(ctx)
	defer cancel()
	nctx = metadata.AppendToOutgoingContext(nctx, "username", *t.Config.Username, "password", *t.Config.Password)
	subscribeClient, err := t.Client.Subscribe(nctx)
	if err != nil {
		t.errors <- &TargetError{
			SubscriptionName: subscriptionName,
			Err:              fmt.Errorf("failed to create a subscribe client, target='%s', retry in %d. err=%v", t.Config.Name, t.Config.RetryTimer, err),
		}
		cancel()
		time.Sleep(t.Config.RetryTimer)
		goto SUBSC
	}
	t.m.Lock()
	t.SubscribeClients[subscriptionName] = subscribeClient
	t.subscribeCancelFn[subscriptionName] = cancel
	t.m.Unlock()
	err = subscribeClient.Send(req)
	if err != nil {
		t.errors <- &TargetError{
			SubscriptionName: subscriptionName,
			Err:              fmt.Errorf("target '%s' send error, retry in %d. err=%v", t.Config.Name, t.Config.RetryTimer, err),
		}
		cancel()
		time.Sleep(t.Config.RetryTimer)
		goto SUBSC
	}
	switch req.GetSubscribe().Mode {
	case gnmi.SubscriptionList_STREAM:
		for {
			response, err := subscribeClient.Recv()
			if err != nil {
				t.errors <- &TargetError{
					SubscriptionName: subscriptionName,
					Err:              err,
				}
				t.errors <- &TargetError{
					SubscriptionName: subscriptionName,
					Err:              fmt.Errorf("retrying in %d", t.Config.RetryTimer),
				}
				cancel()
				time.Sleep(t.Config.RetryTimer)
				goto SUBSC
			}
			t.subscribeResponses <- &SubscribeResponse{
				SubscriptionName: subscriptionName,
				Response:         response,
			}
		}
	case gnmi.SubscriptionList_ONCE:
		for {
			response, err := subscribeClient.Recv()
			if err != nil {
				t.errors <- &TargetError{
					SubscriptionName: subscriptionName,
					Err:              err,
				}
				if errors.Is(err, io.EOF) {
					return
				}
				t.errors <- &TargetError{
					SubscriptionName: subscriptionName,
					Err:              fmt.Errorf("retrying in %d", t.Config.RetryTimer),
				}
				cancel()
				time.Sleep(t.Config.RetryTimer)
				goto SUBSC
			}
			t.subscribeResponses <- &SubscribeResponse{
				SubscriptionName: subscriptionName,
				Response:         response,
			}
			switch response.Response.(type) {
			case *gnmi.SubscribeResponse_SyncResponse:
				return
			}
		}
	case gnmi.SubscriptionList_POLL:
		for {
			select {
			case subName := <-t.pollChan:
				err = t.SubscribeClients[subName].Send(&gnmi.SubscribeRequest{
					Request: &gnmi.SubscribeRequest_Poll{
						Poll: &gnmi.Poll{},
					},
				})
				if err != nil {
					t.errors <- &TargetError{
						SubscriptionName: subscriptionName,
						Err:              fmt.Errorf("failed to send PollRequest: %v", err),
					}
					continue
				}
				response, err := subscribeClient.Recv()
				if err != nil {
					t.errors <- &TargetError{
						SubscriptionName: subscriptionName,
						Err:              err,
					}
					continue
				}
				t.subscribeResponses <- &SubscribeResponse{
					SubscriptionName: subscriptionName,
					Response:         response,
				}
			case <-nctx.Done():
				return
			}
		}
	}
}

func (t *Target) Stop() {
	t.m.Lock()
	for _, cfn := range t.subscribeCancelFn {
		cfn()
	}
	close(t.subscribeResponses)
	close(t.errors)
	close(t.pollChan)
	t.m.Unlock()
}

func loadCerts(tlscfg *tls.Config, c *TargetConfig) error {
	if *c.TLSCert != "" && *c.TLSKey != "" {
		certificate, err := tls.LoadX509KeyPair(*c.TLSCert, *c.TLSKey)
		if err != nil {
			return err
		}
		tlscfg.Certificates = []tls.Certificate{certificate}
		tlscfg.BuildNameToCertificate()
	}
	if c.TLSCA != nil && *c.TLSCA != "" {
		certPool := x509.NewCertPool()
		caFile, err := ioutil.ReadFile(*c.TLSCA)
		if err != nil {
			return err
		}
		if ok := certPool.AppendCertsFromPEM(caFile); !ok {
			return errors.New("failed to append certificate")
		}
		tlscfg.RootCAs = certPool
	}
	return nil
}

func (t *Target) numberOfOnceSubscriptions() int {
	num := 0
	for _, sub := range t.Subscriptions {
		if strings.ToUpper(sub.Mode) == "ONCE" {
			num++
		}
	}
	return num
}

func (tc *TargetConfig) UsernameString() string {
	if tc.Username == nil {
		return "NA"
	}
	return *tc.Username
}

func (tc *TargetConfig) PasswordString() string {
	if tc.Password == nil {
		return "NA"
	}
	return *tc.Password
}
func (tc *TargetConfig) InsecureString() string {
	if tc.Insecure == nil {
		return "NA"
	}
	return fmt.Sprintf("%t", *tc.Insecure)
}

func (tc *TargetConfig) TLSCAString() string {
	if tc.TLSCA == nil || *tc.TLSCA == "" {
		return "NA"
	}
	return *tc.TLSCA
}

func (tc *TargetConfig) TLSKeyString() string {
	if tc.TLSKey == nil || *tc.TLSKey == "" {
		return "NA"
	}
	return *tc.TLSKey
}

func (tc *TargetConfig) TLSCertString() string {
	if tc.TLSCert == nil || *tc.TLSCert == "" {
		return "NA"
	}
	return *tc.TLSCert
}

func (tc *TargetConfig) SkipVerifyString() string {
	if tc.SkipVerify == nil {
		return "NA"
	}
	return fmt.Sprintf("%t", *tc.SkipVerify)
}

func (tc *TargetConfig) SubscriptionString() string {
	return fmt.Sprintf("- %s", strings.Join(tc.Subscriptions, "\n"))
}

func (tc *TargetConfig) OutputsString() string {
	return strings.Join(tc.Outputs, "\n")
}

func (tc *TargetConfig) BufferSizeString() string {
	return fmt.Sprintf("%d", tc.BufferSize)
}

func (tc *TargetConfig) getTLSMinVersion() uint16 {
	v := tlsVersionStringToUint(tc.TLSVersion)
	if v > 0 {
		return v
	}
	return tlsVersionStringToUint(tc.TLSMinVersion)
}

func (tc *TargetConfig) getTLSMaxVersion() uint16 {
	v := tlsVersionStringToUint(tc.TLSVersion)
	if v > 0 {
		return v
	}
	return tlsVersionStringToUint(tc.TLSMaxVersion)
}

func tlsVersionStringToUint(v string) uint16 {
	switch v {
	default:
		return 0
	case "1.3":
		return tls.VersionTLS13
	case "1.2":
		return tls.VersionTLS12
	case "1.1":
		return tls.VersionTLS11
	case "1.0", "1":
		return tls.VersionTLS10
	}
}
<commit_msg>fix possible race condition<commit_after>package collector

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"strings"
	"sync"
	"time"

	"github.com/openconfig/gnmi/proto/gnmi"
	"github.com/openconfig/gnmi/proto/gnmi_ext"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/metadata"
)

const (
	defaultRetryTimer = 10 * time.Second
)

type TargetError struct {
	SubscriptionName string
	Err              error
}

// Target represents a gNMI enabled box
type Target struct {
	Config        *TargetConfig                  `json:"config,omitempty"`
	Subscriptions map[string]*SubscriptionConfig `json:"subscriptions,omitempty"`

	m                  *sync.Mutex
	Client             gnmi.GNMIClient                      `json:"-"`
	SubscribeClients   map[string]gnmi.GNMI_SubscribeClient `json:"-"` // subscription name to subscribeClient
	subscribeCancelFn  map[string]context.CancelFunc
	pollChan           chan string // subscription name to be polled
	subscribeResponses chan *SubscribeResponse
	errors             chan *TargetError
}

// TargetConfig //
type TargetConfig struct {
	Name          string        `mapstructure:"name,omitempty" json:"name,omitempty"`
	Address       string        `mapstructure:"address,omitempty" json:"address,omitempty"`
	Username      *string       `mapstructure:"username,omitempty" json:"username,omitempty"`
	Password      *string       `mapstructure:"password,omitempty" json:"password,omitempty"`
	Timeout       time.Duration `mapstructure:"timeout,omitempty" json:"timeout,omitempty"`
	Insecure      *bool         `mapstructure:"insecure,omitempty" json:"insecure,omitempty"`
	TLSCA         *string       `mapstructure:"tls-ca,omitempty" json:"tls-ca,omitempty"`
	TLSCert       *string       `mapstructure:"tls-cert,omitempty" json:"tls-cert,omitempty"`
	TLSKey        *string       `mapstructure:"tls-key,omitempty" json:"tls-key,omitempty"`
	SkipVerify    *bool         `mapstructure:"skip-verify,omitempty" json:"skip-verify,omitempty"`
	Subscriptions []string      `mapstructure:"subscriptions,omitempty" json:"subscriptions,omitempty"`
	Outputs       []string      `mapstructure:"outputs,omitempty" json:"outputs,omitempty"`
	BufferSize    uint          `mapstructure:"buffer-size,omitempty" json:"buffer-size,omitempty"`
	RetryTimer    time.Duration `mapstructure:"retry,omitempty" json:"retry-timer,omitempty"`
	TLSMinVersion string        `mapstructure:"tls-min-version,omitempty" json:"tls-min-version,omitempty"`
	TLSMaxVersion string        `mapstructure:"tls-max-version,omitempty" json:"tls-max-version,omitempty"`
	TLSVersion    string        `mapstructure:"tls-version,omitempty" json:"tls-version,omitempty"`
}

func (tc *TargetConfig) String() string {
	b, err := json.Marshal(tc)
	if err != nil {
		return ""
	}
	return string(b)
}

// NewTarget //
func NewTarget(c *TargetConfig) *Target {
	t := &Target{
		Config:             c,
		Subscriptions:      make(map[string]*SubscriptionConfig),
		m:                  new(sync.Mutex),
		SubscribeClients:   make(map[string]gnmi.GNMI_SubscribeClient),
		subscribeCancelFn:  make(map[string]context.CancelFunc),
		pollChan:           make(chan string),
		subscribeResponses: make(chan *SubscribeResponse, c.BufferSize),
		errors:             make(chan *TargetError),
	}
	return t
}

// NewTLS //
func (tc *TargetConfig) newTLS() (*tls.Config, error) {
	tlsConfig := &tls.Config{
		Renegotiation:      tls.RenegotiateNever,
		InsecureSkipVerify: *tc.SkipVerify,
		MaxVersion:         tc.getTLSMaxVersion(),
		MinVersion:         tc.getTLSMinVersion(),
	}
	err := loadCerts(tlsConfig, tc)
	if err != nil {
		return nil, err
	}
	return tlsConfig, nil
}

// CreateGNMIClient //
func (t *Target) CreateGNMIClient(ctx context.Context, opts ...grpc.DialOption) error {
	tOpts := make([]grpc.DialOption, 0, len(opts)+1)
	tOpts = append(tOpts, opts...)

	if *t.Config.Insecure {
		tOpts = append(tOpts, grpc.WithInsecure())
	} else {
		tlsConfig, err := t.Config.newTLS()
		if err != nil {
			return err
		}
		tOpts = append(tOpts, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)))
	}
	timeoutCtx, cancel := context.WithTimeout(ctx, t.Config.Timeout)
	defer cancel()
	conn, err := grpc.DialContext(timeoutCtx, t.Config.Address, tOpts...)
	if err != nil {
		return err
	}
	t.Client = gnmi.NewGNMIClient(conn)
	return nil
}

// Capabilities sends a gnmi.CapabilitiesRequest to the target *t and returns a gnmi.CapabilitiesResponse and an error
func (t *Target) Capabilities(ctx context.Context, ext ...*gnmi_ext.Extension) (*gnmi.CapabilityResponse, error) {
	ctx = metadata.AppendToOutgoingContext(ctx, "username", *t.Config.Username, "password", *t.Config.Password)
	response, err := t.Client.Capabilities(ctx, &gnmi.CapabilityRequest{Extension: ext})
	if err != nil {
		return nil, fmt.Errorf("failed sending capabilities request: %v", err)
	}
	return response, nil
}

// Get sends a gnmi.GetRequest to the target *t and returns a gnmi.GetResponse and an error
func (t *Target) Get(ctx context.Context, req *gnmi.GetRequest) (*gnmi.GetResponse, error) {
	ctx = metadata.AppendToOutgoingContext(ctx, "username", *t.Config.Username, "password", *t.Config.Password)
	response, err := t.Client.Get(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("failed sending GetRequest to '%s': %v", t.Config.Address, err)
	}
	return response, nil
}

// Set sends a gnmi.SetRequest to the target *t and returns a gnmi.SetResponse and an error
func (t *Target) Set(ctx context.Context, req *gnmi.SetRequest) (*gnmi.SetResponse, error) {
	ctx = metadata.AppendToOutgoingContext(ctx, "username", *t.Config.Username, "password", *t.Config.Password)
	response, err := t.Client.Set(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("failed sending SetRequest to '%s': %v", t.Config.Address, err)
	}
	return response, nil
}

// Subscribe sends a gnmi.SubscribeRequest to the target *t, responses and error are sent to the target channels
func (t *Target) Subscribe(ctx context.Context, req *gnmi.SubscribeRequest, subscriptionName string) {
SUBSC:
	nctx, cancel := context.WithCancel(ctx)
	defer cancel()
	nctx = metadata.AppendToOutgoingContext(nctx, "username", *t.Config.Username, "password", *t.Config.Password)
	subscribeClient, err := t.Client.Subscribe(nctx)
	if err != nil {
		t.errors <- &TargetError{
			SubscriptionName: subscriptionName,
			Err:              fmt.Errorf("failed to create a subscribe client, target='%s', retry in %d. err=%v", t.Config.Name, t.Config.RetryTimer, err),
		}
		cancel()
		time.Sleep(t.Config.RetryTimer)
		goto SUBSC
	}
	t.m.Lock()
	t.SubscribeClients[subscriptionName] = subscribeClient
	t.subscribeCancelFn[subscriptionName] = cancel
	t.m.Unlock()
	err = subscribeClient.Send(req)
	if err != nil {
		t.errors <- &TargetError{
			SubscriptionName: subscriptionName,
			Err:              fmt.Errorf("target '%s' send error, retry in %d. err=%v", t.Config.Name, t.Config.RetryTimer, err),
		}
		cancel()
		time.Sleep(t.Config.RetryTimer)
		goto SUBSC
	}
	switch req.GetSubscribe().Mode {
	case gnmi.SubscriptionList_STREAM:
		for {
			response, err := subscribeClient.Recv()
			if err != nil {
				t.errors <- &TargetError{
					SubscriptionName: subscriptionName,
					Err:              err,
				}
				t.errors <- &TargetError{
					SubscriptionName: subscriptionName,
					Err:              fmt.Errorf("retrying in %d", t.Config.RetryTimer),
				}
				cancel()
				time.Sleep(t.Config.RetryTimer)
				goto SUBSC
			}
			t.subscribeResponses <- &SubscribeResponse{
				SubscriptionName: subscriptionName,
				Response:         response,
			}
		}
	case gnmi.SubscriptionList_ONCE:
		for {
			response, err := subscribeClient.Recv()
			if err != nil {
				t.errors <- &TargetError{
					SubscriptionName: subscriptionName,
					Err:              err,
				}
				if errors.Is(err, io.EOF) {
					return
				}
				t.errors <- &TargetError{
					SubscriptionName: subscriptionName,
					Err:              fmt.Errorf("retrying in %d", t.Config.RetryTimer),
				}
				cancel()
				time.Sleep(t.Config.RetryTimer)
				goto SUBSC
			}
			t.subscribeResponses <- &SubscribeResponse{
				SubscriptionName: subscriptionName,
				Response:         response,
			}
			switch response.Response.(type) {
			case *gnmi.SubscribeResponse_SyncResponse:
				return
			}
		}
	case gnmi.SubscriptionList_POLL:
		for {
			select {
			case subName := <-t.pollChan:
				err = t.SubscribeClients[subName].Send(&gnmi.SubscribeRequest{
					Request: &gnmi.SubscribeRequest_Poll{
						Poll: &gnmi.Poll{},
					},
				})
				if err != nil {
					t.errors <- &TargetError{
						SubscriptionName: subscriptionName,
						Err:              fmt.Errorf("failed to send PollRequest: %v", err),
					}
					continue
				}
				response, err := subscribeClient.Recv()
				if err != nil {
					t.errors <- &TargetError{
						SubscriptionName: subscriptionName,
						Err:              err,
					}
					continue
				}
				t.subscribeResponses <- &SubscribeResponse{
					SubscriptionName: subscriptionName,
					Response:         response,
				}
			case <-nctx.Done():
				return
			}
		}
	}
}

func (t *Target) Stop() {
	t.m.Lock()
	for _, cfn := range t.subscribeCancelFn {
		cfn()
	}
	close(t.subscribeResponses)
	close(t.errors)
	close(t.pollChan)
	t.m.Unlock()
}

func loadCerts(tlscfg *tls.Config, c *TargetConfig) error {
	if *c.TLSCert != "" && *c.TLSKey != "" {
		certificate, err := tls.LoadX509KeyPair(*c.TLSCert, *c.TLSKey)
		if err != nil {
			return err
		}
		tlscfg.Certificates = []tls.Certificate{certificate}
		tlscfg.BuildNameToCertificate()
	}
	if c.TLSCA != nil && *c.TLSCA != "" {
		certPool := x509.NewCertPool()
		caFile, err := ioutil.ReadFile(*c.TLSCA)
		if err != nil {
			return err
		}
		if ok := certPool.AppendCertsFromPEM(caFile); !ok {
			return errors.New("failed to append certificate")
		}
		tlscfg.RootCAs = certPool
	}
	return nil
}

func (t *Target) numberOfOnceSubscriptions() int {
	num := 0
	for _, sub := range t.Subscriptions {
		if strings.ToUpper(sub.Mode) == "ONCE" {
			num++
		}
	}
	return num
}

func (tc *TargetConfig) UsernameString() string {
	if tc.Username == nil {
		return "NA"
	}
	return *tc.Username
}

func (tc *TargetConfig) PasswordString() string {
	if tc.Password == nil {
		return "NA"
	}
	return *tc.Password
}
func (tc *TargetConfig) InsecureString() string {
	if tc.Insecure == nil {
		return "NA"
	}
	return fmt.Sprintf("%t", *tc.Insecure)
}

func (tc *TargetConfig) TLSCAString() string {
	if tc.TLSCA == nil || *tc.TLSCA == "" {
		return "NA"
	}
	return *tc.TLSCA
}

func (tc *TargetConfig) TLSKeyString() string {
	if tc.TLSKey == nil || *tc.TLSKey == "" {
		return "NA"
	}
	return *tc.TLSKey
}

func (tc *TargetConfig) TLSCertString() string {
	if tc.TLSCert == nil || *tc.TLSCert == "" {
		return "NA"
	}
	return *tc.TLSCert
}

func (tc *TargetConfig) SkipVerifyString() string {
	if tc.SkipVerify == nil {
		return "NA"
	}
	return fmt.Sprintf("%t", *tc.SkipVerify)
}

func (tc *TargetConfig) SubscriptionString() string {
	return fmt.Sprintf("- %s", strings.Join(tc.Subscriptions, "\n"))
}

func (tc *TargetConfig) OutputsString() string {
	return strings.Join(tc.Outputs, "\n")
}

func (tc *TargetConfig) BufferSizeString() string {
	return fmt.Sprintf("%d", tc.BufferSize)
}

func (tc *TargetConfig) getTLSMinVersion() uint16 {
	v := tlsVersionStringToUint(tc.TLSVersion)
	if v > 0 {
		return v
	}
	return tlsVersionStringToUint(tc.TLSMinVersion)
}

func (tc *TargetConfig) getTLSMaxVersion() uint16 {
	v := tlsVersionStringToUint(tc.TLSVersion)
	if v > 0 {
		return v
	}
	return tlsVersionStringToUint(tc.TLSMaxVersion)
}

func tlsVersionStringToUint(v string) uint16 {
	switch v {
	default:
		return 0
	case "1.3":
		return tls.VersionTLS13
	case "1.2":
		return tls.VersionTLS12
	case "1.1":
		return tls.VersionTLS11
	case "1.0", "1":
		return tls.VersionTLS10
	}
}
<|endoftext|>


<commit_before>package command

/*
 Cribbed **heavily** from https://github.com/hashicorp/consul
*/

import (
	"fmt"
	"github.com/PolkaBand/polka/config"
	"github.com/PolkaBand/polka/utils"
	"github.com/mitchellh/cli"
	"log"
	"os"
	"strings"
	"text/template"
)

type GenerateCommand struct {
	Name string
	Ui   cli.Ui
}

func (c *GenerateCommand) Help() string {
	helpText := `
Usage: polka generate [subcommand] concept

Generate a lambda function of type [subcommand] to implement the concept.

Subcommands:

	endpoint	an endpoint that supports CRUD and a health check on (concept)\n
`
	return strings.TrimSpace(helpText)
}

func GetEndpoints() []string {
	endpoints := []string{
		"create.js",
		"read.js",
		"update.js",
		"delete.js",
		"health.js",
	}
	return endpoints
}

func MakeAllEndpoints(currentDir string, conceptName string) {
	concept := Concept{conceptName}
	for _, templateName := range GetEndpoints() {
		FindAndExecuteTemplate(currentDir, templateName, concept)
		dir := strings.Split(templateName, ".")[0]
		log.Printf("%q", strings.Split(templateName, "."))
		config.Create(concept.Name, dir)
	}
}

func MakeEndpointDir(currentDir string, concept Concept) (string, error) {
	endpointDir := fmt.Sprintf("%v/app/endpoint/%v", currentDir, concept.Name)
	log.Printf("trying to make %v\n", endpointDir)
	return endpointDir, os.MkdirAll(endpointDir, 0777)
}

func FindAndExecuteTemplate(currentDir string, templateName string, concept Concept) error {

	templateDir, err := utils.FindTemplateDir()
	if err != nil {
		return err
	}

	endpointDir, err := MakeEndpointDir(currentDir, concept)
	if err != nil {
		return err
	}
	//TODO assume that js is the only implementation language for now
	templatesGlob := fmt.Sprintf("%v/js/endpoint/*.js", templateDir)

	t, err := template.ParseGlob(templatesGlob)
	if err != nil {
		return err
	}
	outputFileName := fmt.Sprintf("%v/%v", endpointDir, templateName)
	outputFile, err := os.Create(outputFileName)
	if err != nil {
		return err
	}
	return t.ExecuteTemplate(outputFile, templateName, concept)
}

func (c *GenerateCommand) Run(args []string) int {
	if _, err := utils.AreWeInAppRootDir(); err != nil {

		return 1
	}

	if len(args) < 2 {
		fmt.Printf("%v", c.Help())
		return 1
	}

	subcommand := args[0]

	//assume that we're in the base polka directory
	//confirm that by checking to see if ./app/ exists
	if currentDir, err := os.Getwd(); err == nil {
		//TODO check to see if ./app/ exists
		switch subcommand {
		case "endpoint":
			MakeAllEndpoints(currentDir, args[1])
		case "integration":
			log.Println("generating integrations are not implemented yet")
		}
		return 0
	}
	return 1
}

func (c *GenerateCommand) Synopsis() string {
	return "generates lambda functions for endpoints"
}
<commit_msg>format changes<commit_after>package command

/*
 Cribbed **heavily** from https://github.com/hashicorp/consul
*/

import (
	"fmt"
	"github.com/PolkaBand/polka/config"
	"github.com/PolkaBand/polka/utils"
	"github.com/mitchellh/cli"
	"log"
	"os"
	"strings"
	"text/template"
)

type GenerateCommand struct {
	Name string
	Ui   cli.Ui
}

func (c *GenerateCommand) Help() string {
	helpText := `
Usage: polka generate [subcommand] concept

Generate a lambda function of type [subcommand] to implement the concept.

Subcommands:

	endpoint	an endpoint that supports CRUD and a health check on (concept)\n
`
	return strings.TrimSpace(helpText)
}

func GetEndpoints() []string {
	endpoints := []string{
		"create.js",
		"read.js",
		"update.js",
		"delete.js",
		"health.js",
	}
	return endpoints
}

func MakeAllEndpoints(currentDir string, conceptName string) {
	concept := Concept{conceptName}
	for _, templateName := range GetEndpoints() {
		FindAndExecuteTemplate(currentDir, templateName, concept)
		dir := strings.Split(templateName, ".")[0]
		log.Printf("%q", strings.Split(templateName, "."))
		config.Create(concept.Name, dir)
	}
}

func MakeEndpointDir(currentDir string, concept Concept) (string, error) {
	endpointDir := fmt.Sprintf("%v/app/endpoint/%v", currentDir, concept.Name)
	log.Printf("trying to make %v\n", endpointDir)
	return endpointDir, os.MkdirAll(endpointDir, 0777)
}

func FindAndExecuteTemplate(currentDir string, templateName string, concept Concept) error {

	templateDir, err := utils.FindTemplateDir()
	if err != nil {
		return err
	}

	endpointDir, err := MakeEndpointDir(currentDir, concept)
	if err != nil {
		return err
	}
	//TODO assume that js is the only implementation language for now
	templatesGlob := fmt.Sprintf("%v/js/endpoint/*.js", templateDir)

	t, err := template.ParseGlob(templatesGlob)
	if err != nil {
		return err
	}
	outputFileName := fmt.Sprintf("%v/%v", endpointDir, templateName)
	outputFile, err := os.Create(outputFileName)
	if err != nil {
		return err
	}
	return t.ExecuteTemplate(outputFile, templateName, concept)
}

func (c *GenerateCommand) Run(args []string) int {
	if _, err := utils.AreWeInAppRootDir(); err != nil {
		return 1
	}

	if len(args) < 2 {
		fmt.Printf("%v", c.Help())
		return 1
	}

	subcommand := args[0]

	//assume that we're in the base polka directory
	//confirm that by checking to see if ./app/ exists
	if currentDir, err := os.Getwd(); err == nil {
		//TODO check to see if ./app/ exists
		switch subcommand {
		case "endpoint":
			MakeAllEndpoints(currentDir, args[1])
		case "integration":
			log.Println("generating integrations are not implemented yet")
		}
		return 0
	}
	return 1
}

func (c *GenerateCommand) Synopsis() string {
	return "generates lambda functions for endpoints"
}
<|endoftext|>


<commit_before>package commands

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

// global variables
var (
	region  string
	project string
	stacks  map[string]*stack
)

// fetchContent - checks the source type, url/s3/file and calls the corresponding function
func fetchContent(source string) (string, error) {
	switch strings.Split(strings.ToLower(source), ":")[0] {
	case "http", "https":
		Log(fmt.Sprintln("Source Type: [http] Detected, Fetching Source: ", source), level.debug)
		resp, err := Get(source)
		if err != nil {
			return "", err
		}
		return resp, nil
	case "s3":
		Log(fmt.Sprintln("Source Type: [s3] Detected, Fetching Source: ", source), level.debug)
		resp, err := S3Read(source)
		if err != nil {
			return "", err
		}
		return resp, nil
	case "lambda":
		Log(fmt.Sprintln("Source Type: [lambda] Detected, Fetching Source: ", source), level.debug)
		lambdaSrc := strings.Split(strings.Replace(source, "lambda:", "", -1), "@")

		var raw interface{}
		if err := json.Unmarshal([]byte(lambdaSrc[0]), &raw); err != nil {
			return "", err
		}

		event, err := json.Marshal(raw)
		if err != nil {
			return "", err
		}

		reg, err := regexp.Compile("[^A-Za-z0-9_-]+")
		if err != nil {
			return "", err
		}

		lambdaName := reg.ReplaceAllString(lambdaSrc[1], "")

		f := awsLambda{
			name:    lambdaName,
			payload: event,
		}

		// using default profile
		sess := manager.sessions[run.profile]
		if err := f.Invoke(sess); err != nil {
			return "", err
		}

		return f.response, nil

	default:
		Log(fmt.Sprintln("Source Type: [file] Detected, Fetching Source: ", source), level.debug)
		b, err := ioutil.ReadFile(source)
		if err != nil {
			return "", err
		}
		return string(b), nil
	}
}

// getName  - Checks if arg is url or file and returns stack name and filepath/url
func getSource(src string) (string, string, error) {

	vals := strings.Split(src, "::")
	if len(vals) < 2 {
		return "", "", errors.New(`Error, invalid format - Usage: stackname::http://someurl OR stackname::path/to/template`)
	}

	return vals[0], vals[1], nil
}
<commit_msg>updated fetchContent to support git-deploy<commit_after>package commands

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

// global variables
var (
	region  string
	project string
	stacks  map[string]*stack
)

// fetchContent - checks the source type, url/s3/file and calls the corresponding function
func fetchContent(source string) (string, error) {
	switch strings.Split(strings.ToLower(source), ":")[0] {
	case "http", "https":
		Log(fmt.Sprintln("Source Type: [http] Detected, Fetching Source: ", source), level.debug)
		resp, err := Get(source)
		if err != nil {
			return "", err
		}
		return resp, nil
	case "s3":
		Log(fmt.Sprintln("Source Type: [s3] Detected, Fetching Source: ", source), level.debug)
		resp, err := S3Read(source)
		if err != nil {
			return "", err
		}
		return resp, nil
	case "lambda":
		Log(fmt.Sprintln("Source Type: [lambda] Detected, Fetching Source: ", source), level.debug)
		lambdaSrc := strings.Split(strings.Replace(source, "lambda:", "", -1), "@")

		var raw interface{}
		if err := json.Unmarshal([]byte(lambdaSrc[0]), &raw); err != nil {
			return "", err
		}

		event, err := json.Marshal(raw)
		if err != nil {
			return "", err
		}

		reg, err := regexp.Compile("[^A-Za-z0-9_-]+")
		if err != nil {
			return "", err
		}

		lambdaName := reg.ReplaceAllString(lambdaSrc[1], "")

		f := awsLambda{
			name:    lambdaName,
			payload: event,
		}

		// using default profile
		sess := manager.sessions[run.profile]
		if err := f.Invoke(sess); err != nil {
			return "", err
		}

		return f.response, nil

	default:
		if gitrepo.URL != "" {
			Log(fmt.Sprintln("Source Type: [git-repo file] Detected, Fetching Source: ", source), level.debug)
			out, ok := gitrepo.files[source]
			if !ok {
				return "", fmt.Errorf("Source file [%s] not found in Repo", source)
			}

			return out, nil
		}

		Log(fmt.Sprintln("Source Type: [file] Detected, Fetching Source: ", source), level.debug)
		b, err := ioutil.ReadFile(source)
		if err != nil {
			return "", err
		}
		return string(b), nil
	}
}

// getName  - Checks if arg is url or file and returns stack name and filepath/url
func getSource(src string) (string, string, error) {

	vals := strings.Split(src, "::")
	if len(vals) < 2 {
		return "", "", errors.New(`Error, invalid format - Usage: stackname::http://someurl OR stackname::path/to/template`)
	}

	return vals[0], vals[1], nil
}
<|endoftext|>


<commit_before>package commands

import (
	"log"
	"os"
	"runtime"

	"github.com/codegangsta/cli"
	api "github.com/yungsang/dockerclient"
	"github.com/yungsang/tablewriter"
	"github.com/yungsang/talk2docker/client"
	"github.com/yungsang/talk2docker/version"
)

func CommandVersion(ctx *cli.Context) {
	var items [][]string

	out := []string{
		"Talk2Docker",
		"v" + ctx.App.Version,
		api.APIVersion,
		runtime.Version(),
		version.GITCOMMIT,
	}
	items = append(items, out)

	var e error

	docker, err := client.GetDockerClient(ctx)
	if err != nil {
		e = err
		goto Display
	}

	{
		version, err := docker.Version()
		if err != nil {
			e = err
			goto Display
		}

		out = []string{
			"Docker Server",
			"v" + version.Version,
			"v" + version.ApiVersion,
			version.GoVersion,
			version.GitCommit,
		}
		items = append(items, out)
	}

Display:
	var header = []string{
		"",
		"Version",
		"API Version",
		"Go Version",
		"Git commit",
	}

	table := tablewriter.NewWriter(os.Stdout)
	table.SetHeader(header)
	table.SetBorder(false)
	table.AppendBulk(items)
	table.Render()

	if e != nil {
		log.Fatal(e)
	}
}
<commit_msg>Fix an alignment bug<commit_after>package commands

import (
	"log"
	"os"
	"runtime"

	"github.com/codegangsta/cli"
	api "github.com/yungsang/dockerclient"
	"github.com/yungsang/tablewriter"
	"github.com/yungsang/talk2docker/client"
	"github.com/yungsang/talk2docker/version"
)

func CommandVersion(ctx *cli.Context) {
	var items [][]string

	out := []string{
		"Talk2Docker",
		"v" + ctx.App.Version,
		api.APIVersion,
		runtime.Version(),
		version.GITCOMMIT,
	}
	items = append(items, out)

	var e error

	docker, err := client.GetDockerClient(ctx)
	if err != nil {
		e = err
		goto Display
	}

	{
		version, err := docker.Version()
		if err != nil {
			e = err
			goto Display
		}

		out = []string{
			"Docker Server",
			"v" + version.Version,
			"v" + version.ApiVersion,
			version.GoVersion,
			version.GitCommit,
		}
		items = append(items, out)
	}

Display:
	var header = []string{
		"",
		"Version",
		"API Version",
		"Go Version",
		"Git commit",
	}

	table := tablewriter.NewWriter(os.Stdout)
	table.SetHeader(header)
	table.SetBorder(false)
	table.SetAlignment(tablewriter.ALIGN_LEFT)
	table.AppendBulk(items)
	table.Render()

	if e != nil {
		log.Fatal(e)
	}
}
<|endoftext|>


<commit_before><commit_msg>Add support for opening a db with TTL support<commit_after><|endoftext|>


<commit_before>package peerstore

import (
	"context"
	"time"

	"github.com/libp2p/go-libp2p-peer"
	ma "github.com/multiformats/go-multiaddr"
	"github.com/dgraph-io/badger"
	"github.com/multiformats/go-multihash"
	"encoding/gob"
	"bytes"
)

type BadgerAddrManager struct {
	DB *badger.DB
	addrSubs map[peer.ID][]*addrSub
}

type addrentry struct {
	Addr []byte
	TTL time.Duration
}

func (mgr *BadgerAddrManager) sendSubscriptionUpdates(p *peer.ID, addrs []ma.Multiaddr) {
	subs := mgr.addrSubs[*p]
	for _, sub := range subs {
		for _, addr := range addrs {
			sub.pubAddr(addr)
		}
	}
}

func (mgr *BadgerAddrManager) Close() {
	if err := mgr.DB.Close(); err != nil {
		log.Error(err)
	}
}

func NewBadgerAddrManager(dataPath string) (*BadgerAddrManager, error) {
	opts := badger.DefaultOptions
	opts.Dir = dataPath
	opts.ValueDir = dataPath
	db, err := badger.Open(opts)
	if err != nil {
		return nil, err
	}
	return &BadgerAddrManager{DB: db}, nil
}

func (mgr *BadgerAddrManager) AddAddr(p peer.ID, addr ma.Multiaddr, ttl time.Duration) {
	mgr.AddAddrs(p, []ma.Multiaddr{addr}, ttl)
}

// use murmur3 because it's the most compact
func hashMultiaddr(addr *ma.Multiaddr) ([]byte, error) {
	return multihash.Encode((*addr).Bytes(), multihash.MURMUR3)
}

func createUniqueKey(p *peer.ID, addr *ma.Multiaddr) ([]byte, error) {
	prefix := []byte(*p)
	addrHash, err := hashMultiaddr(addr)
	if err != nil {
		return nil, err
	}

	return append(prefix, addrHash...), nil
}

func addAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Duration, txn *badger.Txn) {
	for _, addr := range addrs {
		if addr == nil {
			continue
		}
		entry := &addrentry{Addr: addr.Bytes(), TTL: ttl}
		key, err := createUniqueKey(&p, &addr)
		if err != nil {
			log.Error(err)
			txn.Discard()
			return
		}
		buf := &bytes.Buffer{}
		enc := gob.NewEncoder(buf)
		if err := enc.Encode(entry); err != nil {
			log.Error(err)
			txn.Discard()
			return
		}
		txn.SetWithTTL(key, buf.Bytes(), ttl)
	}
}

func (mgr *BadgerAddrManager) AddAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Duration) {
	if ttl <= 0 {
		log.Debugf("short circuiting AddAddrs with ttl %d", ttl)
		return
	}

	txn := mgr.DB.NewTransaction(true)
	defer txn.Discard()

	go mgr.sendSubscriptionUpdates(&p, addrs)
	addAddrs(p, addrs, ttl, txn)

	txn.Commit(nil)
}

func (mgr *BadgerAddrManager) SetAddr(p peer.ID, addr ma.Multiaddr, ttl time.Duration) {
	mgr.SetAddrs(p, []ma.Multiaddr{addr}, ttl)
}

func (mgr *BadgerAddrManager) SetAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Duration) {
	txn := mgr.DB.NewTransaction(true)
	defer txn.Discard()

	for _, addr := range addrs {
		if addr == nil {
			continue
		}
		key, err := createUniqueKey(&p, &addr)
		if err != nil {
			log.Error(err)
			continue
		}
		if ttl <= 0 {
			if err := txn.Delete(key); err != nil {
				log.Error(err)
			}
		} else {
			entry := &addrentry{Addr: addr.Bytes(), TTL: ttl}
			buf := &bytes.Buffer{}
			enc := gob.NewEncoder(buf)
			if err := enc.Encode(entry); err != nil {
				log.Error(err)
				continue
			}
			txn.SetWithTTL(key, buf.Bytes(), ttl)
		}
	}

	txn.Commit(nil)
}

func (mgr *BadgerAddrManager) UpdateAddrs(p peer.ID, oldTTL time.Duration, newTTL time.Duration) {
	prefix := []byte(p)
	txn := mgr.DB.NewTransaction(true)
	defer txn.Discard()
	opts := badger.DefaultIteratorOptions
	iter := txn.NewIterator(opts)

	iter.Seek(prefix)
	for iter.ValidForPrefix(prefix) {
		item := iter.Item()
		addrbytes, err := item.Value()
		if err != nil {
			log.Error(err)
			return
		}
		entry := &addrentry{}
		buf := bytes.NewBuffer(addrbytes)
		dec := gob.NewDecoder(buf)
		if err := dec.Decode(&entry); err != nil {
			log.Error(err)
			return
		}
		if entry.TTL == oldTTL {
			entry.TTL = newTTL
			buf := &bytes.Buffer{}
			enc := gob.NewEncoder(buf)
			if err := enc.Encode(&entry); err != nil {
				log.Error(err)
				return
			}
			txn.SetWithTTL(item.Key(), buf.Bytes(), newTTL)
		}

		iter.Next()
	}

	txn.Commit(nil)
}

func (mgr *BadgerAddrManager) Addrs(p peer.ID) []ma.Multiaddr {
	txn := mgr.DB.NewTransaction(false)
	defer txn.Discard()

	prefix := []byte(p)
	opts := badger.DefaultIteratorOptions
	iter := txn.NewIterator(opts)
	iter.Seek(prefix)

	var addrs []ma.Multiaddr

	for iter.ValidForPrefix(prefix) {
		item := iter.Item()

		if !item.IsDeletedOrExpired() {
			value, err := item.Value()
			if err != nil {
				log.Error(err)
			} else {
				entry := &addrentry{}
				buf := bytes.NewBuffer(value)
				dec := gob.NewDecoder(buf)
				if err := dec.Decode(&entry); err != nil {
					log.Error("deleting bad entry in peerstore for peer", p.String())
					txn.Delete(item.Key())
				} else {
					addrs = append(addrs, ma.Cast(entry.Addr))
				}
			}
		}

		iter.Next()
	}

	txn.Commit(nil)

	return addrs
}

func (mgr *BadgerAddrManager) AddrStream(ctx context.Context, p peer.ID) <-chan ma.Multiaddr {
	addrs := make(chan ma.Multiaddr)

	// TODO: impl

	return addrs
}

func (mgr *BadgerAddrManager) ClearAddrs(p peer.ID) {
	txn := mgr.DB.NewTransaction(true)
	defer txn.Discard()
	it := txn.NewIterator(badger.DefaultIteratorOptions)
	prefix := []byte(p)
	it.Seek(prefix)

	count := 0
	for it.ValidForPrefix(prefix) {
		count++
		if err := txn.Delete(it.Item().Key()); err != nil {
			log.Error(err)
		}
		it.Next()
	}
	txn.Commit(nil)
}
<commit_msg>Break nesting via continues<commit_after>package peerstore

import (
	"context"
	"time"

	"bytes"
	"encoding/gob"
	"github.com/dgraph-io/badger"
	"github.com/libp2p/go-libp2p-peer"
	ma "github.com/multiformats/go-multiaddr"
	"github.com/multiformats/go-multihash"
)

type BadgerAddrManager struct {
	DB       *badger.DB
	addrSubs map[peer.ID][]*addrSub
}

type addrentry struct {
	Addr []byte
	TTL  time.Duration
}

func (mgr *BadgerAddrManager) sendSubscriptionUpdates(p *peer.ID, addrs []ma.Multiaddr) {
	subs := mgr.addrSubs[*p]
	for _, sub := range subs {
		for _, addr := range addrs {
			sub.pubAddr(addr)
		}
	}
}

func (mgr *BadgerAddrManager) Close() {
	if err := mgr.DB.Close(); err != nil {
		log.Error(err)
	}
}

func NewBadgerAddrManager(dataPath string) (*BadgerAddrManager, error) {
	opts := badger.DefaultOptions
	opts.Dir = dataPath
	opts.ValueDir = dataPath
	db, err := badger.Open(opts)
	if err != nil {
		return nil, err
	}
	return &BadgerAddrManager{DB: db}, nil
}

func (mgr *BadgerAddrManager) AddAddr(p peer.ID, addr ma.Multiaddr, ttl time.Duration) {
	mgr.AddAddrs(p, []ma.Multiaddr{addr}, ttl)
}

// use murmur3 because it's the most compact
func hashMultiaddr(addr *ma.Multiaddr) ([]byte, error) {
	return multihash.Encode((*addr).Bytes(), multihash.MURMUR3)
}

func createUniqueKey(p *peer.ID, addr *ma.Multiaddr) ([]byte, error) {
	prefix := []byte(*p)
	addrHash, err := hashMultiaddr(addr)
	if err != nil {
		return nil, err
	}

	return append(prefix, addrHash...), nil
}

func addAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Duration, txn *badger.Txn) {
	for _, addr := range addrs {
		if addr == nil {
			continue
		}
		entry := &addrentry{Addr: addr.Bytes(), TTL: ttl}
		key, err := createUniqueKey(&p, &addr)
		if err != nil {
			log.Error(err)
			txn.Discard()
			return
		}
		buf := &bytes.Buffer{}
		enc := gob.NewEncoder(buf)
		if err := enc.Encode(entry); err != nil {
			log.Error(err)
			txn.Discard()
			return
		}
		txn.SetWithTTL(key, buf.Bytes(), ttl)
	}
}

func (mgr *BadgerAddrManager) AddAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Duration) {
	if ttl <= 0 {
		log.Debugf("short circuiting AddAddrs with ttl %d", ttl)
		return
	}

	txn := mgr.DB.NewTransaction(true)
	defer txn.Discard()

	go mgr.sendSubscriptionUpdates(&p, addrs)
	addAddrs(p, addrs, ttl, txn)

	txn.Commit(nil)
}

func (mgr *BadgerAddrManager) SetAddr(p peer.ID, addr ma.Multiaddr, ttl time.Duration) {
	mgr.SetAddrs(p, []ma.Multiaddr{addr}, ttl)
}

func (mgr *BadgerAddrManager) SetAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Duration) {
	txn := mgr.DB.NewTransaction(true)
	defer txn.Discard()

	for _, addr := range addrs {
		if addr == nil {
			continue
		}
		key, err := createUniqueKey(&p, &addr)
		if err != nil {
			log.Error(err)
			continue
		}
		if ttl <= 0 {
			if err := txn.Delete(key); err != nil {
				log.Error(err)
				continue
			}
		}
		entry := &addrentry{Addr: addr.Bytes(), TTL: ttl}
		buf := &bytes.Buffer{}
		enc := gob.NewEncoder(buf)
		if err := enc.Encode(entry); err != nil {
			log.Error(err)
			continue
		}
		txn.SetWithTTL(key, buf.Bytes(), ttl)
	}

	txn.Commit(nil)
}

func (mgr *BadgerAddrManager) UpdateAddrs(p peer.ID, oldTTL time.Duration, newTTL time.Duration) {
	prefix := []byte(p)
	txn := mgr.DB.NewTransaction(true)
	defer txn.Discard()
	opts := badger.DefaultIteratorOptions
	iter := txn.NewIterator(opts)

	iter.Seek(prefix)
	for iter.ValidForPrefix(prefix) {
		item := iter.Item()
		addrbytes, err := item.Value()
		if err != nil {
			log.Error(err)
			return
		}
		entry := &addrentry{}
		buf := bytes.NewBuffer(addrbytes)
		dec := gob.NewDecoder(buf)
		if err := dec.Decode(&entry); err != nil {
			log.Error(err)
			return
		}
		if entry.TTL == oldTTL {
			entry.TTL = newTTL
			buf := &bytes.Buffer{}
			enc := gob.NewEncoder(buf)
			if err := enc.Encode(&entry); err != nil {
				log.Error(err)
				return
			}
			txn.SetWithTTL(item.Key(), buf.Bytes(), newTTL)
		}

		iter.Next()
	}

	txn.Commit(nil)
}

func (mgr *BadgerAddrManager) Addrs(p peer.ID) []ma.Multiaddr {
	txn := mgr.DB.NewTransaction(false)
	defer txn.Discard()

	prefix := []byte(p)
	opts := badger.DefaultIteratorOptions
	iter := txn.NewIterator(opts)
	iter.Seek(prefix)

	var addrs []ma.Multiaddr

	for ; iter.ValidForPrefix(prefix); iter.Next() {
		item := iter.Item()
		if item.IsDeletedOrExpired() {
			continue
		}

		value, err := item.Value()
		if err != nil {
			log.Error(err)
			continue
		}
		entry := &addrentry{}
		buf := bytes.NewBuffer(value)
		dec := gob.NewDecoder(buf)
		if err := dec.Decode(&entry); err != nil {
			log.Error("deleting bad entry in peerstore for peer", p.String())
			txn.Delete(item.Key())
		} else {
			addrs = append(addrs, ma.Cast(entry.Addr))
		}
	}

	txn.Commit(nil)

	return addrs
}

func (mgr *BadgerAddrManager) AddrStream(ctx context.Context, p peer.ID) <-chan ma.Multiaddr {
	addrs := make(chan ma.Multiaddr)

	// TODO: impl

	return addrs
}

func (mgr *BadgerAddrManager) ClearAddrs(p peer.ID) {
	txn := mgr.DB.NewTransaction(true)
	defer txn.Discard()
	it := txn.NewIterator(badger.DefaultIteratorOptions)
	prefix := []byte(p)
	it.Seek(prefix)

	count := 0
	for it.ValidForPrefix(prefix) {
		count++
		if err := txn.Delete(it.Item().Key()); err != nil {
			log.Error(err)
		}
		it.Next()
	}
	txn.Commit(nil)
}
<|endoftext|>


<commit_before>// Package components contains additional components which can be mounted in a
// fire application.
package components

import (
	"fmt"
	"strings"

	"github.com/gonfire/fire"
	"github.com/labstack/echo"
	"github.com/labstack/echo/middleware"
)

// A Protector is a component that can be mounted in an application to enforce
// common security concerns.
type Protector struct {
	// BodyLimit defines the maximum size of a request body in the form of 4K,
	// 2M, 1G or 1P.
	BodyLimit string

	// AllowMethodOverriding will allow the usage of the X-HTTP-Method-Override
	// header to set a request method when using the POST method.
	AllowMethodOverriding bool

	// AllowedCORSOrigins specifies the allowed origins when CORS.
	AllowedCORSOrigins []string

	// AllowedCORSHeaders specifies the allowed headers when CORS.
	AllowedCORSHeaders []string

	// AllowedCORSMethods specifies the allowed methods when CORS.
	AllowedCORSMethods []string
}

// DefaultProtector returns a protector that is tailored to be used for JSON APIs.
func DefaultProtector() *Protector {
	return &Protector{
		BodyLimit:             "4K",
		AllowMethodOverriding: false,
		AllowedCORSOrigins: []string{
			"*",
		},
		AllowedCORSHeaders: []string{
			echo.HeaderOrigin,
			echo.HeaderContentType,
			echo.HeaderAuthorization,
		},
		AllowedCORSMethods: []string{
			echo.GET,
			echo.POST,
			echo.PATCH,
			echo.DELETE,
		},
	}
}

// Register will register the protector on the passed echo router.
func (p *Protector) Register(router *echo.Echo) {
	// set body limit
	router.Use(middleware.BodyLimit(p.BodyLimit))

	// add gzip compression
	router.Use(middleware.Gzip())

	// enable method overriding
	if p.AllowMethodOverriding {
		router.Pre(middleware.MethodOverride())
	}

	// prepare allowed cors headers
	allowedHeaders := p.AllowedCORSHeaders

	// add method override header if enabled
	if p.AllowMethodOverriding {
		allowedHeaders = append(allowedHeaders, echo.HeaderXHTTPMethodOverride)
	}

	// add cors middleware
	router.Use(middleware.CORSWithConfig(middleware.CORSConfig{
		AllowOrigins: p.AllowedCORSOrigins,
		AllowMethods: p.AllowedCORSMethods,
		AllowHeaders: allowedHeaders,
		MaxAge:       60,
	}))

	// prepare secure config
	config := middleware.DefaultSecureConfig
	config.XFrameOptions = ""

	// TODO: Configure HSTS header.
	// TODO: Force SSL by redirection.

	// add the secure middleware
	router.Use(middleware.SecureWithConfig(config))
}

// Inspect implements the InspectableComponent interface.
func (p *Protector) Inspect() fire.ComponentInfo {
	return fire.ComponentInfo{
		Name: "Protector",
		Settings: fire.Map{
			"Body Limit":              p.BodyLimit,
			"Allow Method Overriding": fmt.Sprintf("%v", p.AllowMethodOverriding),
			"Allowed CORS Origins":    strings.Join(p.AllowedCORSOrigins, ", "),
			"Allowed CORS Methods":    strings.Join(p.AllowedCORSMethods, ", "),
			"Allowed CORS Headers":    strings.Join(p.AllowedCORSHeaders, ", "),
		},
	}
}
<commit_msg>renamed property<commit_after>// Package components contains additional components which can be mounted in a
// fire application.
package components

import (
	"fmt"
	"strings"

	"github.com/gonfire/fire"
	"github.com/labstack/echo"
	"github.com/labstack/echo/middleware"
)

// A Protector is a component that can be mounted in an application to enforce
// common security concerns.
type Protector struct {
	// RequestBodyLimit defines the maximum size of a request body in the form
	// of 4K, 2M, 1G or 1P.
	RequestBodyLimit string

	// AllowMethodOverriding will allow the usage of the X-HTTP-Method-Override
	// header to set a request method when using the POST method.
	AllowMethodOverriding bool

	// AllowedCORSOrigins specifies the allowed origins when CORS.
	AllowedCORSOrigins []string

	// AllowedCORSHeaders specifies the allowed headers when CORS.
	AllowedCORSHeaders []string

	// AllowedCORSMethods specifies the allowed methods when CORS.
	AllowedCORSMethods []string
}

// DefaultProtector returns a protector that is tailored to be used for JSON APIs.
func DefaultProtector() *Protector {
	return &Protector{
		RequestBodyLimit:      "4K",
		AllowMethodOverriding: false,
		AllowedCORSOrigins: []string{
			"*",
		},
		AllowedCORSHeaders: []string{
			echo.HeaderOrigin,
			echo.HeaderContentType,
			echo.HeaderAuthorization,
		},
		AllowedCORSMethods: []string{
			echo.GET,
			echo.POST,
			echo.PATCH,
			echo.DELETE,
		},
	}
}

// Register will register the protector on the passed echo router.
func (p *Protector) Register(router *echo.Echo) {
	// set body limit
	router.Use(middleware.BodyLimit(p.RequestBodyLimit))

	// add gzip compression
	router.Use(middleware.Gzip())

	// enable method overriding
	if p.AllowMethodOverriding {
		router.Pre(middleware.MethodOverride())
	}

	// prepare allowed cors headers
	allowedHeaders := p.AllowedCORSHeaders

	// add method override header if enabled
	if p.AllowMethodOverriding {
		allowedHeaders = append(allowedHeaders, echo.HeaderXHTTPMethodOverride)
	}

	// add cors middleware
	router.Use(middleware.CORSWithConfig(middleware.CORSConfig{
		AllowOrigins: p.AllowedCORSOrigins,
		AllowMethods: p.AllowedCORSMethods,
		AllowHeaders: allowedHeaders,
		MaxAge:       60,
	}))

	// prepare secure config
	config := middleware.DefaultSecureConfig
	config.XFrameOptions = ""

	// TODO: Configure HSTS header.
	// TODO: Force SSL by redirection.

	// add the secure middleware
	router.Use(middleware.SecureWithConfig(config))
}

// Inspect implements the InspectableComponent interface.
func (p *Protector) Inspect() fire.ComponentInfo {
	return fire.ComponentInfo{
		Name: "Protector",
		Settings: fire.Map{
			"Request Body Limit":      p.RequestBodyLimit,
			"Allow Method Overriding": fmt.Sprintf("%v", p.AllowMethodOverriding),
			"Allowed CORS Origins":    strings.Join(p.AllowedCORSOrigins, ", "),
			"Allowed CORS Methods":    strings.Join(p.AllowedCORSMethods, ", "),
			"Allowed CORS Headers":    strings.Join(p.AllowedCORSHeaders, ", "),
		},
	}
}
<|endoftext|>


<commit_before>package configuration

import (
	"time"
)

type DeviceConfig struct {
	Host           string
	Type           string
	Target         string
	User           string
	Pass           string
	Port           int
	Timeout        time.Duration
	CommandTimeout time.Duration
	Config         map[string]string
}

type SplendidConfig struct {
	Debug         bool
	Interval      time.Duration
	Timeout       time.Duration
	GitPush       bool
	Insecure      bool
	Concurrency   int
	HttpListen    string
	HttpEnabled   bool
	SmtpString    string
	Workspace     string
	ExecutableDir string
	ToEmail       string
	FromEmail     string
	UseSyslog     bool
	DefaultUser   string
	DefaultPass   string
	DefaultMethod string
	CmwPass       string
	Devices       []DeviceConfig
	ConfigFile    string
}

// GetConfigs loads the config file, then parses flags
func GetConfigs(configFile string) (*SplendidConfig, *DeviceConfig, error) {
	conf, dconf, err := loadConfig(configFile)
	if err != nil {
		return nil, nil, err
	}
	parseConfigFlags(conf)

	return conf, dconf, nil
}
<commit_msg>chicken/egg with config file solved<commit_after>package configuration

import (
	"time"
	"reflect"
)

type DeviceConfig struct {
	Host           string
	Type           string
	Target         string
	User           string
	Pass           string
	Port           int
	Timeout        time.Duration
	CommandTimeout time.Duration
	Config         map[string]string
}

type SplendidConfig struct {
	Debug         bool
	Interval      time.Duration
	Timeout       time.Duration
	GitPush       bool
	Insecure      bool
	Concurrency   int
	HttpListen    string
	HttpEnabled   bool
	SmtpString    string
	Workspace     string
	ExecutableDir string
	ToEmail       string
	FromEmail     string
	UseSyslog     bool
	DefaultUser   string
	DefaultPass   string
	DefaultMethod string
	CmwPass       string
	Devices       []DeviceConfig
	ConfigFile    string
}

// GetConfigs loads the config file, then parses flags
func GetConfigs(configFile string) (*SplendidConfig, *DeviceConfig, error) {
	c, dconf, err := loadConfig(configFile)
	if err != nil {
		return nil, nil, err
	}
	flags, err := parseConfigFlags(c)
	conf := c.flagUpdate(*flags)
	return &conf, dconf, nil
}

func (c SplendidConfig) flagUpdate(f SplendidConfig) (conf SplendidConfig) {
	old := reflect.ValueOf(c)
	new := reflect.ValueOf(f)
	final := reflect.ValueOf(&conf).Elem()
	for i := 0; i < old.NumField(); i++ {
		if !new.Field(i).IsValid() {
			final.Field(i).Set(new.Field(i))
		} else {
			final.Field(i).Set(old.Field(i))
		}
	}
	return
}<|endoftext|>


<commit_before>package configuration

import (
	"fmt"
	"os"
	"reflect"
	"sort"
	"strconv"
	"strings"

	"github.com/sirupsen/logrus"
	"gopkg.in/yaml.v2"
)

// Version is a major/minor version pair of the form Major.Minor
// Major version upgrades indicate structure or type changes
// Minor version upgrades should be strictly additive
type Version string

// MajorMinorVersion constructs a Version from its Major and Minor components
func MajorMinorVersion(major, minor uint) Version {
	return Version(fmt.Sprintf("%d.%d", major, minor))
}

func (version Version) major() (uint, error) {
	majorPart := strings.Split(string(version), ".")[0]
	major, err := strconv.ParseUint(majorPart, 10, 0)
	return uint(major), err
}

// Major returns the major version portion of a Version
func (version Version) Major() uint {
	major, _ := version.major()
	return major
}

func (version Version) minor() (uint, error) {
	minorPart := strings.Split(string(version), ".")[1]
	minor, err := strconv.ParseUint(minorPart, 10, 0)
	return uint(minor), err
}

// Minor returns the minor version portion of a Version
func (version Version) Minor() uint {
	minor, _ := version.minor()
	return minor
}

// VersionedParseInfo defines how a specific version of a configuration should
// be parsed into the current version
type VersionedParseInfo struct {
	// Version is the version which this parsing information relates to
	Version Version
	// ParseAs defines the type which a configuration file of this version
	// should be parsed into
	ParseAs reflect.Type
	// ConversionFunc defines a method for converting the parsed configuration
	// (of type ParseAs) into the current configuration version
	// Note: this method signature is very unclear with the absence of generics
	ConversionFunc func(interface{}) (interface{}, error)
}

type envVar struct {
	name  string
	value string
}

type envVars []envVar

func (a envVars) Len() int           { return len(a) }
func (a envVars) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a envVars) Less(i, j int) bool { return a[i].name < a[j].name }

// Parser can be used to parse a configuration file and environment of a defined
// version into a unified output structure
type Parser struct {
	prefix  string
	mapping map[Version]VersionedParseInfo
	env     envVars
}

// NewParser returns a *Parser with the given environment prefix which handles
// versioned configurations which match the given parseInfos
func NewParser(prefix string, parseInfos []VersionedParseInfo) *Parser {
	p := Parser{prefix: prefix, mapping: make(map[Version]VersionedParseInfo)}

	for _, parseInfo := range parseInfos {
		p.mapping[parseInfo.Version] = parseInfo
	}

	for _, env := range os.Environ() {
		envParts := strings.SplitN(env, "=", 2)
		p.env = append(p.env, envVar{envParts[0], envParts[1]})
	}

	// We must sort the environment variables lexically by name so that
	// more specific variables are applied before less specific ones
	// (i.e. REGISTRY_STORAGE before
	// REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY). This sucks, but it's a
	// lot simpler and easier to get right than unmarshalling map entries
	// into temporaries and merging with the existing entry.
	sort.Sort(p.env)

	return &p
}

// Parse reads in the given []byte and environment and writes the resulting
// configuration into the input v
//
// Environment variables may be used to override configuration parameters other
// than version, following the scheme below:
// v.Abc may be replaced by the value of PREFIX_ABC,
// v.Abc.Xyz may be replaced by the value of PREFIX_ABC_XYZ, and so forth
func (p *Parser) Parse(in []byte, v interface{}) error {
	var versionedStruct struct {
		Version Version
	}

	if err := yaml.Unmarshal(in, &versionedStruct); err != nil {
		return err
	}

	parseInfo, ok := p.mapping[versionedStruct.Version]
	if !ok {
		return fmt.Errorf("unsupported version: %q", versionedStruct.Version)
	}

	parseAs := reflect.New(parseInfo.ParseAs)
	err := yaml.Unmarshal(in, parseAs.Interface())
	if err != nil {
		return err
	}

	for _, envVar := range p.env {
		pathStr := envVar.name
		if strings.HasPrefix(pathStr, strings.ToUpper(p.prefix)+"_") {
			path := strings.Split(pathStr, "_")

			err = p.overwriteFields(parseAs, pathStr, path[1:], envVar.value)
			if err != nil {
				return err
			}
		}
	}

	c, err := parseInfo.ConversionFunc(parseAs.Interface())
	if err != nil {
		return err
	}
	reflect.ValueOf(v).Elem().Set(reflect.Indirect(reflect.ValueOf(c)))
	return nil
}

// overwriteFields replaces configuration values with alternate values specified
// through the environment. Precondition: an empty path slice must never be
// passed in.
func (p *Parser) overwriteFields(v reflect.Value, fullpath string, path []string, payload string) error {
	for v.Kind() == reflect.Ptr {
		if v.IsNil() {
			panic("encountered nil pointer while handling environment variable " + fullpath)
		}
		v = reflect.Indirect(v)
	}
	switch v.Kind() {
	case reflect.Struct:
		return p.overwriteStruct(v, fullpath, path, payload)
	case reflect.Map:
		return p.overwriteMap(v, fullpath, path, payload)
	case reflect.Interface:
		if v.NumMethod() == 0 {
			if !v.IsNil() {
				return p.overwriteFields(v.Elem(), fullpath, path, payload)
			}
			// Interface was empty; create an implicit map
			var template map[string]interface{}
			wrappedV := reflect.MakeMap(reflect.TypeOf(template))
			v.Set(wrappedV)
			return p.overwriteMap(wrappedV, fullpath, path, payload)
		}
	}
	return nil
}

func (p *Parser) overwriteStruct(v reflect.Value, fullpath string, path []string, payload string) error {
	// Generate case-insensitive map of struct fields
	byUpperCase := make(map[string]int)
	for i := 0; i < v.NumField(); i++ {
		sf := v.Type().Field(i)
		upper := strings.ToUpper(sf.Name)
		if _, present := byUpperCase[upper]; present {
			panic(fmt.Sprintf("field name collision in configuration object: %s", sf.Name))
		}
		byUpperCase[upper] = i
	}

	fieldIndex, present := byUpperCase[path[0]]
	if !present {
		logrus.Warnf("Ignoring unrecognized environment variable %s", fullpath)
		return nil
	}
	field := v.Field(fieldIndex)
	sf := v.Type().Field(fieldIndex)

	if len(path) == 1 {
		// Env var specifies this field directly
		fieldVal := reflect.New(sf.Type)
		err := yaml.Unmarshal([]byte(payload), fieldVal.Interface())
		if err != nil {
			logrus.Warnf("Error parsing environment variable %s", fullpath)
			return err
		}
		field.Set(reflect.Indirect(fieldVal))
		return nil
	}

	// If the field is nil, must create an object
	switch sf.Type.Kind() {
	case reflect.Map:
		if field.IsNil() {
			field.Set(reflect.MakeMap(sf.Type))
		}
	case reflect.Ptr:
		if field.IsNil() {
			field.Set(reflect.New(field.Type().Elem()))
		}
	}

	err := p.overwriteFields(field, fullpath, path[1:], payload)
	if err != nil {
		return err
	}

	return nil
}

func (p *Parser) overwriteMap(m reflect.Value, fullpath string, path []string, payload string) error {
	if m.Type().Key().Kind() != reflect.String {
		// non-string keys unsupported
		logrus.Warnf("Ignoring environment variable %s involving map with non-string keys", fullpath)
		return nil
	}

	if len(path) > 1 {
		// If a matching key exists, get its value and continue the
		// overwriting process.
		for _, k := range m.MapKeys() {
			if strings.ToUpper(k.String()) == path[0] {
				mapValue := m.MapIndex(k)
				// If the existing value is nil, we want to
				// recreate it instead of using this value.
				if (mapValue.Kind() == reflect.Ptr ||
					mapValue.Kind() == reflect.Interface ||
					mapValue.Kind() == reflect.Map) &&
					mapValue.IsNil() {
					break
				}
				return p.overwriteFields(mapValue, fullpath, path[1:], payload)
			}
		}
	}

	// (Re)create this key
	var mapValue reflect.Value
	if m.Type().Elem().Kind() == reflect.Map {
		mapValue = reflect.MakeMap(m.Type().Elem())
	} else {
		mapValue = reflect.New(m.Type().Elem())
	}
	if len(path) > 1 {
		err := p.overwriteFields(mapValue, fullpath, path[1:], payload)
		if err != nil {
			return err
		}
	} else {
		err := yaml.Unmarshal([]byte(payload), mapValue.Interface())
		if err != nil {
			return err
		}
	}

	m.SetMapIndex(reflect.ValueOf(strings.ToLower(path[0])), reflect.Indirect(mapValue))

	return nil
}
<commit_msg>patch-1 - adding more info to the error message<commit_after>package configuration

import (
	"fmt"
	"os"
	"reflect"
	"sort"
	"strconv"
	"strings"

	"github.com/sirupsen/logrus"
	"gopkg.in/yaml.v2"
)

// Version is a major/minor version pair of the form Major.Minor
// Major version upgrades indicate structure or type changes
// Minor version upgrades should be strictly additive
type Version string

// MajorMinorVersion constructs a Version from its Major and Minor components
func MajorMinorVersion(major, minor uint) Version {
	return Version(fmt.Sprintf("%d.%d", major, minor))
}

func (version Version) major() (uint, error) {
	majorPart := strings.Split(string(version), ".")[0]
	major, err := strconv.ParseUint(majorPart, 10, 0)
	return uint(major), err
}

// Major returns the major version portion of a Version
func (version Version) Major() uint {
	major, _ := version.major()
	return major
}

func (version Version) minor() (uint, error) {
	minorPart := strings.Split(string(version), ".")[1]
	minor, err := strconv.ParseUint(minorPart, 10, 0)
	return uint(minor), err
}

// Minor returns the minor version portion of a Version
func (version Version) Minor() uint {
	minor, _ := version.minor()
	return minor
}

// VersionedParseInfo defines how a specific version of a configuration should
// be parsed into the current version
type VersionedParseInfo struct {
	// Version is the version which this parsing information relates to
	Version Version
	// ParseAs defines the type which a configuration file of this version
	// should be parsed into
	ParseAs reflect.Type
	// ConversionFunc defines a method for converting the parsed configuration
	// (of type ParseAs) into the current configuration version
	// Note: this method signature is very unclear with the absence of generics
	ConversionFunc func(interface{}) (interface{}, error)
}

type envVar struct {
	name  string
	value string
}

type envVars []envVar

func (a envVars) Len() int           { return len(a) }
func (a envVars) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a envVars) Less(i, j int) bool { return a[i].name < a[j].name }

// Parser can be used to parse a configuration file and environment of a defined
// version into a unified output structure
type Parser struct {
	prefix  string
	mapping map[Version]VersionedParseInfo
	env     envVars
}

// NewParser returns a *Parser with the given environment prefix which handles
// versioned configurations which match the given parseInfos
func NewParser(prefix string, parseInfos []VersionedParseInfo) *Parser {
	p := Parser{prefix: prefix, mapping: make(map[Version]VersionedParseInfo)}

	for _, parseInfo := range parseInfos {
		p.mapping[parseInfo.Version] = parseInfo
	}

	for _, env := range os.Environ() {
		envParts := strings.SplitN(env, "=", 2)
		p.env = append(p.env, envVar{envParts[0], envParts[1]})
	}

	// We must sort the environment variables lexically by name so that
	// more specific variables are applied before less specific ones
	// (i.e. REGISTRY_STORAGE before
	// REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY). This sucks, but it's a
	// lot simpler and easier to get right than unmarshalling map entries
	// into temporaries and merging with the existing entry.
	sort.Sort(p.env)

	return &p
}

// Parse reads in the given []byte and environment and writes the resulting
// configuration into the input v
//
// Environment variables may be used to override configuration parameters other
// than version, following the scheme below:
// v.Abc may be replaced by the value of PREFIX_ABC,
// v.Abc.Xyz may be replaced by the value of PREFIX_ABC_XYZ, and so forth
func (p *Parser) Parse(in []byte, v interface{}) error {
	var versionedStruct struct {
		Version Version
	}

	if err := yaml.Unmarshal(in, &versionedStruct); err != nil {
		return err
	}

	parseInfo, ok := p.mapping[versionedStruct.Version]
	if !ok {
		return fmt.Errorf("unsupported version: %q", versionedStruct.Version)
	}

	parseAs := reflect.New(parseInfo.ParseAs)
	err := yaml.Unmarshal(in, parseAs.Interface())
	if err != nil {
		return err
	}

	for _, envVar := range p.env {
		pathStr := envVar.name
		if strings.HasPrefix(pathStr, strings.ToUpper(p.prefix)+"_") {
			path := strings.Split(pathStr, "_")

			err = p.overwriteFields(parseAs, pathStr, path[1:], envVar.value)
			if err != nil {
				return err
			}
		}
	}

	c, err := parseInfo.ConversionFunc(parseAs.Interface())
	if err != nil {
		return err
	}
	reflect.ValueOf(v).Elem().Set(reflect.Indirect(reflect.ValueOf(c)))
	return nil
}

// overwriteFields replaces configuration values with alternate values specified
// through the environment. Precondition: an empty path slice must never be
// passed in.
func (p *Parser) overwriteFields(v reflect.Value, fullpath string, path []string, payload string) error {
	for v.Kind() == reflect.Ptr {
		if v.IsNil() {
			panic("encountered nil pointer while handling environment variable " + fullpath)
		}
		v = reflect.Indirect(v)
	}
	switch v.Kind() {
	case reflect.Struct:
		return p.overwriteStruct(v, fullpath, path, payload)
	case reflect.Map:
		return p.overwriteMap(v, fullpath, path, payload)
	case reflect.Interface:
		if v.NumMethod() == 0 {
			if !v.IsNil() {
				return p.overwriteFields(v.Elem(), fullpath, path, payload)
			}
			// Interface was empty; create an implicit map
			var template map[string]interface{}
			wrappedV := reflect.MakeMap(reflect.TypeOf(template))
			v.Set(wrappedV)
			return p.overwriteMap(wrappedV, fullpath, path, payload)
		}
	}
	return nil
}

func (p *Parser) overwriteStruct(v reflect.Value, fullpath string, path []string, payload string) error {
	// Generate case-insensitive map of struct fields
	byUpperCase := make(map[string]int)
	for i := 0; i < v.NumField(); i++ {
		sf := v.Type().Field(i)
		upper := strings.ToUpper(sf.Name)
		if _, present := byUpperCase[upper]; present {
			panic(fmt.Sprintf("field name collision in configuration object: %s", sf.Name))
		}
		byUpperCase[upper] = i
	}

	fieldIndex, present := byUpperCase[path[0]]
	if !present {
		logrus.Warnf("Ignoring unrecognized environment variable %s", fullpath)
		return nil
	}
	field := v.Field(fieldIndex)
	sf := v.Type().Field(fieldIndex)

	if len(path) == 1 {
		// Env var specifies this field directly
		fieldVal := reflect.New(sf.Type)
		err := yaml.Unmarshal([]byte(payload), fieldVal.Interface())
		if err != nil {
			logrus.Warnf("Error parsing environment variable %s: %s", fullpath, err)
			return err
		}
		field.Set(reflect.Indirect(fieldVal))
		return nil
	}

	// If the field is nil, must create an object
	switch sf.Type.Kind() {
	case reflect.Map:
		if field.IsNil() {
			field.Set(reflect.MakeMap(sf.Type))
		}
	case reflect.Ptr:
		if field.IsNil() {
			field.Set(reflect.New(field.Type().Elem()))
		}
	}

	err := p.overwriteFields(field, fullpath, path[1:], payload)
	if err != nil {
		return err
	}

	return nil
}

func (p *Parser) overwriteMap(m reflect.Value, fullpath string, path []string, payload string) error {
	if m.Type().Key().Kind() != reflect.String {
		// non-string keys unsupported
		logrus.Warnf("Ignoring environment variable %s involving map with non-string keys", fullpath)
		return nil
	}

	if len(path) > 1 {
		// If a matching key exists, get its value and continue the
		// overwriting process.
		for _, k := range m.MapKeys() {
			if strings.ToUpper(k.String()) == path[0] {
				mapValue := m.MapIndex(k)
				// If the existing value is nil, we want to
				// recreate it instead of using this value.
				if (mapValue.Kind() == reflect.Ptr ||
					mapValue.Kind() == reflect.Interface ||
					mapValue.Kind() == reflect.Map) &&
					mapValue.IsNil() {
					break
				}
				return p.overwriteFields(mapValue, fullpath, path[1:], payload)
			}
		}
	}

	// (Re)create this key
	var mapValue reflect.Value
	if m.Type().Elem().Kind() == reflect.Map {
		mapValue = reflect.MakeMap(m.Type().Elem())
	} else {
		mapValue = reflect.New(m.Type().Elem())
	}
	if len(path) > 1 {
		err := p.overwriteFields(mapValue, fullpath, path[1:], payload)
		if err != nil {
			return err
		}
	} else {
		err := yaml.Unmarshal([]byte(payload), mapValue.Interface())
		if err != nil {
			return err
		}
	}

	m.SetMapIndex(reflect.ValueOf(strings.ToLower(path[0])), reflect.Indirect(mapValue))

	return nil
}
<|endoftext|>


<commit_before>// field.go'
package main

const (
	empty = iota
	full
	miss
	hit
)

type Cell struct {
	state  int
	weight float64
}

type Field struct {
	Grid          *[][]Cell
	Length, Width int
}

func NewField(length int, width int) (retField *Field) {
	tCell := make([][]Cell, length*width)
	retField.Grid = &tCell
	retField.Length = length
	retField.Width = width
	return
}
<commit_msg>Began to work on putting ships down<commit_after>// field.go'
package main

const (
	empty = iota
	full
	miss
	hit
)

type Cell struct {
	state  int
	weight float64
}

type Field struct {
	Grid          *[][]Cell
	Length, Width int
}

func NewField(length int, width int) (retField *Field) {
	tCell := make([][]Cell, length*width)
	retField.Grid = &tCell
	retField.Length = length
	retField.Width = width
	return
}

func (Fi *Field) putShips(shs map[string]ship, putter func(*Field, *ship) error) error {
	return nil
}
<|endoftext|>