<commit_before>package udata

//-----------------------------------------------------------------------------
// Package factored import statement:
//-----------------------------------------------------------------------------

import (

	// Stdlib:
	"bytes"
	"compress/gzip"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"regexp"
	"strconv"
	"text/template"

	// Community:
	"github.com/Masterminds/sprig"
	log "github.com/Sirupsen/logrus"
	ct "github.com/coreos/container-linux-config-transpiler/config"
	"github.com/coreos/coreos-cloudinit/config/validate"
)

//-----------------------------------------------------------------------------
// Typedefs:
//-----------------------------------------------------------------------------

// CmdData holds the data used by the udata sub-command
type CmdData struct {
	CmdFlags // Command-line flags
	PostProc // Post-processed data
	intData  // Internal logic data
}

// CmdFlags honored by the udata sub-command
type CmdFlags struct {
	AdminEmail          string   // --admin-email
	CaCertPath          string   // --ca-cert-path
	CalicoIPPool        string   // --calico-ip-pool
	ClusterID           string   // --cluster-id
	ClusterState        string   // --cluster-state
	Domain              string   // --domain
	Ec2Region           string   // --ec2-region
	EtcdToken           string   // --etcd-token
	GzipUdata           bool     // --gzip-udata
	HostID              string   // --host-id
	HostName            string   // --host-name
	IaasProvider        string   // --iaas-provider
	MasterCount         int      // --master-count
	DNSProvider         string   // --dns-provider
	DNSApiKey           string   // --dns-api-key
	Prometheus          bool     // --prometheus
	QuorumCount         int      // --quorum-count
	RexrayEndpointIP    string   // --rexray-endpoint-ip
	RexrayStorageDriver string   // --rexray-storage-driver
	Roles               []string // --roles
	SlackWebhook        string   // --slack-webhook
	SMTPURL             string   // --smtp-url
	StubZones           []string // --stub-zone
}

// PostProc data based on previous flags
type PostProc struct {
	AlertManagers string
	Aliases       []string
	CaCert        string
	KatoState     string
	EtcdEndpoints string
	EtcdServers   string
	HostTCPPorts  []string
	HostUDPPorts  []string
	MesosDNSPort  int
	SMTP
	SystemdUnits []string
	ZkServers    string
}

// SMTP structure
type SMTP struct {
	Host string
	Port string
	User string
	Pass string
}

// Internal logic data
type intData struct {
	fragments fragmentSlice
	services  serviceMap
	template  string
	userData  *bytes.Buffer
}

//-----------------------------------------------------------------------------
// func: readFile
//-----------------------------------------------------------------------------

func readFile(path string) string {
	if _, err := os.Stat(path); err == nil {
		data, err := ioutil.ReadFile(path)
		if err != nil {
			log.WithField("cmd", "udata").Fatal(err)
		}
		return string(data)
	}
	return ""
}

//-----------------------------------------------------------------------------
// func: zkServers
//-----------------------------------------------------------------------------

func zkServers(quorumCount int) (zkServers string) {
	for i := 1; i <= quorumCount; i++ {
		zkServers = zkServers + "quorum-" + strconv.Itoa(i) + ":2181"
		if i != quorumCount {
			zkServers = zkServers + ","
		}
	}
	return
}

//-----------------------------------------------------------------------------
// func: etcdServers
//-----------------------------------------------------------------------------

func etcdServers(quorumCount int) (etcdServers string) {
	for i := 1; i <= quorumCount; i++ {
		etcdServers = etcdServers +
			"quorum-" + strconv.Itoa(i) + "=http://quorum-" + strconv.Itoa(i) + ":2380"
		if i != quorumCount {
			etcdServers = etcdServers + ","
		}
	}
	return
}

//-----------------------------------------------------------------------------
// func: etcdEndpoints
//-----------------------------------------------------------------------------

func etcdEndpoints(quorumCount int) (etcdEndpoints string) {
	for i := 1; i <= quorumCount; i++ {
		etcdEndpoints = etcdEndpoints +
			"http://quorum-" + strconv.Itoa(i) + ":2379"
		if i != quorumCount {
			etcdEndpoints = etcdEndpoints + ","
		}
	}
	return
}

//-----------------------------------------------------------------------------
// func: alertManagers
//-----------------------------------------------------------------------------

func alertManagers(masterCount int) (alertManagers string) {
	for i := 1; i <= masterCount; i++ {
		alertManagers = alertManagers + "http://master-" + strconv.Itoa(i) + ":9093"
		if i != masterCount {
			alertManagers = alertManagers + ","
		}
	}
	return
}

//-----------------------------------------------------------------------------
// func: smtpURLSplit
//-----------------------------------------------------------------------------

func smtpURLSplit(smtpURL string) (smtp SMTP) {
	if smtpURL != "" {
		r, _ := regexp.Compile("^smtp://(.+):(.+)@(.+):(\\d+)$")
		if sub := r.FindStringSubmatch(smtpURL); sub != nil {
			smtp.User = sub[1]
			smtp.Pass = sub[2]
			smtp.Host = sub[3]
			smtp.Port = sub[4]
		} else {
			log.WithFields(log.Fields{"cmd": "udata", "id": smtpURL}).
				Fatal("Invalid SMTP URL format.")
		}
	}
	return
}

//-----------------------------------------------------------------------------
// func: mesosDNSPort
//-----------------------------------------------------------------------------

func mesosDNSPort(roles []string) (mesosDNSPort int) {
	mesosDNSPort = 53
	for _, role := range roles {
		if role == "master" {
			for _, role := range roles {
				if role == "worker" {
					mesosDNSPort = 54
					return
				}
			}
		}
	}
	return
}

//-----------------------------------------------------------------------------
// func: aliases
//-----------------------------------------------------------------------------

func aliases(roles []string, hostName string) (aliases []string) {
	for _, i := range roles {
		if i != hostName {
			aliases = append(aliases, i)
		}
	}
	return
}

//-----------------------------------------------------------------------------
// func: groups
//-----------------------------------------------------------------------------

func groups(prometheus bool) (groups []string) {
	groups = []string{"base"}
	if prometheus {
		groups = append(groups, "insight")
	}
	return
}

//-----------------------------------------------------------------------------
// func: listOfTags
//-----------------------------------------------------------------------------

func (d *CmdData) listOfTags() (tags []string) {

	tags = append(d.Roles, d.IaasProvider)
	tags = append(tags, d.ClusterState)

	if d.CaCert != "" {
		tags = append(tags, "cacert")
	}

	if d.Prometheus {
		tags = append(tags, "prometheus")
	}

	return
}

//-----------------------------------------------------------------------------
// func: composeTemplate
//-----------------------------------------------------------------------------

func (d *CmdData) composeTemplate() {

	// Tags used to filter template fragments:
	tags := d.listOfTags()

	// Apply the filter:
	for _, frag := range d.fragments {
		if frag.anyOf(tags) {
			if frag.noneOf(tags) {
				if frag.allOf(tags) {
					d.template += frag.data
				}
			}
		}
	}
}

//-----------------------------------------------------------------------------
// func: renderTemplate
//-----------------------------------------------------------------------------

func (d *CmdData) renderTemplate() {

	// Template parsing:
	t := template.New("udata").Funcs(sprig.TxtFuncMap())
	t, err := t.Parse(d.template)
	if err != nil {
		log.WithField("cmd", "udata").Fatal(err)
	}

	// Apply parsed template to data object:
	d.userData = bytes.NewBuffer(make([]byte, 0, 65536))
	if err = t.Execute(d.userData, d); err != nil {
		log.WithField("cmd", "udata").Fatal(err)
	}
}

//-----------------------------------------------------------------------------
// func: validateUserData
//-----------------------------------------------------------------------------

func (d *CmdData) validateUserData() {

	errors := []string{}

	report, err := validate.Validate(d.userData.Bytes())
	if err != nil {
		errors = append(errors, fmt.Sprintf("%v", err))
	}
	for _, entry := range report.Entries() {
		errors = append(errors, fmt.Sprintf("%v", entry))
	}
	if len(errors) > 0 {
		log.WithField("cmd", "udata").Fatal(errors)
	}
}

//-----------------------------------------------------------------------------
// func: outputUserData
//-----------------------------------------------------------------------------

func (d *CmdData) outputUserData() {

	if d.GzipUdata {
		log.WithFields(log.Fields{"cmd": "udata", "id": d.HostName + "-" + d.HostID}).
			Info("Generating gzipped cloud-config user data")
		w := gzip.NewWriter(os.Stdout)
		if _, err := d.userData.WriteTo(w); err != nil {
			_ = w.Close()
			log.WithField("cmd", "udata").Fatal(err)
		}
		_ = w.Close()
	} else {
		log.WithField("cmd", "udata").Info("Generating plain text cloud-config user data")
		if _, err := d.userData.WriteTo(os.Stdout); err != nil {
			log.WithField("cmd", "udata").Fatal(err)
		}
	}
}

//-----------------------------------------------------------------------------
// func: CmdRun
//-----------------------------------------------------------------------------

// CmdRun takes data from CmdData and outputs valid CoreOS cloud-config user
// data in YAML format to stdout.
func (d *CmdData) CmdRun() {

	// Variables:
	d.CaCert = readFile(d.CaCertPath)
	d.KatoState = readFile(os.Getenv("HOME") + "/.kato/" + d.ClusterID + ".json")
	d.ZkServers = zkServers(d.QuorumCount)
	d.EtcdServers = etcdServers(d.QuorumCount)
	d.EtcdEndpoints = etcdEndpoints(d.QuorumCount)
	d.AlertManagers = alertManagers(d.MasterCount)
	d.SMTP = smtpURLSplit(d.SMTPURL)
	d.MesosDNSPort = mesosDNSPort(d.Roles)
	d.Aliases = aliases(d.Roles, d.HostName)

	// Instance services:
	d.services.load(d.Roles, groups(d.Prometheus))
	d.SystemdUnits = d.services.listUnits()
	d.HostTCPPorts = d.services.listPorts("tcp")
	d.HostUDPPorts = d.services.listPorts("udp")

	// Template:
	d.fragments.load2() // Predefined template fragments.
	d.composeTemplate() // Compose the template.
	d.renderTemplate()  // Render the template into memory.

	// Parse bytes into a Container Linux Config
	config, ast, report := ct.Parse(d.userData.Bytes())
	if report.IsFatal() {
		log.WithField("cmd", "udata").Fatal(report.String())
	}

	// Convert Container Linux Config into an Ignition Config
	ign, report := ct.ConvertAs2_0(config, "", ast)
	if report.IsFatal() {
		log.WithField("cmd", "udata").Fatal(report.String())
	}

	js, err := json.Marshal(ign)
	if err != nil {
		log.WithField("cmd", "udata").Fatal(err)
	}

	d.userData.Reset()
	if _, err := d.userData.Write(js); err != nil {
		log.WithField("cmd", "udata").Fatal(err)
	}

	// User data:
	d.validateUserData() // Validate the generated user data.
	d.outputUserData()   // Output user data to stdout.
}
<commit_msg>Switch from cloud-config to ignition (wip)<commit_after>package udata

//-----------------------------------------------------------------------------
// Package factored import statement:
//-----------------------------------------------------------------------------

import (

	// Stdlib:
	"bytes"
	"compress/gzip"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"regexp"
	"strconv"
	"text/template"

	// Community:
	"github.com/Masterminds/sprig"
	log "github.com/Sirupsen/logrus"
	ct "github.com/coreos/container-linux-config-transpiler/config"
	"github.com/coreos/coreos-cloudinit/config/validate"
)

//-----------------------------------------------------------------------------
// Typedefs:
//-----------------------------------------------------------------------------

// CmdData holds the data used by the udata sub-command
type CmdData struct {
	CmdFlags // Command-line flags
	PostProc // Post-processed data
	intData  // Internal logic data
}

// CmdFlags honored by the udata sub-command
type CmdFlags struct {
	AdminEmail          string   // --admin-email
	CaCertPath          string   // --ca-cert-path
	CalicoIPPool        string   // --calico-ip-pool
	ClusterID           string   // --cluster-id
	ClusterState        string   // --cluster-state
	Domain              string   // --domain
	Ec2Region           string   // --ec2-region
	EtcdToken           string   // --etcd-token
	GzipUdata           bool     // --gzip-udata
	HostID              string   // --host-id
	HostName            string   // --host-name
	IaasProvider        string   // --iaas-provider
	MasterCount         int      // --master-count
	DNSProvider         string   // --dns-provider
	DNSApiKey           string   // --dns-api-key
	Prometheus          bool     // --prometheus
	QuorumCount         int      // --quorum-count
	RexrayEndpointIP    string   // --rexray-endpoint-ip
	RexrayStorageDriver string   // --rexray-storage-driver
	Roles               []string // --roles
	SlackWebhook        string   // --slack-webhook
	SMTPURL             string   // --smtp-url
	StubZones           []string // --stub-zone
}

// PostProc data based on previous flags
type PostProc struct {
	AlertManagers string
	Aliases       []string
	CaCert        string
	KatoState     string
	EtcdEndpoints string
	EtcdServers   string
	HostTCPPorts  []string
	HostUDPPorts  []string
	MesosDNSPort  int
	SMTP
	SystemdUnits []string
	ZkServers    string
}

// SMTP structure
type SMTP struct {
	Host string
	Port string
	User string
	Pass string
}

// Internal logic data
type intData struct {
	fragments fragmentSlice
	services  serviceMap
	template  string
	userData  *bytes.Buffer
}

//-----------------------------------------------------------------------------
// func: readFile
//-----------------------------------------------------------------------------

func readFile(path string) string {
	if _, err := os.Stat(path); err == nil {
		data, err := ioutil.ReadFile(path)
		if err != nil {
			log.WithField("cmd", "udata").Fatal(err)
		}
		return string(data)
	}
	return ""
}

//-----------------------------------------------------------------------------
// func: zkServers
//-----------------------------------------------------------------------------

func zkServers(quorumCount int) (zkServers string) {
	for i := 1; i <= quorumCount; i++ {
		zkServers = zkServers + "quorum-" + strconv.Itoa(i) + ":2181"
		if i != quorumCount {
			zkServers = zkServers + ","
		}
	}
	return
}

//-----------------------------------------------------------------------------
// func: etcdServers
//-----------------------------------------------------------------------------

func etcdServers(quorumCount int) (etcdServers string) {
	for i := 1; i <= quorumCount; i++ {
		etcdServers = etcdServers +
			"quorum-" + strconv.Itoa(i) + "=http://quorum-" + strconv.Itoa(i) + ":2380"
		if i != quorumCount {
			etcdServers = etcdServers + ","
		}
	}
	return
}

//-----------------------------------------------------------------------------
// func: etcdEndpoints
//-----------------------------------------------------------------------------

func etcdEndpoints(quorumCount int) (etcdEndpoints string) {
	for i := 1; i <= quorumCount; i++ {
		etcdEndpoints = etcdEndpoints +
			"http://quorum-" + strconv.Itoa(i) + ":2379"
		if i != quorumCount {
			etcdEndpoints = etcdEndpoints + ","
		}
	}
	return
}

//-----------------------------------------------------------------------------
// func: alertManagers
//-----------------------------------------------------------------------------

func alertManagers(masterCount int) (alertManagers string) {
	for i := 1; i <= masterCount; i++ {
		alertManagers = alertManagers + "http://master-" + strconv.Itoa(i) + ":9093"
		if i != masterCount {
			alertManagers = alertManagers + ","
		}
	}
	return
}

//-----------------------------------------------------------------------------
// func: smtpURLSplit
//-----------------------------------------------------------------------------

func smtpURLSplit(smtpURL string) (smtp SMTP) {
	if smtpURL != "" {
		r, _ := regexp.Compile("^smtp://(.+):(.+)@(.+):(\\d+)$")
		if sub := r.FindStringSubmatch(smtpURL); sub != nil {
			smtp.User = sub[1]
			smtp.Pass = sub[2]
			smtp.Host = sub[3]
			smtp.Port = sub[4]
		} else {
			log.WithFields(log.Fields{"cmd": "udata", "id": smtpURL}).
				Fatal("Invalid SMTP URL format.")
		}
	}
	return
}

//-----------------------------------------------------------------------------
// func: mesosDNSPort
//-----------------------------------------------------------------------------

func mesosDNSPort(roles []string) (mesosDNSPort int) {
	mesosDNSPort = 53
	for _, role := range roles {
		if role == "master" {
			for _, role := range roles {
				if role == "worker" {
					mesosDNSPort = 54
					return
				}
			}
		}
	}
	return
}

//-----------------------------------------------------------------------------
// func: aliases
//-----------------------------------------------------------------------------

func aliases(roles []string, hostName string) (aliases []string) {
	for _, i := range roles {
		if i != hostName {
			aliases = append(aliases, i)
		}
	}
	return
}

//-----------------------------------------------------------------------------
// func: groups
//-----------------------------------------------------------------------------

func groups(prometheus bool) (groups []string) {
	groups = []string{"base"}
	if prometheus {
		groups = append(groups, "insight")
	}
	return
}

//-----------------------------------------------------------------------------
// func: listOfTags
//-----------------------------------------------------------------------------

func (d *CmdData) listOfTags() (tags []string) {

	tags = append(d.Roles, d.IaasProvider)
	tags = append(tags, d.ClusterState)

	if d.CaCert != "" {
		tags = append(tags, "cacert")
	}

	if d.Prometheus {
		tags = append(tags, "prometheus")
	}

	return
}

//-----------------------------------------------------------------------------
// func: composeTemplate
//-----------------------------------------------------------------------------

func (d *CmdData) composeTemplate() {

	// Tags used to filter template fragments:
	tags := d.listOfTags()

	// Apply the filter:
	for _, frag := range d.fragments {
		if frag.anyOf(tags) {
			if frag.noneOf(tags) {
				if frag.allOf(tags) {
					d.template += frag.data
				}
			}
		}
	}
}

//-----------------------------------------------------------------------------
// func: renderTemplate
//-----------------------------------------------------------------------------

func (d *CmdData) renderTemplate() {

	// Template parsing:
	t := template.New("udata").Funcs(sprig.TxtFuncMap())
	t, err := t.Parse(d.template)
	if err != nil {
		log.WithField("cmd", "udata").Fatal(err)
	}

	// Apply parsed template to data object:
	d.userData = bytes.NewBuffer(make([]byte, 0, 65536))
	if err = t.Execute(d.userData, d); err != nil {
		log.WithField("cmd", "udata").Fatal(err)
	}
}

//-----------------------------------------------------------------------------
// func: renderIgnition
//-----------------------------------------------------------------------------

func (d *CmdData) renderIgnition() {

	// Parse bytes into a Container Linux config:
	config, ast, report := ct.Parse(d.userData.Bytes())
	if report.IsFatal() {
		log.WithField("cmd", "udata").Fatal(report.String())
	}

	// Convert Container Linux config into an Ignition config:
	ign, report := ct.ConvertAs2_0(config, "", ast)
	if report.IsFatal() {
		log.WithField("cmd", "udata").Fatal(report.String())
	}

	// Convert Ignition config to JSON:
	js, err := json.Marshal(ign)
	if err != nil {
		log.WithField("cmd", "udata").Fatal(err)
	}

	// Write JSON to the userData buffer:
	d.userData.Reset()
	if _, err := d.userData.Write(js); err != nil {
		log.WithField("cmd", "udata").Fatal(err)
	}
}

//-----------------------------------------------------------------------------
// func: validateUserData
//-----------------------------------------------------------------------------

func (d *CmdData) validateUserData() {

	errors := []string{}

	report, err := validate.Validate(d.userData.Bytes())
	if err != nil {
		errors = append(errors, fmt.Sprintf("%v", err))
	}
	for _, entry := range report.Entries() {
		errors = append(errors, fmt.Sprintf("%v", entry))
	}
	if len(errors) > 0 {
		log.WithField("cmd", "udata").Fatal(errors)
	}
}

//-----------------------------------------------------------------------------
// func: outputUserData
//-----------------------------------------------------------------------------

func (d *CmdData) outputUserData() {

	if d.GzipUdata {
		log.WithFields(log.Fields{"cmd": "udata", "id": d.HostName + "-" + d.HostID}).
			Info("Generating gzipped cloud-config user data")
		w := gzip.NewWriter(os.Stdout)
		if _, err := d.userData.WriteTo(w); err != nil {
			_ = w.Close()
			log.WithField("cmd", "udata").Fatal(err)
		}
		_ = w.Close()
	} else {
		log.WithField("cmd", "udata").Info("Generating plain text cloud-config user data")
		if _, err := d.userData.WriteTo(os.Stdout); err != nil {
			log.WithField("cmd", "udata").Fatal(err)
		}
	}
}

//-----------------------------------------------------------------------------
// func: CmdRun
//-----------------------------------------------------------------------------

// CmdRun takes data from CmdData and outputs valid CoreOS cloud-config user
// data in YAML format to stdout.
func (d *CmdData) CmdRun() {

	// Variables:
	d.CaCert = readFile(d.CaCertPath)
	d.KatoState = readFile(os.Getenv("HOME") + "/.kato/" + d.ClusterID + ".json")
	d.ZkServers = zkServers(d.QuorumCount)
	d.EtcdServers = etcdServers(d.QuorumCount)
	d.EtcdEndpoints = etcdEndpoints(d.QuorumCount)
	d.AlertManagers = alertManagers(d.MasterCount)
	d.SMTP = smtpURLSplit(d.SMTPURL)
	d.MesosDNSPort = mesosDNSPort(d.Roles)
	d.Aliases = aliases(d.Roles, d.HostName)

	// Systemd units and ports:
	d.services.load(d.Roles, groups(d.Prometheus))
	d.SystemdUnits = d.services.listUnits()
	d.HostTCPPorts = d.services.listPorts("tcp")
	d.HostUDPPorts = d.services.listPorts("udp")

	// Template to ignition JSON:
	d.fragments.load2() // Load all fragments.
	d.composeTemplate() // Compose the template.
	d.renderTemplate()  // Container linux config.
	d.renderIgnition()  // Ignition JSON.

	// User data:
	d.validateUserData() // Validate the generated user data.
	d.outputUserData()   // Output user data to stdout.
}
<|endoftext|>


<commit_before>package config

import (
	"encoding/json"
	"fmt"
	"reflect"
	"strconv"
	"strings"
	"time"
)

type decoder interface {
	Decode(value string) error
}

type fieldInfo struct {
	Name    string
	Default string
	Field   reflect.Value
}

type stringTemplate string

func decoderFrom(field reflect.Value) (d decoder) {
	// it may be impossible for a struct field to fail this check
	if !field.CanInterface() {
		return
	}

	d, ok := field.Interface().(decoder)
	if !ok && field.CanAddr() {
		d, _ = field.Addr().Interface().(decoder)
	}

	return d
}

// taken from github.com/kelseyhightower/envconfig
func structFields(spec interface{}) ([]fieldInfo, error) {
	s := reflect.ValueOf(spec).Elem()

	// over allocate an info array, we will extend if needed later
	infos := make([]fieldInfo, 0, s.NumField())
	for i := 0; i < s.NumField(); i++ {
		f := s.Field(i)
		ftype := s.Type().Field(i)

		fieldName := ftype.Tag.Get("name")
		if fieldName == "" {
			fieldName = strings.ToLower(ftype.Name)
		}

		// Capture information about the config variable
		info := fieldInfo{
			Name:    fieldName,
			Field:   f,
			Default: ftype.Tag.Get("default"),
		}
		infos = append(infos, info)

		if f.Kind() == reflect.Struct {
			// honor Decode if present
			if decoderFrom(f) == nil {
				embeddedPtr := f.Addr().Interface()
				embeddedInfos, err := structFields(embeddedPtr)
				if err != nil {
					return nil, err
				}
				infos = append(infos[:len(infos)-1], embeddedInfos...)

				continue
			}
		}
	}

	return infos, nil
}

func processField(value string, field reflect.Value) error {
	typ := field.Type()

	decoder := decoderFrom(field)
	if decoder != nil {
		return decoder.Decode(value)
	}

	if typ.Kind() == reflect.Ptr {
		typ = typ.Elem()
		if field.IsNil() {
			field.Set(reflect.New(typ))
		}
		field = field.Elem()
	}

	switch typ.Kind() {
	case reflect.String:
		field.SetString(value)
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		var (
			val int64
			err error
		)
		if field.Kind() == reflect.Int64 && typ.PkgPath() == "time" && typ.Name() == "Duration" {
			var d time.Duration
			d, err = time.ParseDuration(value)
			val = int64(d)
		} else {
			val, err = strconv.ParseInt(value, 0, typ.Bits())
		}
		if err != nil {
			return err
		}

		field.SetInt(val)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		val, err := strconv.ParseUint(value, 0, typ.Bits())
		if err != nil {
			return err
		}
		field.SetUint(val)
	case reflect.Bool:
		val, err := strconv.ParseBool(value)
		if err != nil {
			return err
		}
		field.SetBool(val)
	case reflect.Float32, reflect.Float64:
		val, err := strconv.ParseFloat(value, typ.Bits())
		if err != nil {
			return err
		}
		field.SetFloat(val)
	case reflect.Slice:
		vals := strings.Split(value, ",")
		sl := reflect.MakeSlice(typ, len(vals), len(vals))
		for i, val := range vals {
			err := processField(val, sl.Index(i))
			if err != nil {
				return err
			}
		}
		field.Set(sl)
	case reflect.Map:
		pairs := strings.Split(value, ",")
		mp := reflect.MakeMap(typ)
		for _, pair := range pairs {
			kvpair := strings.Split(pair, ":")
			if len(kvpair) != 2 {
				return fmt.Errorf("invalid map item: %q", pair)
			}
			k := reflect.New(typ.Key()).Elem()
			err := processField(kvpair[0], k)
			if err != nil {
				return err
			}
			v := reflect.New(typ.Elem()).Elem()
			err = processField(kvpair[1], v)
			if err != nil {
				return err
			}
			mp.SetMapIndex(k, v)
		}
		field.Set(mp)
	}

	return nil
}

func (f *stringTemplate) Decode(value string) error {
	*f = stringTemplate(value)

	return nil
}

func (f *stringTemplate) Format(a ...string) string {
	res := string(*f)

	for i := 0; i < len(a); i += 2 {
		res = strings.Replace(res, "{"+a[i]+"}", a[i+1], -1)
	}

	return res
}

func (f stringTemplate) MarshalJSON() ([]byte, error) {
	return json.Marshal(string(f))
}
<commit_msg>Make configMap marshaling code aware of quotes.<commit_after>package config

import (
	"encoding/json"
	"fmt"
	"reflect"
	"strconv"
	"strings"
	"time"
)

type decoder interface {
	Decode(value string) error
}

type fieldInfo struct {
	Name    string
	Default string
	Field   reflect.Value
}

type stringTemplate string

func decoderFrom(field reflect.Value) (d decoder) {
	// it may be impossible for a struct field to fail this check
	if !field.CanInterface() {
		return
	}

	d, ok := field.Interface().(decoder)
	if !ok && field.CanAddr() {
		d, _ = field.Addr().Interface().(decoder)
	}

	return d
}

// taken from github.com/kelseyhightower/envconfig
func structFields(spec interface{}) ([]fieldInfo, error) {
	s := reflect.ValueOf(spec).Elem()

	// over allocate an info array, we will extend if needed later
	infos := make([]fieldInfo, 0, s.NumField())
	for i := 0; i < s.NumField(); i++ {
		f := s.Field(i)
		ftype := s.Type().Field(i)

		fieldName := ftype.Tag.Get("name")
		if fieldName == "" {
			fieldName = strings.ToLower(ftype.Name)
		}

		// Capture information about the config variable
		info := fieldInfo{
			Name:    fieldName,
			Field:   f,
			Default: ftype.Tag.Get("default"),
		}
		infos = append(infos, info)

		if f.Kind() == reflect.Struct {
			// honor Decode if present
			if decoderFrom(f) == nil {
				embeddedPtr := f.Addr().Interface()
				embeddedInfos, err := structFields(embeddedPtr)
				if err != nil {
					return nil, err
				}
				infos = append(infos[:len(infos)-1], embeddedInfos...)

				continue
			}
		}
	}

	return infos, nil
}

func processField(value string, field reflect.Value) error {
	typ := field.Type()

	decoder := decoderFrom(field)
	if decoder != nil {
		return decoder.Decode(value)
	}

	if typ.Kind() == reflect.Ptr {
		typ = typ.Elem()
		if field.IsNil() {
			field.Set(reflect.New(typ))
		}
		field = field.Elem()
	}

	switch typ.Kind() {
	case reflect.String:
		field.SetString(value)
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		var (
			val int64
			err error
		)
		if field.Kind() == reflect.Int64 && typ.PkgPath() == "time" && typ.Name() == "Duration" {
			var d time.Duration
			d, err = time.ParseDuration(value)
			val = int64(d)
		} else {
			val, err = strconv.ParseInt(value, 0, typ.Bits())
		}
		if err != nil {
			return err
		}

		field.SetInt(val)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		val, err := strconv.ParseUint(value, 0, typ.Bits())
		if err != nil {
			return err
		}
		field.SetUint(val)
	case reflect.Bool:
		val, err := strconv.ParseBool(value)
		if err != nil {
			return err
		}
		field.SetBool(val)
	case reflect.Float32, reflect.Float64:
		val, err := strconv.ParseFloat(value, typ.Bits())
		if err != nil {
			return err
		}
		field.SetFloat(val)
	case reflect.Slice:
		vals := strings.Split(value, ",")
		sl := reflect.MakeSlice(typ, len(vals), len(vals))
		for i, val := range vals {
			err := processField(val, sl.Index(i))
			if err != nil {
				return err
			}
		}
		field.Set(sl)
	case reflect.Map:
		pairs, err := getMapPairsFromString(value)
		if err != nil {
			return fmt.Errorf("could not split value %q into map items: %v", value, err)
		}
		mp := reflect.MakeMap(typ)
		for _, pair := range pairs {
			kvpair := strings.Split(pair, ":")
			if len(kvpair) != 2 {
				return fmt.Errorf("invalid map item: %q", pair)
			}
			k := reflect.New(typ.Key()).Elem()
			err := processField(kvpair[0], k)
			if err != nil {
				return err
			}
			v := reflect.New(typ.Elem()).Elem()
			err = processField(kvpair[1], v)
			if err != nil {
				return err
			}
			mp.SetMapIndex(k, v)
		}
		field.Set(mp)
	}

	return nil
}

type parserState int
const (
	Plain parserState  = iota
	DoubleQuoted
	SingleQuoted
)

// Split the pair candidates by commas not located inside open quotes
// Escape characters are not supported for simplicity, as we don't
// expect to find them inside the map values for our use cases
func getMapPairsFromString(value string) (pairs []string , err error) {
	pairs = make([]string, 0)
	state := Plain
	var start, quote int

	if value == "" {
		return
	}
	for i, ch := range(strings.Split(value, "")) {
		if ch == `"` {
			if state == Plain {
				state = DoubleQuoted
				quote = i
			} else if state == DoubleQuoted {
				state = Plain
				quote = 0
			}
		}
		if ch == "'" {
			if state == Plain {
				state = SingleQuoted
				quote = i
			} else if state == SingleQuoted {
				state = Plain
				quote = 0
			}
		}
		if ch == "," && state == Plain {
			pairs = append(pairs, value[start:i])
			start = i
		}
	}
	if state != Plain {
		err = fmt.Errorf("unclosed quote starting at position %d", quote)
	}
	pairs = append(pairs, value[start:])
	return
}

func (f *stringTemplate) Decode(value string) error {
	*f = stringTemplate(value)

	return nil
}

func (f *stringTemplate) Format(a ...string) string {
	res := string(*f)

	for i := 0; i < len(a); i += 2 {
		res = strings.Replace(res, "{"+a[i]+"}", a[i+1], -1)
	}

	return res
}

func (f stringTemplate) MarshalJSON() ([]byte, error) {
	return json.Marshal(string(f))
}
<|endoftext|>


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

package aws

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

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/ec2"

	"github.com/coreos/mantle/util"
)

func (a *API) AddKey(name, key string) error {
	_, err := a.ec2.ImportKeyPair(&ec2.ImportKeyPairInput{
		KeyName:           &name,
		PublicKeyMaterial: []byte(key),
	})

	return err
}

func (a *API) DeleteKey(name string) error {
	_, err := a.ec2.DeleteKeyPair(&ec2.DeleteKeyPairInput{
		KeyName: &name,
	})

	return err
}

// CreateInstances creates EC2 instances with a given name tag, optional ssh key name, user data. The image ID, instance type, and security group set in the API will be used. CreateInstances will block until all instances are running and have an IP address.
func (a *API) CreateInstances(name, keyname, userdata string, count uint64) ([]*ec2.Instance, error) {
	cnt := int64(count)

	var ud *string
	if len(userdata) > 0 {
		tud := base64.StdEncoding.EncodeToString([]byte(userdata))
		ud = &tud
	}

	err := a.ensureInstanceProfile(a.opts.IAMInstanceProfile)
	if err != nil {
		return nil, fmt.Errorf("error verifying IAM instance profile: %v", err)
	}

	sgId, err := a.getSecurityGroupID(a.opts.SecurityGroup)
	if err != nil {
		return nil, fmt.Errorf("error resolving security group: %v", err)
	}
	key := &keyname
	if keyname == "" {
		key = nil
	}
	inst := ec2.RunInstancesInput{
		ImageId:          &a.opts.AMI,
		MinCount:         &cnt,
		MaxCount:         &cnt,
		KeyName:          key,
		InstanceType:     &a.opts.InstanceType,
		SecurityGroupIds: []*string{&sgId},
		UserData:         ud,
		IamInstanceProfile: &ec2.IamInstanceProfileSpecification{
			Name: &a.opts.IAMInstanceProfile,
		},
		TagSpecifications: []*ec2.TagSpecification{
			&ec2.TagSpecification{
				ResourceType: aws.String(ec2.ResourceTypeInstance),
				Tags: []*ec2.Tag{
					&ec2.Tag{
						Key:   aws.String("Name"),
						Value: aws.String(name),
					},
				},
			},
		},
	}

	reservations, err := a.ec2.RunInstances(&inst)
	if err != nil {
		return nil, fmt.Errorf("error running instances: %v", err)
	}

	ids := make([]string, len(reservations.Instances))
	for i, inst := range reservations.Instances {
		ids[i] = *inst.InstanceId
	}

	// loop until all machines are online
	var insts []*ec2.Instance

	// 5 minutes is a pretty reasonable timeframe for AWS instances to work.
	timeout := 5 * time.Minute
	// don't make api calls too quickly, or we will hit the rate limit
	delay := 10 * time.Second
	err = util.WaitUntilReady(timeout, delay, func() (bool, error) {
		desc, err := a.ec2.DescribeInstances(&ec2.DescribeInstancesInput{
			InstanceIds: aws.StringSlice(ids),
		})
		if err != nil {
			return false, err
		}
		insts = desc.Reservations[0].Instances

		for _, i := range insts {
			if *i.State.Name != ec2.InstanceStateNameRunning || i.PublicIpAddress == nil {
				return false, nil
			}
		}
		return true, nil
	})
	if err != nil {
		a.TerminateInstances(ids)
		return nil, fmt.Errorf("waiting for instances to run: %v", err)
	}

	return insts, nil
}

// TerminateInstances schedules EC2 instances to be terminated.
func (a *API) TerminateInstances(ids []string) error {
	input := &ec2.TerminateInstancesInput{
		InstanceIds: aws.StringSlice(ids),
	}

	if _, err := a.ec2.TerminateInstances(input); err != nil {
		return err
	}

	return nil
}

func (a *API) CreateTags(resources []string, tags map[string]string) error {
	tagObjs := make([]*ec2.Tag, 0, len(tags))
	for key, value := range tags {
		tagObjs = append(tagObjs, &ec2.Tag{
			Key:   aws.String(key),
			Value: aws.String(value),
		})
	}
	_, err := a.ec2.CreateTags(&ec2.CreateTagsInput{
		Resources: aws.StringSlice(resources),
		Tags:      tagObjs,
	})
	if err != nil {
		return fmt.Errorf("error creating tags: %v", err)
	}
	return err
}

func (a *API) GetConsoleOutput(instanceID string, wait bool) (string, error) {
	var output string

	err := util.Retry(60, 5*time.Second, func() error {
		res, err := a.ec2.GetConsoleOutput(&ec2.GetConsoleOutputInput{
			InstanceId: aws.String(instanceID),
		})
		if err != nil {
			return fmt.Errorf("couldn't get console output of %v: %v", instanceID, err)
		}

		if res.Output == nil {
			if wait {
				plog.Debugf("waiting for console for %v", instanceID)
				return fmt.Errorf("timed out waiting for console output of %v", instanceID)
			} else {
				return nil
			}
		}

		decoded, err := base64.StdEncoding.DecodeString(*res.Output)
		if err != nil {
			return fmt.Errorf("couldn't decode console output of %v: %v", instanceID, err)
		}

		output = string(decoded)
		return nil
	})

	return output, err
}

// getSecurityGroupID gets a security group matching the given name.
// If the security group does not exist, it's created.
func (a *API) getSecurityGroupID(name string) (string, error) {
	sgIds, err := a.ec2.DescribeSecurityGroups(&ec2.DescribeSecurityGroupsInput{
		GroupNames: []*string{&name},
	})
	if isSecurityGroupNotExist(err) {
		return a.createSecurityGroup(name)
	}

	if err != nil {
		return "", fmt.Errorf("unable to get security group named %v: %v", name, err)
	}
	if len(sgIds.SecurityGroups) == 0 {
		return "", fmt.Errorf("zero security groups matched name %v", name)
	}
	return *sgIds.SecurityGroups[0].GroupId, nil
}

// createSecurityGroup creates a security group with tcp/22 access allowed from the
// internet.
func (a *API) createSecurityGroup(name string) (string, error) {
	sg, err := a.ec2.CreateSecurityGroup(&ec2.CreateSecurityGroupInput{
		GroupName:   aws.String(name),
		Description: aws.String("mantle security group for testing"),
	})
	if err != nil {
		return "", err
	}
	plog.Debugf("created security group %v", *sg.GroupId)

	allowedIngresses := []ec2.AuthorizeSecurityGroupIngressInput{
		{
			// SSH access from the public internet
			GroupId: sg.GroupId,
			IpPermissions: []*ec2.IpPermission{
				{
					IpProtocol: aws.String("tcp"),
					IpRanges: []*ec2.IpRange{
						{
							CidrIp: aws.String("0.0.0.0/0"),
						},
					},
					FromPort: aws.Int64(22),
					ToPort:   aws.Int64(22),
				},
			},
		},
		{
			// Access from all things in this vpc with the same SG (e.g. other
			// machines in our kola cluster)
			GroupId:                 sg.GroupId,
			SourceSecurityGroupName: aws.String(name),
		},
	}

	for _, input := range allowedIngresses {
		_, err := a.ec2.AuthorizeSecurityGroupIngress(&input)

		if err != nil {
			// We created the SG but can't add all the needed rules, let's try to
			// bail gracefully
			_, delErr := a.ec2.DeleteSecurityGroup(&ec2.DeleteSecurityGroupInput{
				GroupId: sg.GroupId,
			})
			if delErr != nil {
				return "", fmt.Errorf("created sg %v (%v) but couldn't authorize it. Manual deletion may be required: %v", *sg.GroupId, name, err)
			}
			return "", fmt.Errorf("created sg %v (%v), but couldn't authorize it and thus deleted it: %v", *sg.GroupId, name, err)
		}
	}
	return *sg.GroupId, err
}

func isSecurityGroupNotExist(err error) bool {
	if err == nil {
		return false
	}
	if awsErr, ok := (err).(awserr.Error); ok {
		if awsErr.Code() == "InvalidGroup.NotFound" {
			return true
		}
	}
	return false
}
<commit_msg>platform/aws: tag instances with const created-by<commit_after>// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package aws

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

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/ec2"

	"github.com/coreos/mantle/util"
)

func (a *API) AddKey(name, key string) error {
	_, err := a.ec2.ImportKeyPair(&ec2.ImportKeyPairInput{
		KeyName:           &name,
		PublicKeyMaterial: []byte(key),
	})

	return err
}

func (a *API) DeleteKey(name string) error {
	_, err := a.ec2.DeleteKeyPair(&ec2.DeleteKeyPairInput{
		KeyName: &name,
	})

	return err
}

// CreateInstances creates EC2 instances with a given name tag, optional ssh key name, user data. The image ID, instance type, and security group set in the API will be used. CreateInstances will block until all instances are running and have an IP address.
func (a *API) CreateInstances(name, keyname, userdata string, count uint64) ([]*ec2.Instance, error) {
	cnt := int64(count)

	var ud *string
	if len(userdata) > 0 {
		tud := base64.StdEncoding.EncodeToString([]byte(userdata))
		ud = &tud
	}

	err := a.ensureInstanceProfile(a.opts.IAMInstanceProfile)
	if err != nil {
		return nil, fmt.Errorf("error verifying IAM instance profile: %v", err)
	}

	sgId, err := a.getSecurityGroupID(a.opts.SecurityGroup)
	if err != nil {
		return nil, fmt.Errorf("error resolving security group: %v", err)
	}
	key := &keyname
	if keyname == "" {
		key = nil
	}
	inst := ec2.RunInstancesInput{
		ImageId:          &a.opts.AMI,
		MinCount:         &cnt,
		MaxCount:         &cnt,
		KeyName:          key,
		InstanceType:     &a.opts.InstanceType,
		SecurityGroupIds: []*string{&sgId},
		UserData:         ud,
		IamInstanceProfile: &ec2.IamInstanceProfileSpecification{
			Name: &a.opts.IAMInstanceProfile,
		},
		TagSpecifications: []*ec2.TagSpecification{
			&ec2.TagSpecification{
				ResourceType: aws.String(ec2.ResourceTypeInstance),
				Tags: []*ec2.Tag{
					&ec2.Tag{
						Key:   aws.String("Name"),
						Value: aws.String(name),
					},
					&ec2.Tag{
						Key:   aws.String("CreatedBy"),
						Value: aws.String("mantle"),
					},
				},
			},
		},
	}

	reservations, err := a.ec2.RunInstances(&inst)
	if err != nil {
		return nil, fmt.Errorf("error running instances: %v", err)
	}

	ids := make([]string, len(reservations.Instances))
	for i, inst := range reservations.Instances {
		ids[i] = *inst.InstanceId
	}

	// loop until all machines are online
	var insts []*ec2.Instance

	// 5 minutes is a pretty reasonable timeframe for AWS instances to work.
	timeout := 5 * time.Minute
	// don't make api calls too quickly, or we will hit the rate limit
	delay := 10 * time.Second
	err = util.WaitUntilReady(timeout, delay, func() (bool, error) {
		desc, err := a.ec2.DescribeInstances(&ec2.DescribeInstancesInput{
			InstanceIds: aws.StringSlice(ids),
		})
		if err != nil {
			return false, err
		}
		insts = desc.Reservations[0].Instances

		for _, i := range insts {
			if *i.State.Name != ec2.InstanceStateNameRunning || i.PublicIpAddress == nil {
				return false, nil
			}
		}
		return true, nil
	})
	if err != nil {
		a.TerminateInstances(ids)
		return nil, fmt.Errorf("waiting for instances to run: %v", err)
	}

	return insts, nil
}

// TerminateInstances schedules EC2 instances to be terminated.
func (a *API) TerminateInstances(ids []string) error {
	input := &ec2.TerminateInstancesInput{
		InstanceIds: aws.StringSlice(ids),
	}

	if _, err := a.ec2.TerminateInstances(input); err != nil {
		return err
	}

	return nil
}

func (a *API) CreateTags(resources []string, tags map[string]string) error {
	tagObjs := make([]*ec2.Tag, 0, len(tags))
	for key, value := range tags {
		tagObjs = append(tagObjs, &ec2.Tag{
			Key:   aws.String(key),
			Value: aws.String(value),
		})
	}
	_, err := a.ec2.CreateTags(&ec2.CreateTagsInput{
		Resources: aws.StringSlice(resources),
		Tags:      tagObjs,
	})
	if err != nil {
		return fmt.Errorf("error creating tags: %v", err)
	}
	return err
}

func (a *API) GetConsoleOutput(instanceID string, wait bool) (string, error) {
	var output string

	err := util.Retry(60, 5*time.Second, func() error {
		res, err := a.ec2.GetConsoleOutput(&ec2.GetConsoleOutputInput{
			InstanceId: aws.String(instanceID),
		})
		if err != nil {
			return fmt.Errorf("couldn't get console output of %v: %v", instanceID, err)
		}

		if res.Output == nil {
			if wait {
				plog.Debugf("waiting for console for %v", instanceID)
				return fmt.Errorf("timed out waiting for console output of %v", instanceID)
			} else {
				return nil
			}
		}

		decoded, err := base64.StdEncoding.DecodeString(*res.Output)
		if err != nil {
			return fmt.Errorf("couldn't decode console output of %v: %v", instanceID, err)
		}

		output = string(decoded)
		return nil
	})

	return output, err
}

// getSecurityGroupID gets a security group matching the given name.
// If the security group does not exist, it's created.
func (a *API) getSecurityGroupID(name string) (string, error) {
	sgIds, err := a.ec2.DescribeSecurityGroups(&ec2.DescribeSecurityGroupsInput{
		GroupNames: []*string{&name},
	})
	if isSecurityGroupNotExist(err) {
		return a.createSecurityGroup(name)
	}

	if err != nil {
		return "", fmt.Errorf("unable to get security group named %v: %v", name, err)
	}
	if len(sgIds.SecurityGroups) == 0 {
		return "", fmt.Errorf("zero security groups matched name %v", name)
	}
	return *sgIds.SecurityGroups[0].GroupId, nil
}

// createSecurityGroup creates a security group with tcp/22 access allowed from the
// internet.
func (a *API) createSecurityGroup(name string) (string, error) {
	sg, err := a.ec2.CreateSecurityGroup(&ec2.CreateSecurityGroupInput{
		GroupName:   aws.String(name),
		Description: aws.String("mantle security group for testing"),
	})
	if err != nil {
		return "", err
	}
	plog.Debugf("created security group %v", *sg.GroupId)

	allowedIngresses := []ec2.AuthorizeSecurityGroupIngressInput{
		{
			// SSH access from the public internet
			GroupId: sg.GroupId,
			IpPermissions: []*ec2.IpPermission{
				{
					IpProtocol: aws.String("tcp"),
					IpRanges: []*ec2.IpRange{
						{
							CidrIp: aws.String("0.0.0.0/0"),
						},
					},
					FromPort: aws.Int64(22),
					ToPort:   aws.Int64(22),
				},
			},
		},
		{
			// Access from all things in this vpc with the same SG (e.g. other
			// machines in our kola cluster)
			GroupId:                 sg.GroupId,
			SourceSecurityGroupName: aws.String(name),
		},
	}

	for _, input := range allowedIngresses {
		_, err := a.ec2.AuthorizeSecurityGroupIngress(&input)

		if err != nil {
			// We created the SG but can't add all the needed rules, let's try to
			// bail gracefully
			_, delErr := a.ec2.DeleteSecurityGroup(&ec2.DeleteSecurityGroupInput{
				GroupId: sg.GroupId,
			})
			if delErr != nil {
				return "", fmt.Errorf("created sg %v (%v) but couldn't authorize it. Manual deletion may be required: %v", *sg.GroupId, name, err)
			}
			return "", fmt.Errorf("created sg %v (%v), but couldn't authorize it and thus deleted it: %v", *sg.GroupId, name, err)
		}
	}
	return *sg.GroupId, err
}

func isSecurityGroupNotExist(err error) bool {
	if err == nil {
		return false
	}
	if awsErr, ok := (err).(awserr.Error); ok {
		if awsErr.Code() == "InvalidGroup.NotFound" {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package http

import (
	"bytes"
	"container/list"
	"fmt"
	"log"
	"net"
	"net/http"
	"strings"
	"time"
)

type eventMessage struct {
	id    string
	event string
	data  string
}

type eventSource struct {
	sink           chan *eventMessage
	staled         chan *consumer
	add            chan *consumer
	close          chan bool
	timeout        time.Duration
	closeOnTimeout bool

	consumers *list.List
}

// EventSource interface provides methods for sending messages and closing all connections.
type EventSource interface {
	// it should implement ServerHTTP method
	http.Handler

	// send message to all consumers
	SendMessage(data, event, id string)

	// consumers count
	ConsumersCount() int

	// close and clear all consumers
	Close()

	// set the timeout for message sends
	SetTimeout(n time.Duration)

	// set timeout behaviour
	CloseOnTimeout(b bool)
}

func prepareMessage(m *eventMessage) []byte {
	var data bytes.Buffer
	if len(m.id) > 0 {
		data.WriteString(fmt.Sprintf("id: %s\n", strings.Replace(m.id, "\n", "", -1)))
	}
	if len(m.event) > 0 {
		data.WriteString(fmt.Sprintf("event: %s\n", strings.Replace(m.event, "\n", "", -1)))
	}
	if len(m.data) > 0 {
		lines := strings.Split(m.data, "\n")
		for _, line := range lines {
			data.WriteString(fmt.Sprintf("data: %s\n", line))
		}
	}
	data.WriteString("\n")
	return data.Bytes()
}

func controlProcess(es *eventSource) {
	for {
		select {
		case em := <-es.sink:
			message := prepareMessage(em)
			for e := es.consumers.Front(); e != nil; e = e.Next() {
				c := e.Value.(*consumer)
				var closed bool

				// First check if a previous message caused an error
				select {
				case err := <-c.err:
					netErr, ok := err.(net.Error)
					if !ok || !netErr.Timeout() || es.closeOnTimeout {
						close(c.in)
						c.close <- true
						es.staled <- c
						closed = true
					}
				default:
				}

				// Only send this message if there was no error that
				// caused the consumer to get closed
				if !closed {
					select {
					case c.in <- message:
					default:
					}
				}
			}
		case <-es.close:
			close(es.sink)
			close(es.add)
			close(es.staled)
			close(es.close)
			for e := es.consumers.Front(); e != nil; e = e.Next() {
				c := e.Value.(*consumer)
				c.close <- true
			}
			es.consumers.Init()
			return
		case c := <-es.add:
			es.consumers.PushBack(c)
		case c := <-es.staled:
			toRemoveEls := make([]*list.Element, 0, 1)
			for e := es.consumers.Front(); e != nil; e = e.Next() {
				if e.Value.(*consumer) == c {
					toRemoveEls = append(toRemoveEls, e)
				}
			}
			for _, e := range toRemoveEls {
				es.consumers.Remove(e)
			}
		}
	}
}

// New creates new EventSource instance.
func New() EventSource {
	es := new(eventSource)
	es.sink = make(chan *eventMessage, 1)
	es.close = make(chan bool)
	es.staled = make(chan *consumer, 1)
	es.add = make(chan *consumer)
	es.consumers = list.New()
	es.timeout = 2 * time.Second
	es.closeOnTimeout = true
	go controlProcess(es)
	return es
}

// SetTimeout sets the write timeout for individual messages. The
// default is 2 seconds.
func (es *eventSource) SetTimeout(n time.Duration) {
	es.timeout = n
}

// CloseOnTimeout sets whether a write timeout should close the
// connection or just drop the message.
//
// If the connection gets closed on a timeout, it's the client's
// responsibility to re-establish a connection. If the connection
// doesn't get closed, messages might get sent to a potentially dead
// client.
//
// The default is true.
func (es *eventSource) CloseOnTimeout(b bool) {
	es.closeOnTimeout = b
}

func (es *eventSource) Close() {
	es.close <- true
}

// ServeHTTP implements http.Handler interface.
func (es *eventSource) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
	cons, err := newConsumer(resp, es)
	if err != nil {
		log.Print("Can't create connection to a consumer: ", err)
		return
	}
	es.add <- cons
	// wait until EventSource closes all connection
	<-cons.close
}

func (es *eventSource) sendEventMessage(e *eventMessage) {
	es.sink <- e
}

func (es *eventSource) SendMessage(data, event, id string) {
	em := &eventMessage{id, event, data}
	es.sendEventMessage(em)
}

func (es *eventSource) ConsumersCount() int {
	return es.consumers.Len()
}
<commit_msg>move timeout behaviour into Settings struct<commit_after>package http

import (
	"bytes"
	"container/list"
	"fmt"
	"log"
	"net"
	"net/http"
	"strings"
	"time"
)

type eventMessage struct {
	id    string
	event string
	data  string
}

type eventSource struct {
	sink           chan *eventMessage
	staled         chan *consumer
	add            chan *consumer
	close          chan bool
	timeout        time.Duration
	closeOnTimeout bool

	consumers *list.List
}

type Settings struct {
	// SetTimeout sets the write timeout for individual messages. The
	// default is 2 seconds.
	Timeout        time.Duration

	// CloseOnTimeout sets whether a write timeout should close the
	// connection or just drop the message.
	//
	// If the connection gets closed on a timeout, it's the client's
	// responsibility to re-establish a connection. If the connection
	// doesn't get closed, messages might get sent to a potentially dead
	// client.
	//
	// The default is true.
	CloseOnTimeout bool
}

func DefaultSettings() *settings {
	return &Settings{
		Timeout:        2 * time.Second,
		CloseOnTimeout: true,
	}
}

// EventSource interface provides methods for sending messages and closing all connections.
type EventSource interface {
	// it should implement ServerHTTP method
	http.Handler

	// send message to all consumers
	SendMessage(data, event, id string)

	// consumers count
	ConsumersCount() int

	// close and clear all consumers
	Close()
}

func prepareMessage(m *eventMessage) []byte {
	var data bytes.Buffer
	if len(m.id) > 0 {
		data.WriteString(fmt.Sprintf("id: %s\n", strings.Replace(m.id, "\n", "", -1)))
	}
	if len(m.event) > 0 {
		data.WriteString(fmt.Sprintf("event: %s\n", strings.Replace(m.event, "\n", "", -1)))
	}
	if len(m.data) > 0 {
		lines := strings.Split(m.data, "\n")
		for _, line := range lines {
			data.WriteString(fmt.Sprintf("data: %s\n", line))
		}
	}
	data.WriteString("\n")
	return data.Bytes()
}

func controlProcess(es *eventSource) {
	for {
		select {
		case em := <-es.sink:
			message := prepareMessage(em)
			for e := es.consumers.Front(); e != nil; e = e.Next() {
				c := e.Value.(*consumer)
				var closed bool

				// First check if a previous message caused an error
				select {
				case err := <-c.err:
					netErr, ok := err.(net.Error)
					if !ok || !netErr.Timeout() || es.closeOnTimeout {
						close(c.in)
						c.close <- true
						es.staled <- c
						closed = true
					}
				default:
				}

				// Only send this message if there was no error that
				// caused the consumer to get closed
				if !closed {
					select {
					case c.in <- message:
					default:
					}
				}
			}
		case <-es.close:
			close(es.sink)
			close(es.add)
			close(es.staled)
			close(es.close)
			for e := es.consumers.Front(); e != nil; e = e.Next() {
				c := e.Value.(*consumer)
				c.close <- true
			}
			es.consumers.Init()
			return
		case c := <-es.add:
			es.consumers.PushBack(c)
		case c := <-es.staled:
			toRemoveEls := make([]*list.Element, 0, 1)
			for e := es.consumers.Front(); e != nil; e = e.Next() {
				if e.Value.(*consumer) == c {
					toRemoveEls = append(toRemoveEls, e)
				}
			}
			for _, e := range toRemoveEls {
				es.consumers.Remove(e)
			}
		}
	}
}

// New creates new EventSource instance.
func New(settings *Settings) EventSource {
	if settings == nil {
		settings = DefaultSettings()
	}

	es := new(eventSource)
	es.sink = make(chan *eventMessage, 1)
	es.close = make(chan bool)
	es.staled = make(chan *consumer, 1)
	es.add = make(chan *consumer)
	es.consumers = list.New()
	es.timeout = settings.Timeout
	es.closeOnTimeout = settings.CloseOnTimeout
	go controlProcess(es)
	return es
}

func (es *eventSource) Close() {
	es.close <- true
}

// ServeHTTP implements http.Handler interface.
func (es *eventSource) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
	cons, err := newConsumer(resp, es)
	if err != nil {
		log.Print("Can't create connection to a consumer: ", err)
		return
	}
	es.add <- cons
	// wait until EventSource closes all connection
	<-cons.close
}

func (es *eventSource) sendEventMessage(e *eventMessage) {
	es.sink <- e
}

func (es *eventSource) SendMessage(data, event, id string) {
	em := &eventMessage{id, event, data}
	es.sendEventMessage(em)
}

func (es *eventSource) ConsumersCount() int {
	return es.consumers.Len()
}
<|endoftext|>


<commit_before>package container

import (
	"archive/tar"
	"bytes"
	"encoding/hex"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
	"time"
	"errors"

	"github.com/spf13/viper"

	"github.com/openblockchain/obc-peer/openchain/util"
	pb "github.com/openblockchain/obc-peer/protos"
)

func addFile(tw *tar.Writer, path string, info os.FileInfo, fbytes []byte) error {
	h, err := tar.FileInfoHeader(info, path)
	if err != nil {
		return fmt.Errorf("Error getting FileInfoHeader: %s", err)
	}
	//Let's take the variance out of the tar, make headers identical by using zero time
	var zeroTime time.Time
	h.AccessTime = zeroTime
	h.ModTime = zeroTime
	h.ChangeTime = zeroTime
	h.Name = path
	if err = tw.WriteHeader(h); err != nil {
		return fmt.Errorf("Error writing header: %s", err)
	}
	rdr := bytes.NewReader(fbytes)
	if _, err := io.Copy(tw, rdr); err != nil {
		return fmt.Errorf("Error copying file : %s", err)
	}
	return nil
}

//hashFilesInDir computes h=hash(h,file bytes) for each file in a directory
//Directory entries are traversed recursively. In the end a single
//hash value is returned for the entire directory structure
func hashFilesInDir(rootDir string, dir string, hash []byte, tw *tar.Writer) ([]byte, error) {
	//ReadDir returns sorted list of files in dir
	fis, err := ioutil.ReadDir(rootDir + "/" + dir)
	if err != nil {
		return hash, fmt.Errorf("ReadDir failed %s\n", err)
	}
	for _, fi := range fis {
		name := fmt.Sprintf("%s/%s", dir, fi.Name())
		if fi.IsDir() {
			var err error
			hash, err = hashFilesInDir(rootDir, name, hash, tw)
			if err != nil {
				return hash, err
			}
			continue
		}
		buf, err := ioutil.ReadFile(rootDir + "/" + name)
		if err != nil {
			fmt.Printf("Error reading %s\n", err)
			return hash, err
		}

		newSlice := make([]byte, len(hash)+len(buf))
		copy(newSlice[len(buf):], hash[:])
		//hash = md5.Sum(newSlice)
		hash = util.ComputeCryptoHash(newSlice)

		if tw != nil {
			if err = addFile(tw, "src/"+name, fi, buf); err != nil {
				return hash, fmt.Errorf("Error adding file to tar %s", err)
			}
		}
	}
	return hash, nil
}

func isCodeExist(tmppath string) error {
	file, err := os.Open(tmppath)
	if err != nil {
		return fmt.Errorf("Download failer %s", err)
	}

	fi, err := file.Stat()
	if err != nil {
		return fmt.Errorf("could not stat file %s", err)
	}

	if !fi.IsDir() {
		return fmt.Errorf("file %s is not dir\n", file.Name())
	}

	return nil
}

func getCodeFromHTTP(path string) (codegopath string, err error) {
	codegopath = ""
	err = nil

	env := os.Environ()
	var newgopath string
	var origgopath string
	var gopathenvIndex int
	for i, v := range env {
		if strings.Index(v, "GOPATH=") == 0 {
			p := strings.SplitAfter(v, "GOPATH=")
			origgopath = p[1]
			newgopath = origgopath + "/_usercode_"
			gopathenvIndex = i
			break
		}
	}

	if newgopath == "" {
		err = fmt.Errorf("GOPATH not defined")
		return
	}

	//ignore errors.. _usercode_ might exist. TempDir will catch any other errors
	os.Mkdir(newgopath, 0755)

	if codegopath, err = ioutil.TempDir(newgopath, ""); err != nil {
		err = fmt.Errorf("could not create tmp dir under %s(%s)", newgopath, err)
		return
	}

	var out bytes.Buffer
	// If we don't copy the obc-peer code into the temp dir with the chaincode,
	// go get will have to redownload obc-peer, which requires credentials.
	cmd := exec.Command("cp", "-r", origgopath + "/src", codegopath + "/src")
	cmd.Env = env
	cmd.Stdout = &out
	err = cmd.Start()

	// Create a go routine that will wait for the command to finish
	done := make(chan error, 1)
	go func() {
		done <- cmd.Wait()
	}()

	select {
	case <-time.After(time.Duration(viper.GetInt("chaincode.deploytimeout")) * time.Millisecond):
		// If pulling repos takes too long, we should give up
		// (This can happen if a repo is private and the git clone asks for credentials)
		if err = cmd.Process.Kill(); err != nil {
			err = fmt.Errorf("failed to kill: %s", err)
		} else {
			err = errors.New("Getting chaincode took too long")
		}
	case err = <-done:
		// If we're here, the 'go get' command must have finished
		if err != nil {
			err = fmt.Errorf("process done with error = %v", err)
		}
	}

	env[gopathenvIndex] = "GOPATH=" + codegopath

	// Use a 'go get' command to pull the chaincode from the given repo
	cmd = exec.Command("go", "get", path)
	cmd.Env = env

	cmd.Stdout = &out
	err = cmd.Start()

	// Create a go routine that will wait for the command to finish
	done = make(chan error, 1)
	go func() {
		done <- cmd.Wait()
	}()

	select {
	case <-time.After(time.Duration(viper.GetInt("chaincode.deploytimeout")) * time.Millisecond):
		// If pulling repos takes too long, we should give up
		// (This can happen if a repo is private and the git clone asks for credentials)
		if err = cmd.Process.Kill(); err != nil {
			err = fmt.Errorf("failed to kill: %s", err)
		}
		err = errors.New("Getting chaincode took too long")
	case err = <-done:
		// If we're here, the 'go get' command must have finished
		if err != nil {
			err = fmt.Errorf("process done with error = %v", err)
		}
	}
	return
}

func getCodeFromFS(path string) (codegopath string, err error) {
	env := os.Environ()
	var gopath string
	for _, v := range env {
		if strings.Index(v, "GOPATH=") == 0 {
			p := strings.SplitAfter(v, "GOPATH=")
			gopath = p[1]
			break
		}
	}

	if gopath == "" {
		return
	}

	codegopath = gopath

	return
}

//name could be ChaincodeID.Name or ChaincodeID.Path
func generateHashFromSignature(path string, ctor string, args []string) []byte {
	fargs := ctor
	if args != nil {
		for _, str := range args {
			fargs = fargs + str
		}
	}
	cbytes := []byte(path + fargs)

	b := make([]byte, len(cbytes))
	copy(b, cbytes)
	hash := util.ComputeCryptoHash(b)
	return hash
}

//generateHashcode gets hashcode of the code under path. If path is a HTTP(s) url
//it downloads the code first to compute the hash.
//NOTE: for dev mode, user builds and runs chaincode manually. The name provided
//by the user is equivalent to the path. This method will treat the name
//as codebytes and compute the hash from it. ie, user cannot run the chaincode
//with the same (name, ctor, args)
func generateHashcode(spec *pb.ChaincodeSpec, tw *tar.Writer) (string, error) {
	if spec == nil {
		return "", fmt.Errorf("Cannot generate hashcode from nil spec")
	}

	chaincodeID := spec.ChaincodeID
	if chaincodeID == nil || chaincodeID.Path == "" {
		return "", fmt.Errorf("Cannot generate hashcode from empty chaincode path")
	}

	ctor := spec.CtorMsg
	if ctor == nil || ctor.Function == "" {
		return "", fmt.Errorf("Cannot generate hashcode from empty ctor")
	}

	//code root will point to the directory where the code exists
	//in the case of http it will be a temporary dir that
	//will have to be deleted
	var codegopath string

	var ishttp bool
	defer func() {
		if ishttp && codegopath != "" {
			os.RemoveAll(codegopath)
		}
	}()

	path := chaincodeID.Path

	var err error
	var actualcodepath string
	if strings.HasPrefix(path, "http://") {
		ishttp = true
		actualcodepath = path[7:]
		codegopath, err = getCodeFromHTTP(actualcodepath)
	} else if strings.HasPrefix(path, "https://") {
		ishttp = true
		actualcodepath = path[8:]
		codegopath, err = getCodeFromHTTP(actualcodepath)
	} else {
		actualcodepath = path
		codegopath, err = getCodeFromFS(path)
	}

	if err != nil {
		return "", fmt.Errorf("Error getting code %s", err)
	}

	tmppath := codegopath + "/src/" + actualcodepath
	if err = isCodeExist(tmppath); err != nil {
		return "", fmt.Errorf("code does not exist %s", err)
	}

	hash := generateHashFromSignature(actualcodepath, ctor.Function, ctor.Args)

	hash, err = hashFilesInDir(codegopath+"/src/", actualcodepath, hash, tw)
	if err != nil {
		return "", fmt.Errorf("Could not get hashcode for %s - %s\n", path, err)
	}

	return hex.EncodeToString(hash[:]), nil
}
<commit_msg>Reverting this file to match the openblockchain/obc-peer's repo<commit_after>package container

import (
	"archive/tar"
	"bytes"
	"encoding/hex"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
	"time"
	"errors"

	"github.com/spf13/viper"

	"github.com/openblockchain/obc-peer/openchain/util"
	pb "github.com/openblockchain/obc-peer/protos"
)

func addFile(tw *tar.Writer, path string, info os.FileInfo, fbytes []byte) error {
	h, err := tar.FileInfoHeader(info, path)
	if err != nil {
		return fmt.Errorf("Error getting FileInfoHeader: %s", err)
	}
	//Let's take the variance out of the tar, make headers identical by using zero time
	var zeroTime time.Time
	h.AccessTime = zeroTime
	h.ModTime = zeroTime
	h.ChangeTime = zeroTime
	h.Name = path
	if err = tw.WriteHeader(h); err != nil {
		return fmt.Errorf("Error writing header: %s", err)
	}
	rdr := bytes.NewReader(fbytes)
	if _, err := io.Copy(tw, rdr); err != nil {
		return fmt.Errorf("Error copying file : %s", err)
	}
	return nil
}

//hashFilesInDir computes h=hash(h,file bytes) for each file in a directory
//Directory entries are traversed recursively. In the end a single
//hash value is returned for the entire directory structure
func hashFilesInDir(rootDir string, dir string, hash []byte, tw *tar.Writer) ([]byte, error) {
	//ReadDir returns sorted list of files in dir
	fis, err := ioutil.ReadDir(rootDir + "/" + dir)
	if err != nil {
		return hash, fmt.Errorf("ReadDir failed %s\n", err)
	}
	for _, fi := range fis {
		name := fmt.Sprintf("%s/%s", dir, fi.Name())
		if fi.IsDir() {
			var err error
			hash, err = hashFilesInDir(rootDir, name, hash, tw)
			if err != nil {
				return hash, err
			}
			continue
		}
		buf, err := ioutil.ReadFile(rootDir + "/" + name)
		if err != nil {
			fmt.Printf("Error reading %s\n", err)
			return hash, err
		}

		newSlice := make([]byte, len(hash)+len(buf))
		copy(newSlice[len(buf):], hash[:])
		//hash = md5.Sum(newSlice)
		hash = util.ComputeCryptoHash(newSlice)

		if tw != nil {
			if err = addFile(tw, "src/"+name, fi, buf); err != nil {
				return hash, fmt.Errorf("Error adding file to tar %s", err)
			}
		}
	}
	return hash, nil
}

func isCodeExist(tmppath string) error {
	file, err := os.Open(tmppath)
	if err != nil {
		return fmt.Errorf("Download failer %s", err)
	}

	fi, err := file.Stat()
	if err != nil {
		return fmt.Errorf("could not stat file %s", err)
	}

	if !fi.IsDir() {
		return fmt.Errorf("file %s is not dir\n", file.Name())
	}

	return nil
}

func getCodeFromHTTP(path string) (codegopath string, err error) {
	codegopath = ""
	err = nil

	env := os.Environ()
	var newgopath string
	var origgopath string
	var gopathenvIndex int
	for i, v := range env {
		if strings.Index(v, "GOPATH=") == 0 {
			p := strings.SplitAfter(v, "GOPATH=")
			origgopath = p[1]
			newgopath = origgopath + "/_usercode_"
			gopathenvIndex = i
			break
		}
	}

	if newgopath == "" {
		err = fmt.Errorf("GOPATH not defined")
		return
	}

	//ignore errors.. _usercode_ might exist. TempDir will catch any other errors
	os.Mkdir(newgopath, 0755)

	if codegopath, err = ioutil.TempDir(newgopath, ""); err != nil {
		err = fmt.Errorf("could not create tmp dir under %s(%s)", newgopath, err)
		return
	}

	env[gopathenvIndex] = "GOPATH=" + codegopath

	// Use a 'go get' command to pull the chaincode from the given repo
	cmd := exec.Command("go", "get", path)
	cmd.Env = env
	var out bytes.Buffer
	cmd.Stdout = &out
	err = cmd.Start()

	// Create a go routine that will wait for the command to finish
	done := make(chan error, 1)
	go func() {
		done <- cmd.Wait()
	}()

	select {
	case <-time.After(time.Duration(viper.GetInt("chaincode.deploytimeout")) * time.Millisecond):
		// If pulling repos takes too long, we should give up
		// (This can happen if a repo is private and the git clone asks for credentials)
		if err = cmd.Process.Kill(); err != nil {
			err = fmt.Errorf("failed to kill: %s", err)
		} else {
			err = errors.New("Getting chaincode took too long")
		}
	case err = <-done:
		// If we're here, the 'go get' command must have finished
		if err != nil {
			err = fmt.Errorf("process done with error = %v", err)
		}
	}
	return
}

func getCodeFromFS(path string) (codegopath string, err error) {
	env := os.Environ()
	var gopath string
	for _, v := range env {
		if strings.Index(v, "GOPATH=") == 0 {
			p := strings.SplitAfter(v, "GOPATH=")
			gopath = p[1]
			break
		}
	}

	if gopath == "" {
		return
	}

	codegopath = gopath

	return
}

//name could be ChaincodeID.Name or ChaincodeID.Path
func generateHashFromSignature(path string, ctor string, args []string) []byte {
	fargs := ctor
	if args != nil {
		for _, str := range args {
			fargs = fargs + str
		}
	}
	cbytes := []byte(path + fargs)

	b := make([]byte, len(cbytes))
	copy(b, cbytes)
	hash := util.ComputeCryptoHash(b)
	return hash
}

//generateHashcode gets hashcode of the code under path. If path is a HTTP(s) url
//it downloads the code first to compute the hash.
//NOTE: for dev mode, user builds and runs chaincode manually. The name provided
//by the user is equivalent to the path. This method will treat the name
//as codebytes and compute the hash from it. ie, user cannot run the chaincode
//with the same (name, ctor, args)
func generateHashcode(spec *pb.ChaincodeSpec, tw *tar.Writer) (string, error) {
	if spec == nil {
		return "", fmt.Errorf("Cannot generate hashcode from nil spec")
	}

	chaincodeID := spec.ChaincodeID
	if chaincodeID == nil || chaincodeID.Path == "" {
		return "", fmt.Errorf("Cannot generate hashcode from empty chaincode path")
	}

	ctor := spec.CtorMsg
	if ctor == nil || ctor.Function == "" {
		return "", fmt.Errorf("Cannot generate hashcode from empty ctor")
	}

	//code root will point to the directory where the code exists
	//in the case of http it will be a temporary dir that
	//will have to be deleted
	var codegopath string

	var ishttp bool
	defer func() {
		if ishttp && codegopath != "" {
			os.RemoveAll(codegopath)
		}
	}()

	path := chaincodeID.Path

	var err error
	var actualcodepath string
	if strings.HasPrefix(path, "http://") {
		ishttp = true
		actualcodepath = path[7:]
		codegopath, err = getCodeFromHTTP(actualcodepath)
	} else if strings.HasPrefix(path, "https://") {
		ishttp = true
		actualcodepath = path[8:]
		codegopath, err = getCodeFromHTTP(actualcodepath)
	} else {
		actualcodepath = path
		codegopath, err = getCodeFromFS(path)
	}

	if err != nil {
		return "", fmt.Errorf("Error getting code %s", err)
	}

	tmppath := codegopath + "/src/" + actualcodepath
	if err = isCodeExist(tmppath); err != nil {
		return "", fmt.Errorf("code does not exist %s", err)
	}

	hash := generateHashFromSignature(actualcodepath, ctor.Function, ctor.Args)

	hash, err = hashFilesInDir(codegopath+"/src/", actualcodepath, hash, tw)
	if err != nil {
		return "", fmt.Errorf("Could not get hashcode for %s - %s\n", path, err)
	}

	return hex.EncodeToString(hash[:]), nil
}
<|endoftext|>


<commit_before>// Copyright 2019 The Prometheus 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 https allows the implementation of TLS.
package https

import (
	"crypto/tls"
	"crypto/x509"
	"io/ioutil"
	"net/http"

	"github.com/pkg/errors"
	"gopkg.in/yaml.v2"
)

type Config struct {
	TLSConfig TLSStruct `yaml:"tlsConfig"`
}

type TLSStruct struct {
	TLSCertPath string `yaml:"tlsCertPath"`
	TLSKeyPath  string `yaml:"tlsKeyPath"`
	ClientAuth  string `yaml:"clientAuth"`
	ClientCAs   string `yaml:"clientCAs"`
}

func getTLSConfig(configPath string) (*tls.Config, error) {
	content, err := ioutil.ReadFile(configPath)
	if err != nil {
		return nil, err
	}
	c := &Config{}
	err = yaml.Unmarshal(content, c)
	if err != nil {
		return nil, err
	}
	return configToTLSConfig(&c.TLSConfig)
}

func configToTLSConfig(c *TLSStruct) (*tls.Config, error) {
	cfg := &tls.Config{}
	if len(c.TLSCertPath) == 0 {
		return nil, errors.New("missing TLSCertPath")
	}
	if len(c.TLSKeyPath) == 0 {
		return nil, errors.New("missing TLSKeyPath")
	}
	loadCert := func() (*tls.Certificate, error) {
		cert, err := tls.LoadX509KeyPair(c.TLSCertPath, c.TLSKeyPath)
		if err != nil {
			return nil, errors.Wrap(err, "failed to load X509KeyPair")
		}
		return &cert, nil
	}
	// Confirm that certificate and key paths are valid.
	if _, err := loadCert(); err != nil {
		return nil, err
	}
	cfg.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
		return loadCert()
	}

	if len(c.ClientCAs) > 0 {
		clientCAPool := x509.NewCertPool()
		clientCAFile, err := ioutil.ReadFile(c.ClientCAs)
		if err != nil {
			return nil, err
		}
		clientCAPool.AppendCertsFromPEM(clientCAFile)
		cfg.ClientCAs = clientCAPool
	}
	if len(c.ClientAuth) > 0 {
		switch s := (c.ClientAuth); s {
		case "NoClientCert":
			cfg.ClientAuth = tls.NoClientCert
		case "RequestClientCert":
			cfg.ClientAuth = tls.RequestClientCert
		case "RequireClientCert":
			cfg.ClientAuth = tls.RequireAnyClientCert
		case "VerifyClientCertIfGiven":
			cfg.ClientAuth = tls.VerifyClientCertIfGiven
		case "RequireAndVerifyClientCert":
			cfg.ClientAuth = tls.RequireAndVerifyClientCert
		case "":
			cfg.ClientAuth = tls.NoClientCert
		default:
			return nil, errors.New("Invalid ClientAuth: " + s)
		}
	}
	if len(c.ClientCAs) > 0 && cfg.ClientAuth == tls.NoClientCert {
		return nil, errors.New("Client CA's have been configured without a Client Auth Policy")
	}
	return cfg, nil
}

// Listen starts the server on the given address. If tlsConfigPath isn't empty the server connection will be started using TLS.
func Listen(server *http.Server, tlsConfigPath string) error {
	if (tlsConfigPath) == "" {
		return server.ListenAndServe()
	}
	var err error
	server.TLSConfig, err = getTLSConfig(tlsConfigPath)
	if err != nil {
		return err
	}
	// Set the GetConfigForClient method of the HTTPS server so that the config
	// and certs are reloaded on new connections.
	server.TLSConfig.GetConfigForClient = func(*tls.ClientHelloInfo) (*tls.Config, error) {
		return getTLSConfig(tlsConfigPath)
	}
	return server.ListenAndServeTLS("", "")
}
<commit_msg>Expose the function to generate tls.Config<commit_after>// Copyright 2019 The Prometheus 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 https allows the implementation of TLS.
package https

import (
	"crypto/tls"
	"crypto/x509"
	"io/ioutil"
	"net/http"

	"github.com/pkg/errors"
	"gopkg.in/yaml.v2"
)

type Config struct {
	TLSConfig TLSStruct `yaml:"tlsConfig"`
}

type TLSStruct struct {
	TLSCertPath string `yaml:"tlsCertPath"`
	TLSKeyPath  string `yaml:"tlsKeyPath"`
	ClientAuth  string `yaml:"clientAuth"`
	ClientCAs   string `yaml:"clientCAs"`
}

func getTLSConfig(configPath string) (*tls.Config, error) {
	content, err := ioutil.ReadFile(configPath)
	if err != nil {
		return nil, err
	}
	c := &Config{}
	err = yaml.Unmarshal(content, c)
	if err != nil {
		return nil, err
	}
	return ConfigToTLSConfig(&c.TLSConfig)
}

// ConfigToTLSConfig generates the golang tls.Config from the TLSStruct config.
func ConfigToTLSConfig(c *TLSStruct) (*tls.Config, error) {
	cfg := &tls.Config{}
	if len(c.TLSCertPath) == 0 {
		return nil, errors.New("missing TLSCertPath")
	}
	if len(c.TLSKeyPath) == 0 {
		return nil, errors.New("missing TLSKeyPath")
	}
	loadCert := func() (*tls.Certificate, error) {
		cert, err := tls.LoadX509KeyPair(c.TLSCertPath, c.TLSKeyPath)
		if err != nil {
			return nil, errors.Wrap(err, "failed to load X509KeyPair")
		}
		return &cert, nil
	}
	// Confirm that certificate and key paths are valid.
	if _, err := loadCert(); err != nil {
		return nil, err
	}
	cfg.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
		return loadCert()
	}

	if len(c.ClientCAs) > 0 {
		clientCAPool := x509.NewCertPool()
		clientCAFile, err := ioutil.ReadFile(c.ClientCAs)
		if err != nil {
			return nil, err
		}
		clientCAPool.AppendCertsFromPEM(clientCAFile)
		cfg.ClientCAs = clientCAPool
	}
	if len(c.ClientAuth) > 0 {
		switch s := (c.ClientAuth); s {
		case "NoClientCert":
			cfg.ClientAuth = tls.NoClientCert
		case "RequestClientCert":
			cfg.ClientAuth = tls.RequestClientCert
		case "RequireClientCert":
			cfg.ClientAuth = tls.RequireAnyClientCert
		case "VerifyClientCertIfGiven":
			cfg.ClientAuth = tls.VerifyClientCertIfGiven
		case "RequireAndVerifyClientCert":
			cfg.ClientAuth = tls.RequireAndVerifyClientCert
		case "":
			cfg.ClientAuth = tls.NoClientCert
		default:
			return nil, errors.New("Invalid ClientAuth: " + s)
		}
	}
	if len(c.ClientCAs) > 0 && cfg.ClientAuth == tls.NoClientCert {
		return nil, errors.New("Client CA's have been configured without a Client Auth Policy")
	}
	return cfg, nil
}

// Listen starts the server on the given address. If tlsConfigPath isn't empty the server connection will be started using TLS.
func Listen(server *http.Server, tlsConfigPath string) error {
	if (tlsConfigPath) == "" {
		return server.ListenAndServe()
	}
	var err error
	server.TLSConfig, err = getTLSConfig(tlsConfigPath)
	if err != nil {
		return err
	}
	// Set the GetConfigForClient method of the HTTPS server so that the config
	// and certs are reloaded on new connections.
	server.TLSConfig.GetConfigForClient = func(*tls.ClientHelloInfo) (*tls.Config, error) {
		return getTLSConfig(tlsConfigPath)
	}
	return server.ListenAndServeTLS("", "")
}
<|endoftext|>


<commit_before>package preconditions

import (
	"fmt"

	"github.com/Everlane/evan/common"

	"github.com/google/go-github/github"
)

type GithubCombinedStatusPrecondition struct{}

func (gh *GithubCombinedStatusPrecondition) Status(deployment common.Deployment) common.PreconditionResult {
	repo := deployment.Application().Repository()
	ref := deployment.SHA1()
	if ref == "" {
		ref = deployment.Ref()
	}

	client, err := common.GithubClient(deployment)
	if err != nil {
		return createResult(gh, err)
	}

	status, _, err := client.Repositories.GetCombinedStatus(repo.Owner(), repo.Name(), ref, nil)
	if err != nil {
		return createResult(gh, err)
	}

	state := *status.State
	totalCount := *status.TotalCount

	var result error = nil
	// GitHub will report a "pending" status if there are zero status checks,
	// therefore we want to only check the status if there are one or more
	// status checks.
	if totalCount > 0 && state != "success" {
		result = fmt.Errorf("Non-success status for ref: %v", *status.State)
	}
	return createResult(gh, result)
}

// Require that the branch for deployment not be behind the default branch
// on GitHub. If `AutoMerge` is true then it will try to create a merge via
// the GitHub API if the deployed ref is behind.
type GithubRequireAheadPrecondition struct {
	AutoMerge bool
}

type GithubRequireAheadContext struct {
	RepoClient *common.GithubRepository
	RepoDetails *github.Repository
}

// Compares the ref being deployed against the default branch on GitHub to
// determine whether or not a merge needs to happen. Returns `false` if it's
// a force deployment.
func (gh *GithubRequireAheadPrecondition) NeedsMerge(deployment common.Deployment, ctx *GithubRequireAheadContext) (bool, error) {
	if deployment.IsForce() {
		return false, nil
	}

	base := *ctx.RepoDetails.DefaultBranch
	head := deployment.Ref()

	comparison, err := ctx.RepoClient.CompareCommits(base, head)
	if err != nil {
		return false, err
	}

	return (*comparison.BehindBy > 0), nil
}

// Creates merge commit to get the target branch (deployment's ref) up-to-date
// with the default branch of the repository.
func (gh *GithubRequireAheadPrecondition) Merge(deployment common.Deployment, ctx *GithubRequireAheadContext) (string, error) {
	base := deployment.Ref()
	head := *ctx.RepoDetails.DefaultBranch
	commitMessage := fmt.Sprintf("Merge '%v' into '%v'", head, base)
	commit, err := ctx.RepoClient.Merge(base, head, commitMessage)
	if err != nil {
		return "", err
	} else {
		return *commit.SHA, nil
	}
}

func (gh *GithubRequireAheadPrecondition) Status(deployment common.Deployment) common.PreconditionResult {
	githubClient, err := common.GithubClient(deployment)
	if err != nil {
		return createResult(gh, err)
	}

	repoClient := &common.GithubRepository{
		Repository:   deployment.Application().Repository(),
		GithubClient: githubClient,
	}
	repoDetails, err := repoClient.Get()
	if err != nil {
		return createResult(gh, err)
	}

	ctx := &GithubRequireAheadContext{
		RepoClient: repoClient,
		RepoDetails: repoDetails,
	}

	needsMerge, err := gh.NeedsMerge(deployment, ctx)
	if err != nil {
		return createResult(gh, err)
	}

	// Halt if we don't need to merge!
	if !needsMerge {
		return createResult(gh, nil)
	}

	if !gh.AutoMerge {
		return createResult(gh, fmt.Errorf("Merge needed for ref '%v'", deployment.Ref()))
	}

	sha1, err := gh.Merge(deployment, ctx)
	if err != nil {
		return createResult(gh, err)
	}
	// Update the SHA1 to point to the new merge commit
	deployment.SetSHA1(sha1)

	return createResult(gh, nil)
}
<commit_msg>Allow skipping status checks for a deployment's commit if none defined<commit_after>package preconditions

import (
	"fmt"

	"github.com/Everlane/evan/common"

	"github.com/google/go-github/github"
)

type GithubCombinedStatusPrecondition struct {
	// If true then it will ignore the reported status if GitHub reports that
	// there are no status checks.
	AllowEmpty bool
}

func (gh *GithubCombinedStatusPrecondition) Status(deployment common.Deployment) common.PreconditionResult {
	repo := deployment.Application().Repository()
	ref := deployment.SHA1()
	if ref == "" {
		ref = deployment.Ref()
	}

	client, err := common.GithubClient(deployment)
	if err != nil {
		return createResult(gh, err)
	}

	status, _, err := client.Repositories.GetCombinedStatus(repo.Owner(), repo.Name(), ref, nil)
	if err != nil {
		return createResult(gh, err)
	}

	// Skip if there are no status checks and that's allowed
	if *status.TotalCount == 0 && gh.AllowEmpty {
		return createResult(gh, nil)
	}

	var result error = nil
	if *status.State != "success" {
		result = fmt.Errorf("Non-success status for ref: %v", *status.State)
	}
	return createResult(gh, result)
}

// Require that the branch for deployment not be behind the default branch
// on GitHub. If `AutoMerge` is true then it will try to create a merge via
// the GitHub API if the deployed ref is behind.
type GithubRequireAheadPrecondition struct {
	AutoMerge bool
}

type GithubRequireAheadContext struct {
	RepoClient *common.GithubRepository
	RepoDetails *github.Repository
}

// Compares the ref being deployed against the default branch on GitHub to
// determine whether or not a merge needs to happen. Returns `false` if it's
// a force deployment.
func (gh *GithubRequireAheadPrecondition) NeedsMerge(deployment common.Deployment, ctx *GithubRequireAheadContext) (bool, error) {
	if deployment.IsForce() {
		return false, nil
	}

	base := *ctx.RepoDetails.DefaultBranch
	head := deployment.Ref()

	comparison, err := ctx.RepoClient.CompareCommits(base, head)
	if err != nil {
		return false, err
	}

	return (*comparison.BehindBy > 0), nil
}

// Creates merge commit to get the target branch (deployment's ref) up-to-date
// with the default branch of the repository.
func (gh *GithubRequireAheadPrecondition) Merge(deployment common.Deployment, ctx *GithubRequireAheadContext) (string, error) {
	base := deployment.Ref()
	head := *ctx.RepoDetails.DefaultBranch
	commitMessage := fmt.Sprintf("Merge '%v' into '%v'", head, base)
	commit, err := ctx.RepoClient.Merge(base, head, commitMessage)
	if err != nil {
		return "", err
	} else {
		return *commit.SHA, nil
	}
}

func (gh *GithubRequireAheadPrecondition) Status(deployment common.Deployment) common.PreconditionResult {
	githubClient, err := common.GithubClient(deployment)
	if err != nil {
		return createResult(gh, err)
	}

	repoClient := &common.GithubRepository{
		Repository:   deployment.Application().Repository(),
		GithubClient: githubClient,
	}
	repoDetails, err := repoClient.Get()
	if err != nil {
		return createResult(gh, err)
	}

	ctx := &GithubRequireAheadContext{
		RepoClient: repoClient,
		RepoDetails: repoDetails,
	}

	needsMerge, err := gh.NeedsMerge(deployment, ctx)
	if err != nil {
		return createResult(gh, err)
	}

	// Halt if we don't need to merge!
	if !needsMerge {
		return createResult(gh, nil)
	}

	if !gh.AutoMerge {
		return createResult(gh, fmt.Errorf("Merge needed for ref '%v'", deployment.Ref()))
	}

	sha1, err := gh.Merge(deployment, ctx)
	if err != nil {
		return createResult(gh, err)
	}
	// Update the SHA1 to point to the new merge commit
	deployment.SetSHA1(sha1)

	return createResult(gh, nil)
}
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package tchannel

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/uber/tchannel-go/typed"
)

type testCallReq int

const (
	hasHeaders testCallReq = (1 << iota)
	hasChecksum
	totalCombinations
)

func (cr testCallReq) req() lazyCallReq {
	// TODO: Constructing a frame is ugly because the initial flags byte is
	// written in reqResWriter instead of callReq. We should instead handle that
	// in callReq, which will allow our tests to be sane.
	f := NewFrame(100)
	fh := fakeHeader()
	f.Header = fh
	fh.write(typed.NewWriteBuffer(f.headerBuffer))

	payload := typed.NewWriteBuffer(f.Payload)
	payload.WriteSingleByte(0)           // flags
	payload.WriteUint32(42)              // TTL
	payload.WriteBytes(make([]byte, 25)) // tracing
	payload.WriteLen8String("bankmoji")  // service

	if cr&hasHeaders == 0 {
		payload.WriteSingleByte(0) // number of headers
	} else {
		payload.WriteSingleByte(3)    // number of headers
		payload.WriteLen8String("k1") // header 1 key
		payload.WriteLen8String("v1") // header 1 value
		payload.WriteLen8String("k2") // header 2 key
		payload.WriteLen8String("v2") // header 2 value
		payload.WriteLen8String("k3") // header 3 key
		payload.WriteLen8String("v3") // header 3 value
	}

	if cr&hasChecksum == 0 {
		checksum := ChecksumTypeCrc32C
		payload.WriteSingleByte(byte(checksum)) // checksum type
		payload.WriteUint32(0)                  // checksum contents
	} else {
		checksum := ChecksumTypeNone
		payload.WriteSingleByte(byte(checksum)) // checksum type
		// no checksum contents for None
	}
	payload.WriteLen16String("moneys") // method
	return newLazyCallReq(f)
}

func TestLazyCallReqRejectsOtherFrames(t *testing.T) {
	msg := &initReq{initMessage{id: 1, Version: 0x1, initParams: initParams{
		InitParamHostPort:    "0.0.0.0:0",
		InitParamProcessName: "test",
	}}}
	frame := NewFrame(MaxFramePayloadSize)
	err := frame.write(msg)
	require.NoError(t, err, "Error writing message to frame.")
	assert.Panics(t, func() {
		newLazyCallReq(frame)
	}, "Should panic when creating lazyCallReq from non-callReq frame.")
}

func allFrameTypes() []testCallReq {
	combinations := make([]testCallReq, totalCombinations)
	for i := 0; i < int(totalCombinations); i++ {
		combinations[i] = testCallReq(combinations[i])
	}
	return combinations
}

func withLazyCallReqCombinations(f func(cr testCallReq)) {
	for cr := testCallReq(0); cr < totalCombinations; cr++ {
		f(cr)
	}
}

func TestLazyCallReqService(t *testing.T) {
	withLazyCallReqCombinations(func(crt testCallReq) {
		cr := crt.req()
		assert.Equal(t, "bankmoji", cr.Service(), "Service name mismatch")
	})
}

func TestLazyCallReqMethod(t *testing.T) {
	withLazyCallReqCombinations(func(crt testCallReq) {
		cr := crt.req()
		assert.Equal(t, "moneys", cr.Method(), "Method name mismatch")
	})
}

func TestLazyCallReqTTL(t *testing.T) {
	withLazyCallReqCombinations(func(crt testCallReq) {
		cr := crt.req()
		assert.Equal(t, 42*time.Millisecond, cr.TTL(), "Failed to parse TTL from frame.")
	})
}
<commit_msg>Remove unused method which isn't correct<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package tchannel

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/uber/tchannel-go/typed"
)

type testCallReq int

const (
	hasHeaders testCallReq = (1 << iota)
	hasChecksum
	totalCombinations
)

func (cr testCallReq) req() lazyCallReq {
	// TODO: Constructing a frame is ugly because the initial flags byte is
	// written in reqResWriter instead of callReq. We should instead handle that
	// in callReq, which will allow our tests to be sane.
	f := NewFrame(100)
	fh := fakeHeader()
	f.Header = fh
	fh.write(typed.NewWriteBuffer(f.headerBuffer))

	payload := typed.NewWriteBuffer(f.Payload)
	payload.WriteSingleByte(0)           // flags
	payload.WriteUint32(42)              // TTL
	payload.WriteBytes(make([]byte, 25)) // tracing
	payload.WriteLen8String("bankmoji")  // service

	if cr&hasHeaders == 0 {
		payload.WriteSingleByte(0) // number of headers
	} else {
		payload.WriteSingleByte(3)    // number of headers
		payload.WriteLen8String("k1") // header 1 key
		payload.WriteLen8String("v1") // header 1 value
		payload.WriteLen8String("k2") // header 2 key
		payload.WriteLen8String("v2") // header 2 value
		payload.WriteLen8String("k3") // header 3 key
		payload.WriteLen8String("v3") // header 3 value
	}

	if cr&hasChecksum == 0 {
		checksum := ChecksumTypeCrc32C
		payload.WriteSingleByte(byte(checksum)) // checksum type
		payload.WriteUint32(0)                  // checksum contents
	} else {
		checksum := ChecksumTypeNone
		payload.WriteSingleByte(byte(checksum)) // checksum type
		// no checksum contents for None
	}
	payload.WriteLen16String("moneys") // method
	return newLazyCallReq(f)
}

func TestLazyCallReqRejectsOtherFrames(t *testing.T) {
	msg := &initReq{initMessage{id: 1, Version: 0x1, initParams: initParams{
		InitParamHostPort:    "0.0.0.0:0",
		InitParamProcessName: "test",
	}}}
	frame := NewFrame(MaxFramePayloadSize)
	err := frame.write(msg)
	require.NoError(t, err, "Error writing message to frame.")
	assert.Panics(t, func() {
		newLazyCallReq(frame)
	}, "Should panic when creating lazyCallReq from non-callReq frame.")
}

func withLazyCallReqCombinations(f func(cr testCallReq)) {
	for cr := testCallReq(0); cr < totalCombinations; cr++ {
		f(cr)
	}
}

func TestLazyCallReqService(t *testing.T) {
	withLazyCallReqCombinations(func(crt testCallReq) {
		cr := crt.req()
		assert.Equal(t, "bankmoji", cr.Service(), "Service name mismatch")
	})
}

func TestLazyCallReqMethod(t *testing.T) {
	withLazyCallReqCombinations(func(crt testCallReq) {
		cr := crt.req()
		assert.Equal(t, "moneys", cr.Method(), "Method name mismatch")
	})
}

func TestLazyCallReqTTL(t *testing.T) {
	withLazyCallReqCombinations(func(crt testCallReq) {
		cr := crt.req()
		assert.Equal(t, 42*time.Millisecond, cr.TTL(), "Failed to parse TTL from frame.")
	})
}
<|endoftext|>