<commit_before>package cs

import (
	"github.com/denverdino/aliyungo/common"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

type ServerlessCreationArgs struct {
	ClusterType          KubernetesClusterType `json:"cluster_type"`
	Name                 string                `json:"name"`
	RegionId             string                `json:"region_id"`
	VpcId                string                `json:"vpc_id"`
	VSwitchId            string                `json:"vswitch_id"`
	VswitchIds           []string              `json:"vswitch_ids"`
	EndpointPublicAccess bool                  `json:"public_slb"`
	PrivateZone          bool                  `json:"private_zone"`
	NatGateway           bool                  `json:"nat_gateway"`
	DeletionProtection   bool                  `json:"deletion_protection"`
	SecurityGroupId      string                `json:"security_group_id"`
	Tags                 []Tag                 `json:"tags"`
	Addons               []Addon               `json:"addons"`
}

type ServerlessClusterResponse struct {
	ClusterId          string                `json:"cluster_id"`
	Name               string                `json:"name"`
	ClusterType        KubernetesClusterType `json:"cluster_type"`
	RegionId           string                `json:"region_id"`
	State              ClusterState          `json:"state"`
	VpcId              string                `json:"vpc_id"`
	VSwitchId          string                `json:"vswitch_id"`
	SecurityGroupId    string                `json:"security_group_id"`
	Tags               []Tag                 `json:"tags"`
	Created            time.Time             `json:"created"`
	Updated            time.Time             `json:"updated"`
	InitVersion        string                `json:"init_version"`
	CurrentVersion     string                `json:"current_version"`
	PrivateZone        bool                  `json:"private_zone"`
	DeletionProtection bool                  `json:"deletion_protection"`
}

type Tag struct {
	Key   string `json:"key"`
	Value string `json:"value"`
}

func (this *ServerlessCreationArgs) Validate() error {
	if this.Name == "" || this.RegionId == "" || this.VpcId == "" {
		return common.GetCustomError("InvalidParameters", "The name,region_id,vpc_id not allowed empty")
	}
	return nil
}

//create Serverless cluster
func (client *Client) CreateServerlessKubernetesCluster(args *ServerlessCreationArgs) (*ClusterCommonResponse, error) {
	if args == nil {
		return nil, common.GetCustomError("InvalidArgs", "The args is nil ")
	}

	if err := args.Validate(); err != nil {
		return nil, err
	}

	//reset clusterType,
	args.ClusterType = ServerlessKubernetes
	cluster := &ClusterCommonResponse{}
	err := client.Invoke(common.Region(args.RegionId), http.MethodPost, "/clusters", nil, args, &cluster)
	if err != nil {
		return nil, err
	}
	return cluster, nil
}

//describe Serverless cluster
func (client *Client) DescribeServerlessKubernetesCluster(clusterId string) (*ServerlessClusterResponse, error) {
	cluster := &ServerlessClusterResponse{}
	err := client.Invoke("", http.MethodGet, "/clusters/"+clusterId, nil, nil, cluster)
	if err != nil {
		return nil, err
	}
	return cluster, nil
}

//describe Serverless clsuters
func (client *Client) DescribeServerlessKubernetesClusters() ([]*ServerlessClusterResponse, error) {
	allClusters := make([]*ServerlessClusterResponse, 0)
	askClusters := make([]*ServerlessClusterResponse, 0)

	err := client.Invoke("", http.MethodGet, "/clusters", nil, nil, &allClusters)
	if err != nil {
		return askClusters, err
	}

	for _, cluster := range allClusters {
		if cluster.ClusterType == ClusterTypeServerlessKubernetes {
			askClusters = append(askClusters, cluster)
		}
	}

	return askClusters, nil
}

//new api for get cluster kube user config
func (client *Client) DescribeClusterUserConfig(clusterId string, privateIpAddress bool) (*ClusterConfig, error) {
	config := &ClusterConfig{}
	query := url.Values{}
	query.Add("PrivateIpAddress", strconv.FormatBool(privateIpAddress))

	err := client.Invoke("", http.MethodGet, "/k8s/"+clusterId+"/user_config", query, nil, &config)
	if err != nil {
		return nil, err
	}
	return config, nil
}
<commit_msg>create serverless cluster set k8s version<commit_after>package cs

import (
	"github.com/denverdino/aliyungo/common"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

type ServerlessCreationArgs struct {
	ClusterType          KubernetesClusterType `json:"cluster_type"`
	Name                 string                `json:"name"`
	RegionId             string                `json:"region_id"`
	VpcId                string                `json:"vpc_id"`
	VSwitchId            string                `json:"vswitch_id"`
	VswitchIds           []string              `json:"vswitch_ids"`
	EndpointPublicAccess bool                  `json:"public_slb"`
	PrivateZone          bool                  `json:"private_zone"`
	NatGateway           bool                  `json:"nat_gateway"`
	KubernetesVersion    string                `json:"kubernetes_version"`
	DeletionProtection   bool                  `json:"deletion_protection"`
	SecurityGroupId      string                `json:"security_group_id"`
	Tags                 []Tag                 `json:"tags"`
	Addons               []Addon               `json:"addons"`
}

type ServerlessClusterResponse struct {
	ClusterId          string                `json:"cluster_id"`
	Name               string                `json:"name"`
	ClusterType        KubernetesClusterType `json:"cluster_type"`
	RegionId           string                `json:"region_id"`
	State              ClusterState          `json:"state"`
	VpcId              string                `json:"vpc_id"`
	VSwitchId          string                `json:"vswitch_id"`
	SecurityGroupId    string                `json:"security_group_id"`
	Tags               []Tag                 `json:"tags"`
	Created            time.Time             `json:"created"`
	Updated            time.Time             `json:"updated"`
	InitVersion        string                `json:"init_version"`
	CurrentVersion     string                `json:"current_version"`
	PrivateZone        bool                  `json:"private_zone"`
	DeletionProtection bool                  `json:"deletion_protection"`
}

type Tag struct {
	Key   string `json:"key"`
	Value string `json:"value"`
}

func (this *ServerlessCreationArgs) Validate() error {
	if this.Name == "" || this.RegionId == "" || this.VpcId == "" {
		return common.GetCustomError("InvalidParameters", "The name,region_id,vpc_id not allowed empty")
	}
	return nil
}

//create Serverless cluster
func (client *Client) CreateServerlessKubernetesCluster(args *ServerlessCreationArgs) (*ClusterCommonResponse, error) {
	if args == nil {
		return nil, common.GetCustomError("InvalidArgs", "The args is nil ")
	}

	if err := args.Validate(); err != nil {
		return nil, err
	}

	//reset clusterType,
	args.ClusterType = ServerlessKubernetes
	cluster := &ClusterCommonResponse{}
	err := client.Invoke(common.Region(args.RegionId), http.MethodPost, "/clusters", nil, args, &cluster)
	if err != nil {
		return nil, err
	}
	return cluster, nil
}

//describe Serverless cluster
func (client *Client) DescribeServerlessKubernetesCluster(clusterId string) (*ServerlessClusterResponse, error) {
	cluster := &ServerlessClusterResponse{}
	err := client.Invoke("", http.MethodGet, "/clusters/"+clusterId, nil, nil, cluster)
	if err != nil {
		return nil, err
	}
	return cluster, nil
}

//describe Serverless clsuters
func (client *Client) DescribeServerlessKubernetesClusters() ([]*ServerlessClusterResponse, error) {
	allClusters := make([]*ServerlessClusterResponse, 0)
	askClusters := make([]*ServerlessClusterResponse, 0)

	err := client.Invoke("", http.MethodGet, "/clusters", nil, nil, &allClusters)
	if err != nil {
		return askClusters, err
	}

	for _, cluster := range allClusters {
		if cluster.ClusterType == ClusterTypeServerlessKubernetes {
			askClusters = append(askClusters, cluster)
		}
	}

	return askClusters, nil
}

//new api for get cluster kube user config
func (client *Client) DescribeClusterUserConfig(clusterId string, privateIpAddress bool) (*ClusterConfig, error) {
	config := &ClusterConfig{}
	query := url.Values{}
	query.Add("PrivateIpAddress", strconv.FormatBool(privateIpAddress))

	err := client.Invoke("", http.MethodGet, "/k8s/"+clusterId+"/user_config", query, nil, &config)
	if err != nil {
		return nil, err
	}
	return config, nil
}
<|endoftext|>


<commit_before>package provider

import (
	"encoding/base64"
	"errors"
	"time"

	"github.com/NetSys/di/db"
	"github.com/NetSys/di/dsl"

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

	log "github.com/Sirupsen/logrus"
)

const spotPrice = "0.5"

// Ubuntu 15.10, us-west-2, 64-bit hvm-ssd
const ami = "ami-acd63bcc"
const awsRegion = "us-west-2"

type awsSpotCluster struct {
	*ec2.EC2

	namespace  string
	aclTrigger db.Trigger
}

func (clst *awsSpotCluster) Start(conn db.Conn, clusterID int, namespace string) error {
	session := session.New()
	session.Config.Region = aws.String(awsRegion)

	clst.EC2 = ec2.New(session)
	clst.namespace = namespace
	clst.aclTrigger = conn.TriggerTick(60, db.ClusterTable)

	go clst.watchACLs(conn, clusterID)

	return nil
}

func (clst *awsSpotCluster) Disconnect() {
	/* Ideally we'd close clst.ec2 as well, but the API doesn't export that ability
	* apparently. */
	clst.aclTrigger.Stop()
}

func (clst awsSpotCluster) Boot(bootSet []Machine) error {
	if len(bootSet) <= 0 {
		return nil
	}

	one := int64(1)
	var spotIds []string
	for _, m := range bootSet {
		cloudConfig64 := base64.StdEncoding.EncodeToString([]byte(cloudConfigUbuntu(m.SSHKeys, "wily")))
		resp, err := clst.RequestSpotInstances(&ec2.RequestSpotInstancesInput{
			SpotPrice: aws.String(spotPrice),
			LaunchSpecification: &ec2.RequestSpotLaunchSpecification{
				ImageId:        aws.String(ami),
				InstanceType:   aws.String(m.Size),
				UserData:       &cloudConfig64,
				SecurityGroups: []*string{&clst.namespace},
			},
			InstanceCount: &one,
		})

		if err != nil {
			return err
		}

		for _, request := range resp.SpotInstanceRequests {
			spotIds = append(spotIds, *request.SpotInstanceRequestId)
		}
	}

	if err := clst.tagSpotRequests(spotIds); err != nil {
		return err
	}

	if err := clst.wait(spotIds, true); err != nil {
		return err
	}

	return nil
}

func (clst awsSpotCluster) Stop(ids []string) error {
	spots, err := clst.DescribeSpotInstanceRequests(
		&ec2.DescribeSpotInstanceRequestsInput{
			SpotInstanceRequestIds: aws.StringSlice(ids),
		})
	if err != nil {
		return err
	}

	instIds := []string{}
	for _, spot := range spots.SpotInstanceRequests {
		if spot.InstanceId != nil {
			instIds = append(instIds, *spot.InstanceId)
		}
	}

	if len(instIds) > 0 {
		_, err = clst.TerminateInstances(&ec2.TerminateInstancesInput{
			InstanceIds: aws.StringSlice(instIds),
		})
		if err != nil {
			return err
		}
	}

	_, err = clst.CancelSpotInstanceRequests(&ec2.CancelSpotInstanceRequestsInput{
		SpotInstanceRequestIds: aws.StringSlice(ids),
	})
	if err != nil {
		return err
	}

	if err := clst.wait(ids, false); err != nil {
		return err
	}

	return nil
}

func (clst awsSpotCluster) Get() ([]Machine, error) {
	spots, err := clst.DescribeSpotInstanceRequests(nil)
	if err != nil {
		return nil, err
	}

	insts, err := clst.DescribeInstances(&ec2.DescribeInstancesInput{
		Filters: []*ec2.Filter{
			{
				Name:   aws.String("instance.group-name"),
				Values: []*string{aws.String(clst.namespace)},
			},
		},
	})
	if err != nil {
		return nil, err
	}

	instMap := make(map[string]*ec2.Instance)
	for _, res := range insts.Reservations {
		for _, inst := range res.Instances {
			instMap[*inst.InstanceId] = inst
		}
	}

	machines := []Machine{}
	for _, spot := range spots.SpotInstanceRequests {
		if *spot.State != ec2.SpotInstanceStateActive &&
			*spot.State != ec2.SpotInstanceStateOpen {
			continue
		}

		var inst *ec2.Instance
		if spot.InstanceId != nil {
			inst = instMap[*spot.InstanceId]
		}

		// Due to a race condition in the AWS API, it's possible that spot
		// requests might lose their Tags.  If handled naively, those spot
		// requests would technically be without a namespace, meaning the
		// instances they create would be live forever as zombies.
		//
		// To mitigate this issue, we rely not only on the spot request tags, but
		// additionally on the instance security group.  If a spot request has a
		// running instance in the appropriate security group, it is by
		// definition in our namespace.  Thus, we only check the tags for spot
		// requests without running instances.
		if inst == nil {
			var isOurs bool
			for _, tag := range spot.Tags {
				ns := clst.namespace
				if tag != nil && tag.Key != nil && *tag.Key == ns {
					isOurs = true
					break
				}
			}

			if !isOurs {
				continue
			}
		}

		machine := Machine{
			ID:       *spot.SpotInstanceRequestId,
			Provider: db.AmazonSpot,
		}

		if inst != nil {
			if *inst.State.Name != ec2.InstanceStateNamePending &&
				*inst.State.Name != ec2.InstanceStateNameRunning {
				continue
			}

			if inst.PublicIpAddress != nil {
				machine.PublicIP = *inst.PublicIpAddress
			}

			if inst.PrivateIpAddress != nil {
				machine.PrivateIP = *inst.PrivateIpAddress
			}

			if inst.InstanceType != nil {
				machine.Size = *inst.InstanceType
			}
		}

		machines = append(machines, machine)
	}

	return machines, nil
}

func (clst *awsSpotCluster) PickBestSize(ram dsl.Range, cpu dsl.Range, maxPrice float64) string {
	return pickBestSize(awsDescriptions, ram, cpu, maxPrice)
}

func (clst *awsSpotCluster) tagSpotRequests(spotIds []string) error {
	var err error
	for i := 0; i < 30; i++ {
		_, err = clst.CreateTags(&ec2.CreateTagsInput{
			Tags: []*ec2.Tag{
				{Key: aws.String(clst.namespace), Value: aws.String("")},
			},
			Resources: aws.StringSlice(spotIds),
		})
		if err == nil {
			return nil
		}
		time.Sleep(5 * time.Second)
	}

	log.Warn("Failed to tag spot requests: ", err)
	clst.CancelSpotInstanceRequests(
		&ec2.CancelSpotInstanceRequestsInput{
			SpotInstanceRequestIds: aws.StringSlice(spotIds),
		})

	return err
}

/* Wait for the spot request 'ids' to have booted or terminated depending on the value of
* 'boot' */
func (clst *awsSpotCluster) wait(ids []string, boot bool) error {
OuterLoop:
	for i := 0; i < 100; i++ {
		machines, err := clst.Get()
		if err != nil {
			log.WithError(err).Warn("Failed to get machines.")
			time.Sleep(2 * time.Second)
			continue
		}

		exists := make(map[string]struct{})
		for _, inst := range machines {
			exists[inst.ID] = struct{}{}
		}

		for _, id := range ids {
			if _, ok := exists[id]; ok != boot {
				time.Sleep(2 * time.Second)
				continue OuterLoop
			}
		}

		return nil
	}

	return errors.New("timed out")
}

func (clst *awsSpotCluster) updateSecurityGroups(acls []string) error {
	resp, err := clst.DescribeSecurityGroups(
		&ec2.DescribeSecurityGroupsInput{
			Filters: []*ec2.Filter{
				{
					Name:   aws.String("group-name"),
					Values: []*string{aws.String(clst.namespace)},
				},
			},
		})

	if err != nil {
		return err
	}

	ingress := []*ec2.IpPermission{}
	groups := resp.SecurityGroups
	if len(groups) > 1 {
		return errors.New("Multiple Security Groups with the same name: " +
			clst.namespace)
	} else if len(groups) == 0 {
		clst.CreateSecurityGroup(&ec2.CreateSecurityGroupInput{
			Description: aws.String("Declarative Infrastructure Group"),
			GroupName:   aws.String(clst.namespace),
		})
	} else {
		/* XXX: Deal with egress rules. */
		ingress = groups[0].IpPermissions
	}

	permMap := make(map[string]bool)
	for _, acl := range acls {
		permMap[acl] = true
	}

	groupIngressExists := false
	for i, p := range ingress {
		if (i > 0 || p.FromPort != nil || p.ToPort != nil ||
			*p.IpProtocol != "-1") && p.UserIdGroupPairs == nil {
			log.Info("Revoke ingress security group: ", *p)
			_, err = clst.RevokeSecurityGroupIngress(
				&ec2.RevokeSecurityGroupIngressInput{
					GroupName:     aws.String(clst.namespace),
					IpPermissions: []*ec2.IpPermission{p}})
			if err != nil {
				return err
			}

			continue
		}

		for _, ipr := range p.IpRanges {
			ip := *ipr.CidrIp
			if !permMap[ip] {
				log.Info("Revoke ingress security group: ", ip)
				_, err = clst.RevokeSecurityGroupIngress(
					&ec2.RevokeSecurityGroupIngressInput{
						GroupName:  aws.String(clst.namespace),
						CidrIp:     aws.String(ip),
						FromPort:   p.FromPort,
						IpProtocol: p.IpProtocol,
						ToPort:     p.ToPort})
				if err != nil {
					return err
				}
			} else {
				permMap[ip] = false
			}
		}

		if len(groups) > 0 {
			for _, grp := range p.UserIdGroupPairs {
				if *grp.GroupId != *groups[0].GroupId {
					log.Info("Revoke ingress security group GroupID: ",
						*grp.GroupId)
					_, err = clst.RevokeSecurityGroupIngress(
						&ec2.RevokeSecurityGroupIngressInput{
							GroupName:               aws.String(clst.namespace),
							SourceSecurityGroupName: grp.GroupName})
					if err != nil {
						return err
					}
				} else {
					groupIngressExists = true
				}
			}
		}
	}

	if !groupIngressExists {
		log.Info("Add intragroup ACL")
		_, err = clst.AuthorizeSecurityGroupIngress(
			&ec2.AuthorizeSecurityGroupIngressInput{
				GroupName:               aws.String(clst.namespace),
				SourceSecurityGroupName: aws.String(clst.namespace)})
	}

	for perm, install := range permMap {
		if !install {
			continue
		}

		log.Info("Add ACL: ", perm)
		_, err = clst.AuthorizeSecurityGroupIngress(
			&ec2.AuthorizeSecurityGroupIngressInput{
				CidrIp:     aws.String(perm),
				GroupName:  aws.String(clst.namespace),
				IpProtocol: aws.String("-1")})

		if err != nil {
			return err
		}
	}

	return nil
}

func (clst *awsSpotCluster) watchACLs(conn db.Conn, clusterID int) {
	for range clst.aclTrigger.C {
		var acls []string
		conn.Transact(func(view db.Database) error {
			clusters := view.SelectFromCluster(func(c db.Cluster) bool {
				return c.ID == clusterID
			})

			if len(clusters) == 0 {
				log.Warn("Undefined cluster.")
				return nil
			} else if len(clusters) > 1 {
				panic("Duplicate Clusters")
			}

			acls = clusters[0].AdminACL
			return nil
		})

		clst.updateSecurityGroups(acls)
	}
}
<commit_msg>awsSpot: Batch boot requests<commit_after>package provider

import (
	"encoding/base64"
	"errors"
	"time"

	"github.com/NetSys/di/db"
	"github.com/NetSys/di/dsl"

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

	log "github.com/Sirupsen/logrus"
)

const spotPrice = "0.5"

// Ubuntu 15.10, us-west-2, 64-bit hvm-ssd
const ami = "ami-acd63bcc"
const awsRegion = "us-west-2"

type awsSpotCluster struct {
	*ec2.EC2

	namespace  string
	aclTrigger db.Trigger
}

func (clst *awsSpotCluster) Start(conn db.Conn, clusterID int, namespace string) error {
	session := session.New()
	session.Config.Region = aws.String(awsRegion)

	clst.EC2 = ec2.New(session)
	clst.namespace = namespace
	clst.aclTrigger = conn.TriggerTick(60, db.ClusterTable)

	go clst.watchACLs(conn, clusterID)

	return nil
}

func (clst *awsSpotCluster) Disconnect() {
	/* Ideally we'd close clst.ec2 as well, but the API doesn't export that ability
	* apparently. */
	clst.aclTrigger.Stop()
}

func (clst awsSpotCluster) Boot(bootSet []Machine) error {
	if len(bootSet) <= 0 {
		return nil
	}

	type bootReq struct {
		cfg  string
		size string
	}

	bootReqMap := make(map[bootReq]int64) // From boot request to an instance count.
	for _, m := range bootSet {
		br := bootReq{
			cfg:  cloudConfigUbuntu(m.SSHKeys, "wily"),
			size: m.Size,
		}
		bootReqMap[br] = bootReqMap[br] + 1
	}

	var spotIds []string
	for br, count := range bootReqMap {
		cloudConfig64 := base64.StdEncoding.EncodeToString([]byte(br.cfg))
		resp, err := clst.RequestSpotInstances(&ec2.RequestSpotInstancesInput{
			SpotPrice: aws.String(spotPrice),
			LaunchSpecification: &ec2.RequestSpotLaunchSpecification{
				ImageId:        aws.String(ami),
				InstanceType:   aws.String(br.size),
				UserData:       &cloudConfig64,
				SecurityGroups: []*string{&clst.namespace},
			},
			InstanceCount: &count,
		})

		if err != nil {
			return err
		}

		for _, request := range resp.SpotInstanceRequests {
			spotIds = append(spotIds, *request.SpotInstanceRequestId)
		}
	}

	if err := clst.tagSpotRequests(spotIds); err != nil {
		return err
	}

	if err := clst.wait(spotIds, true); err != nil {
		return err
	}

	return nil
}

func (clst awsSpotCluster) Stop(ids []string) error {
	spots, err := clst.DescribeSpotInstanceRequests(
		&ec2.DescribeSpotInstanceRequestsInput{
			SpotInstanceRequestIds: aws.StringSlice(ids),
		})
	if err != nil {
		return err
	}

	instIds := []string{}
	for _, spot := range spots.SpotInstanceRequests {
		if spot.InstanceId != nil {
			instIds = append(instIds, *spot.InstanceId)
		}
	}

	if len(instIds) > 0 {
		_, err = clst.TerminateInstances(&ec2.TerminateInstancesInput{
			InstanceIds: aws.StringSlice(instIds),
		})
		if err != nil {
			return err
		}
	}

	_, err = clst.CancelSpotInstanceRequests(&ec2.CancelSpotInstanceRequestsInput{
		SpotInstanceRequestIds: aws.StringSlice(ids),
	})
	if err != nil {
		return err
	}

	if err := clst.wait(ids, false); err != nil {
		return err
	}

	return nil
}

func (clst awsSpotCluster) Get() ([]Machine, error) {
	spots, err := clst.DescribeSpotInstanceRequests(nil)
	if err != nil {
		return nil, err
	}

	insts, err := clst.DescribeInstances(&ec2.DescribeInstancesInput{
		Filters: []*ec2.Filter{
			{
				Name:   aws.String("instance.group-name"),
				Values: []*string{aws.String(clst.namespace)},
			},
		},
	})
	if err != nil {
		return nil, err
	}

	instMap := make(map[string]*ec2.Instance)
	for _, res := range insts.Reservations {
		for _, inst := range res.Instances {
			instMap[*inst.InstanceId] = inst
		}
	}

	machines := []Machine{}
	for _, spot := range spots.SpotInstanceRequests {
		if *spot.State != ec2.SpotInstanceStateActive &&
			*spot.State != ec2.SpotInstanceStateOpen {
			continue
		}

		var inst *ec2.Instance
		if spot.InstanceId != nil {
			inst = instMap[*spot.InstanceId]
		}

		// Due to a race condition in the AWS API, it's possible that spot
		// requests might lose their Tags.  If handled naively, those spot
		// requests would technically be without a namespace, meaning the
		// instances they create would be live forever as zombies.
		//
		// To mitigate this issue, we rely not only on the spot request tags, but
		// additionally on the instance security group.  If a spot request has a
		// running instance in the appropriate security group, it is by
		// definition in our namespace.  Thus, we only check the tags for spot
		// requests without running instances.
		if inst == nil {
			var isOurs bool
			for _, tag := range spot.Tags {
				ns := clst.namespace
				if tag != nil && tag.Key != nil && *tag.Key == ns {
					isOurs = true
					break
				}
			}

			if !isOurs {
				continue
			}
		}

		machine := Machine{
			ID:       *spot.SpotInstanceRequestId,
			Provider: db.AmazonSpot,
		}

		if inst != nil {
			if *inst.State.Name != ec2.InstanceStateNamePending &&
				*inst.State.Name != ec2.InstanceStateNameRunning {
				continue
			}

			if inst.PublicIpAddress != nil {
				machine.PublicIP = *inst.PublicIpAddress
			}

			if inst.PrivateIpAddress != nil {
				machine.PrivateIP = *inst.PrivateIpAddress
			}

			if inst.InstanceType != nil {
				machine.Size = *inst.InstanceType
			}
		}

		machines = append(machines, machine)
	}

	return machines, nil
}

func (clst *awsSpotCluster) PickBestSize(ram dsl.Range, cpu dsl.Range, maxPrice float64) string {
	return pickBestSize(awsDescriptions, ram, cpu, maxPrice)
}

func (clst *awsSpotCluster) tagSpotRequests(spotIds []string) error {
	var err error
	for i := 0; i < 30; i++ {
		_, err = clst.CreateTags(&ec2.CreateTagsInput{
			Tags: []*ec2.Tag{
				{Key: aws.String(clst.namespace), Value: aws.String("")},
			},
			Resources: aws.StringSlice(spotIds),
		})
		if err == nil {
			return nil
		}
		time.Sleep(5 * time.Second)
	}

	log.Warn("Failed to tag spot requests: ", err)
	clst.CancelSpotInstanceRequests(
		&ec2.CancelSpotInstanceRequestsInput{
			SpotInstanceRequestIds: aws.StringSlice(spotIds),
		})

	return err
}

/* Wait for the spot request 'ids' to have booted or terminated depending on the value of
* 'boot' */
func (clst *awsSpotCluster) wait(ids []string, boot bool) error {
OuterLoop:
	for i := 0; i < 100; i++ {
		machines, err := clst.Get()
		if err != nil {
			log.WithError(err).Warn("Failed to get machines.")
			time.Sleep(2 * time.Second)
			continue
		}

		exists := make(map[string]struct{})
		for _, inst := range machines {
			exists[inst.ID] = struct{}{}
		}

		for _, id := range ids {
			if _, ok := exists[id]; ok != boot {
				time.Sleep(2 * time.Second)
				continue OuterLoop
			}
		}

		return nil
	}

	return errors.New("timed out")
}

func (clst *awsSpotCluster) updateSecurityGroups(acls []string) error {
	resp, err := clst.DescribeSecurityGroups(
		&ec2.DescribeSecurityGroupsInput{
			Filters: []*ec2.Filter{
				{
					Name:   aws.String("group-name"),
					Values: []*string{aws.String(clst.namespace)},
				},
			},
		})

	if err != nil {
		return err
	}

	ingress := []*ec2.IpPermission{}
	groups := resp.SecurityGroups
	if len(groups) > 1 {
		return errors.New("Multiple Security Groups with the same name: " +
			clst.namespace)
	} else if len(groups) == 0 {
		clst.CreateSecurityGroup(&ec2.CreateSecurityGroupInput{
			Description: aws.String("Declarative Infrastructure Group"),
			GroupName:   aws.String(clst.namespace),
		})
	} else {
		/* XXX: Deal with egress rules. */
		ingress = groups[0].IpPermissions
	}

	permMap := make(map[string]bool)
	for _, acl := range acls {
		permMap[acl] = true
	}

	groupIngressExists := false
	for i, p := range ingress {
		if (i > 0 || p.FromPort != nil || p.ToPort != nil ||
			*p.IpProtocol != "-1") && p.UserIdGroupPairs == nil {
			log.Info("Revoke ingress security group: ", *p)
			_, err = clst.RevokeSecurityGroupIngress(
				&ec2.RevokeSecurityGroupIngressInput{
					GroupName:     aws.String(clst.namespace),
					IpPermissions: []*ec2.IpPermission{p}})
			if err != nil {
				return err
			}

			continue
		}

		for _, ipr := range p.IpRanges {
			ip := *ipr.CidrIp
			if !permMap[ip] {
				log.Info("Revoke ingress security group: ", ip)
				_, err = clst.RevokeSecurityGroupIngress(
					&ec2.RevokeSecurityGroupIngressInput{
						GroupName:  aws.String(clst.namespace),
						CidrIp:     aws.String(ip),
						FromPort:   p.FromPort,
						IpProtocol: p.IpProtocol,
						ToPort:     p.ToPort})
				if err != nil {
					return err
				}
			} else {
				permMap[ip] = false
			}
		}

		if len(groups) > 0 {
			for _, grp := range p.UserIdGroupPairs {
				if *grp.GroupId != *groups[0].GroupId {
					log.Info("Revoke ingress security group GroupID: ",
						*grp.GroupId)
					_, err = clst.RevokeSecurityGroupIngress(
						&ec2.RevokeSecurityGroupIngressInput{
							GroupName:               aws.String(clst.namespace),
							SourceSecurityGroupName: grp.GroupName})
					if err != nil {
						return err
					}
				} else {
					groupIngressExists = true
				}
			}
		}
	}

	if !groupIngressExists {
		log.Info("Add intragroup ACL")
		_, err = clst.AuthorizeSecurityGroupIngress(
			&ec2.AuthorizeSecurityGroupIngressInput{
				GroupName:               aws.String(clst.namespace),
				SourceSecurityGroupName: aws.String(clst.namespace)})
	}

	for perm, install := range permMap {
		if !install {
			continue
		}

		log.Info("Add ACL: ", perm)
		_, err = clst.AuthorizeSecurityGroupIngress(
			&ec2.AuthorizeSecurityGroupIngressInput{
				CidrIp:     aws.String(perm),
				GroupName:  aws.String(clst.namespace),
				IpProtocol: aws.String("-1")})

		if err != nil {
			return err
		}
	}

	return nil
}

func (clst *awsSpotCluster) watchACLs(conn db.Conn, clusterID int) {
	for range clst.aclTrigger.C {
		var acls []string
		conn.Transact(func(view db.Database) error {
			clusters := view.SelectFromCluster(func(c db.Cluster) bool {
				return c.ID == clusterID
			})

			if len(clusters) == 0 {
				log.Warn("Undefined cluster.")
				return nil
			} else if len(clusters) > 1 {
				panic("Duplicate Clusters")
			}

			acls = clusters[0].AdminACL
			return nil
		})

		clst.updateSecurityGroups(acls)
	}
}
<|endoftext|>


<commit_before>package gke

import (
	"context"
	"log"
	"time"

	gke "cloud.google.com/go/container/apiv1"

	containerpb "google.golang.org/genproto/googleapis/container/v1"
	"gopkg.in/alecthomas/kingpin.v2"
	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
)

const (
	cloudScope        = "https://www.googleapis.com/auth/cloud-platform"
	monitorWriteScope = "https://www.googleapis.com/auth/monitoring.write"
	storageReadScope  = "https://www.googleapis.com/auth/devstorage.read_only"
	statusRunning     = "RUNNING"
)

// Cluster holds the fields used to generate an API request.
type Cluster struct {
	// ProjectID is the ID of your project to use when creating a cluster.
	ProjectID string
	// Enable the dashboard.
	Dashboard bool
	// The zone to launch the cluster
	Zone string
	// The number of nodes to create in this cluster
	NodeCount int32
	// The authentication information for accessing the master
	MasterAuth *containerpb.MasterAuth
	// The name of this cluster
	Name     string
	NodePool string
	// Configuration for the Kubernetes Dashboard
	KubernetesDashboard bool
	// The service client used when performing different requests.
	client *gke.ClusterManagerClient
	ctx    context.Context
}

// New sets the GKE client used when authenticating each API request.
func (c *Cluster) New(*kingpin.ParseContext) error {
	// See https://cloud.google.com/docs/authentication/.
	// Use GOOGLE_APPLICATION_CREDENTIALS environment variable to specify
	// a service account key file to authenticate to the API.

	client, err := gke.NewClusterManagerClient(context.Background())
	if err != nil {
		log.Fatalf("Could not create the client: %v", err)
	}
	c.client = client
	c.ctx = context.Background()

	return nil
}

// List current k8s clusters.
func (c *Cluster) List(*kingpin.ParseContext) error {

	req := &containerpb.ListClustersRequest{
		ProjectId: c.ProjectID,
		Zone:      c.Zone,
	}
	list, err := c.client.ListClusters(c.ctx, req)
	if err != nil {
		log.Fatalf("failed to list clusters: %v", err)
	}
	for _, v := range list.Clusters {
		log.Printf("Cluster %q (%s) master_version: v%s", v.Name, v.Status, v.CurrentMasterVersion)
	}
	return nil
}

// Create a new k8s cluster
func (c *Cluster) Create(*kingpin.ParseContext) error {
	req := &containerpb.CreateClusterRequest{
		ProjectId: c.ProjectID,
		Zone:      c.Zone,
		Cluster: &containerpb.Cluster{
			Name:             c.Name,
			InitialNodeCount: c.NodeCount,
			// If unspecified, the defaults are used.
			NodeConfig: &containerpb.NodeConfig{},
			// The authentication information for accessing the master endpoint.
			MasterAuth: &containerpb.MasterAuth{},

			AddonsConfig: &containerpb.AddonsConfig{
				KubernetesDashboard: &containerpb.KubernetesDashboard{
					Disabled: c.Dashboard,
				},
			},

			// ResourceLabels map[string]string `protobuf:"bytes,15,rep,name=resource_labels,json=resourceLabels" json:"resource_labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
		},
	}
	log.Printf("Cluster request: %+v", req)

	rep, err := c.client.CreateCluster(c.ctx, req)

	if err != nil {
		log.Fatalf("Couldn't create a cluster:%v", err)
	}
	log.Printf("Cluster %s create is called for project %s and zone %s. Status %v", rep.Name, c.ProjectID, rep.Zone, rep.Status)

	return c.waitForCluster()
}

// Delete a k8s cluster.
func (c *Cluster) Delete(*kingpin.ParseContext) error {
	log.Printf("Removing cluster %v from project %v, zone %v", c.Name, c.ProjectID, c.Zone)

	req := &containerpb.DeleteClusterRequest{
		ProjectId: c.ProjectID,
		Zone:      c.Zone,
		ClusterId: c.Name,
	}

	if _, err := c.client.DeleteCluster(c.ctx, req); err != nil {
		log.Fatal(err)
	}

	log.Printf("Cluster %s set for deletion", c.Name)
	return nil
}

// // Apply a aminfest to the k8s cluster.
// func (c *Cluster) Apply(*kingpin.ParseContext) error {
// 	// create the clientset
// 	clientset, err := kubernetes.NewForConfig(config)
// 	if err != nil {
// 		panic(err.Error())
// 	}
// 	for {
// 		pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{})
// 		if err != nil {
// 			panic(err.Error())
// 		}
// 		fmt.Printf("There are %d pods in the cluster\n", len(pods.Items))

// 		// Examples for error handling:
// 		// - Use helper functions like e.g. errors.IsNotFound()
// 		// - And/or cast to StatusError and use its properties like e.g. ErrStatus.Message
// 		namespace := "default"
// 		pod := "example-xxxxx"
// 		_, err = clientset.CoreV1().Pods(namespace).Get(pod, metav1.GetOptions{})
// 		if errors.IsNotFound(err) {
// 			fmt.Printf("Pod %s in namespace %s not found\n", pod, namespace)
// 		} else if statusError, isStatus := err.(*errors.StatusError); isStatus {
// 			fmt.Printf("Error getting pod %s in namespace %s: %v\n",
// 				pod, namespace, statusError.ErrStatus.Message)
// 		} else if err != nil {
// 			panic(err.Error())
// 		} else {
// 			fmt.Printf("Found pod %s in namespace %s\n", pod, namespace)
// 		}

// 		time.Sleep(10 * time.Second)
// 	}
// 	return nil
// }

func (c *Cluster) waitForCluster() error {
	req := &containerpb.GetClusterRequest{
		ProjectId: c.ProjectID,
		Zone:      c.Zone,
		ClusterId: c.Name,
	}
	for {
		cluster, err := c.client.GetCluster(c.ctx, req)
		if err != nil {
			log.Fatalf("Couldn't get cluster info:%v", err)
		}
		if cluster.Status == containerpb.Cluster_RUNNING {
			log.Printf("Cluster %v is running", c.Name)
			return nil
		}
		log.Printf("%v cluster %v", cluster.Status, c.Name)
		retry := time.Second * 10
		log.Printf("cluster not ready, retrying in %v", retry)
		time.Sleep(retry)
	}
}

func (c *Cluster) waitForNodePool() error {
	req := &containerpb.GetNodePoolRequest{
		ProjectId:  c.ProjectID,
		Zone:       c.Zone,
		ClusterId:  c.Name,
		NodePoolId: c.NodePool,
	}
	for {
		nodepool, err := c.client.GetNodePool(c.ctx, req)
		if err != nil {
			log.Fatalf("Couldn't get node pool info:%v", err)
		}
		if nodepool.Status == containerpb.NodePool_RUNNING {
			log.Printf("Nodepool %v is running", c.Name)
			return nil
		}
		log.Printf("%v nodepool %v", nodepool.Status, c.Name)
		retry := time.Second * 10
		log.Printf("cluster not ready, retrying in %v", retry)
		time.Sleep(retry)
	}
}
<commit_msg>removed bosoleete constants<commit_after>package gke

import (
	"context"
	"log"
	"time"

	gke "cloud.google.com/go/container/apiv1"

	containerpb "google.golang.org/genproto/googleapis/container/v1"
	"gopkg.in/alecthomas/kingpin.v2"
	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
)

// Cluster holds the fields used to generate an API request.
type Cluster struct {
	// ProjectID is the ID of your project to use when creating a cluster.
	ProjectID string
	// Enable the dashboard.
	Dashboard bool
	// The zone to launch the cluster
	Zone string
	// The number of nodes to create in this cluster
	NodeCount int32
	// The authentication information for accessing the master
	MasterAuth *containerpb.MasterAuth
	// The name of this cluster
	Name     string
	NodePool string
	// Configuration for the Kubernetes Dashboard
	KubernetesDashboard bool
	// The service client used when performing different requests.
	client *gke.ClusterManagerClient
	ctx    context.Context
}

// New sets the GKE client used when authenticating each API request.
func (c *Cluster) New(*kingpin.ParseContext) error {
	// See https://cloud.google.com/docs/authentication/.
	// Use GOOGLE_APPLICATION_CREDENTIALS environment variable to specify
	// a service account key file to authenticate to the API.

	client, err := gke.NewClusterManagerClient(context.Background())
	if err != nil {
		log.Fatalf("Could not create the client: %v", err)
	}
	c.client = client
	c.ctx = context.Background()

	return nil
}

// List current k8s clusters.
func (c *Cluster) List(*kingpin.ParseContext) error {

	req := &containerpb.ListClustersRequest{
		ProjectId: c.ProjectID,
		Zone:      c.Zone,
	}
	list, err := c.client.ListClusters(c.ctx, req)
	if err != nil {
		log.Fatalf("failed to list clusters: %v", err)
	}
	for _, v := range list.Clusters {
		log.Printf("Cluster %q (%s) master_version: v%s", v.Name, v.Status, v.CurrentMasterVersion)
	}
	return nil
}

// Create a new k8s cluster
func (c *Cluster) Create(*kingpin.ParseContext) error {
	req := &containerpb.CreateClusterRequest{
		ProjectId: c.ProjectID,
		Zone:      c.Zone,
		Cluster: &containerpb.Cluster{
			Name:             c.Name,
			InitialNodeCount: c.NodeCount,
			// If unspecified, the defaults are used.
			NodeConfig: &containerpb.NodeConfig{},
			// The authentication information for accessing the master endpoint.
			MasterAuth: &containerpb.MasterAuth{},

			AddonsConfig: &containerpb.AddonsConfig{
				KubernetesDashboard: &containerpb.KubernetesDashboard{
					Disabled: c.Dashboard,
				},
			},

			// ResourceLabels map[string]string `protobuf:"bytes,15,rep,name=resource_labels,json=resourceLabels" json:"resource_labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
		},
	}
	log.Printf("Cluster request: %+v", req)

	rep, err := c.client.CreateCluster(c.ctx, req)

	if err != nil {
		log.Fatalf("Couldn't create a cluster:%v", err)
	}
	log.Printf("Cluster %s create is called for project %s and zone %s. Status %v", rep.Name, c.ProjectID, rep.Zone, rep.Status)

	return c.waitForCluster()
}

// Delete a k8s cluster.
func (c *Cluster) Delete(*kingpin.ParseContext) error {
	log.Printf("Removing cluster %v from project %v, zone %v", c.Name, c.ProjectID, c.Zone)

	req := &containerpb.DeleteClusterRequest{
		ProjectId: c.ProjectID,
		Zone:      c.Zone,
		ClusterId: c.Name,
	}

	if _, err := c.client.DeleteCluster(c.ctx, req); err != nil {
		log.Fatal(err)
	}

	log.Printf("Cluster %s set for deletion", c.Name)
	return nil
}

// // Apply a aminfest to the k8s cluster.
// func (c *Cluster) Apply(*kingpin.ParseContext) error {
// 	// create the clientset
// 	clientset, err := kubernetes.NewForConfig(config)
// 	if err != nil {
// 		panic(err.Error())
// 	}
// 	for {
// 		pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{})
// 		if err != nil {
// 			panic(err.Error())
// 		}
// 		fmt.Printf("There are %d pods in the cluster\n", len(pods.Items))

// 		// Examples for error handling:
// 		// - Use helper functions like e.g. errors.IsNotFound()
// 		// - And/or cast to StatusError and use its properties like e.g. ErrStatus.Message
// 		namespace := "default"
// 		pod := "example-xxxxx"
// 		_, err = clientset.CoreV1().Pods(namespace).Get(pod, metav1.GetOptions{})
// 		if errors.IsNotFound(err) {
// 			fmt.Printf("Pod %s in namespace %s not found\n", pod, namespace)
// 		} else if statusError, isStatus := err.(*errors.StatusError); isStatus {
// 			fmt.Printf("Error getting pod %s in namespace %s: %v\n",
// 				pod, namespace, statusError.ErrStatus.Message)
// 		} else if err != nil {
// 			panic(err.Error())
// 		} else {
// 			fmt.Printf("Found pod %s in namespace %s\n", pod, namespace)
// 		}

// 		time.Sleep(10 * time.Second)
// 	}
// 	return nil
// }

func (c *Cluster) waitForCluster() error {
	req := &containerpb.GetClusterRequest{
		ProjectId: c.ProjectID,
		Zone:      c.Zone,
		ClusterId: c.Name,
	}
	for {
		cluster, err := c.client.GetCluster(c.ctx, req)
		if err != nil {
			log.Fatalf("Couldn't get cluster info:%v", err)
		}
		if cluster.Status == containerpb.Cluster_RUNNING {
			log.Printf("Cluster %v is running", c.Name)
			return nil
		}
		log.Printf("%v cluster %v", cluster.Status, c.Name)
		retry := time.Second * 10
		log.Printf("cluster not ready, retrying in %v", retry)
		time.Sleep(retry)
	}
}

func (c *Cluster) waitForNodePool() error {
	req := &containerpb.GetNodePoolRequest{
		ProjectId:  c.ProjectID,
		Zone:       c.Zone,
		ClusterId:  c.Name,
		NodePoolId: c.NodePool,
	}
	for {
		nodepool, err := c.client.GetNodePool(c.ctx, req)
		if err != nil {
			log.Fatalf("Couldn't get node pool info:%v", err)
		}
		if nodepool.Status == containerpb.NodePool_RUNNING {
			log.Printf("Nodepool %v is running", c.Name)
			return nil
		}
		log.Printf("%v nodepool %v", nodepool.Status, c.Name)
		retry := time.Second * 10
		log.Printf("cluster not ready, retrying in %v", retry)
		time.Sleep(retry)
	}
}
<|endoftext|>


<commit_before>package provider

import (
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	fsnotify "gopkg.in/fsnotify.v1"

	"github.com/BurntSushi/toml"
	"github.com/Nitro/sidecar/catalog"
	"github.com/Nitro/sidecar/service"
	"github.com/containous/flaeg"
	"github.com/containous/traefik/log"
	"github.com/containous/traefik/safe"
	"github.com/containous/traefik/types"
)

const (
	method = "wrr"
	weight = 0
	sticky = false
)

var _ Provider = (*Sidecar)(nil)

// Sidecar holds configurations of the Sidecar provider
type Sidecar struct {
	BaseProvider      `mapstructure:",squash"`
	Endpoint          string `description:"Sidecar URL"`
	Frontend          string `description:"Configuration file for frontend"`
	configurationChan chan<- types.ConfigMessage
	RefreshConn       flaeg.Duration `description:"How often to refresh the connection to Sidecar backend"`
	connTimer         *time.Timer
}

type callback func(map[string][]*service.Service, error)

// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *Sidecar) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
	provider.configurationChan = configurationChan
	if provider.Watch {
		safe.Go(func() {
			provider.sidecarWatcher()
		})

		watcher, err := fsnotify.NewWatcher()
		if err != nil {
			log.Errorln("Error creating file watcher", err)
			return err
		}

		file, err := os.Open(provider.Frontend)
		if err != nil {
			log.Errorln("Error opening file", err)
			return err
		}
		defer file.Close()
		pool.Go(func(stop chan bool) {
			defer watcher.Close()
			for {
				select {
				case <-stop:
					return
				case event := <-watcher.Events:
					if strings.Contains(event.Name, file.Name()) {
						log.Debug("Sidecar Frontend File event:", event)
						states, errState := provider.fetchState()
						if errState != nil {
							log.Errorln("Error reloading Sidecar config", errState)
						}
						provider.loadSidecarConfig(states.ByService())
					}
				case errWatcher := <-watcher.Errors:
					log.Errorln("Watcher event error", errWatcher)
				}
			}
		})
		err = watcher.Add(filepath.Dir(file.Name()))
		if err != nil {
			log.Error("Error adding file watcher", err)
			return err
		}
	}
	states, err := provider.fetchState()
	if err != nil {
		log.Fatalln("Error reloading Sidecar config", err)
	}
	err = provider.loadSidecarConfig(states.ByService())
	if err != nil {
		return err
	}
	return nil
}

func (provider *Sidecar) constructConfig(sidecarStates map[string][]*service.Service) (*types.Configuration, error) {
	log.Infoln("loading sidecar config")
	sidecarConfig := types.Configuration{Backends: provider.makeBackends(sidecarStates)}
	var err error
	sidecarConfig.Frontends, err = provider.makeFrontend()
	if err != nil {
		return nil, err
	}
	return &sidecarConfig, nil
}

func (provider *Sidecar) loadSidecarConfig(sidecarStates map[string][]*service.Service) error {
	conf, err := provider.constructConfig(sidecarStates)
	if err != nil {
		return err
	}
	provider.configurationChan <- types.ConfigMessage{
		ProviderName:  "sidecar",
		Configuration: conf,
	}
	return nil
}

func (provider *Sidecar) sidecarWatcher() error {
	//set timeout to be just a bot more than connection refresh interval
	provider.connTimer = time.NewTimer(time.Duration(provider.RefreshConn))
	tr := &http.Transport{ResponseHeaderTimeout: 0}
	client := &http.Client{
		Timeout:   0,
		Transport: tr}
	log.Debugf("Using %s Sidecar connection refresh interval", provider.RefreshConn)
	provider.recycleConn(client, tr)
	return nil
}

func (provider *Sidecar) recycleConn(client *http.Client, tr *http.Transport) {
	var err error
	var resp *http.Response
	var req *http.Request
	for { //use refresh interval to occasionally reconnect to Sidecar in case the stream connection is lost
		req, err = http.NewRequest("GET", provider.Endpoint+"/watch", nil)
		if err != nil {
			log.Errorf("Error creating http request to Sidecar: %s, Error: %s", provider.Endpoint, err)
			continue
		}
		resp, err = client.Do(req)
		if err != nil {
			log.Errorf("Error connecting to Sidecar: %s, Error: %s", provider.Endpoint, err)
			time.Sleep(5 * time.Second)
			continue
		}
		safe.Go(func() { catalog.DecodeStream(resp.Body, provider.callbackLoader) })

		//wait on refresh connection timer.  If this expires we haven't seen an update in a
		//while and should cancel the request, reset the time, and reconnect just in case
		<-provider.connTimer.C
		provider.connTimer.Reset(time.Duration(provider.RefreshConn))
		tr.CancelRequest(req)
	}
}

func (provider *Sidecar) callbackLoader(sidecarStates map[string][]*service.Service, err error) {
	//load config regardless
	provider.loadSidecarConfig(sidecarStates)

	if err != nil {
		return
	}
	//else reset connection timer
	if !provider.connTimer.Stop() {
		<-provider.connTimer.C
	}
	provider.connTimer.Reset(time.Duration(provider.RefreshConn))
	return
}

func (provider *Sidecar) makeFrontend() (map[string]*types.Frontend, error) {
	configuration := new(types.Configuration)
	if _, err := toml.DecodeFile(provider.Frontend, configuration); err != nil {
		log.Errorf("Error reading file: %s", err)
		return nil, err
	}
	return configuration.Frontends, nil
}

func (provider *Sidecar) makeBackends(sidecarStates map[string][]*service.Service) map[string]*types.Backend {
	sidecarBacks := make(map[string]*types.Backend)
	for serviceName, services := range sidecarStates {
		newServers := make(map[string]types.Server)
		newBackend := &types.Backend{LoadBalancer: &types.LoadBalancer{Method: method, Sticky: sticky},
			Servers: newServers}
		for _, serv := range services {
			if serv.IsAlive() {
				for i := 0; i < len(serv.Ports); i++ {
					ipAddr, err := net.LookupIP(serv.Hostname)
					if err != nil {
						log.Errorln("Error resolving Ip address, ", err)
						newBackend.Servers[serv.Hostname] = types.Server{URL: "http://" + serv.Hostname + ":" + strconv.FormatInt(serv.Ports[i].Port, 10)}
					} else {
						newBackend.Servers[serv.Hostname] = types.Server{URL: "http://" + ipAddr[0].String() + ":" + strconv.FormatInt(serv.Ports[i].Port, 10)}
					}
				}
			}
		}
		sidecarBacks[serviceName] = newBackend
	}
	return sidecarBacks
}

func (provider *Sidecar) fetchState() (*catalog.ServicesState, error) {
	client := &http.Client{Timeout: 5 * time.Second}
	resp, err := client.Get(provider.Endpoint + "/state.json")
	if err != nil {
		return nil, err
	}

	bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	state, err := catalog.Decode(bytes)
	if err != nil {
		return nil, err
	}
	return state, nil
}
<commit_msg>Fix resource leak<commit_after>package provider

import (
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	fsnotify "gopkg.in/fsnotify.v1"

	"github.com/BurntSushi/toml"
	"github.com/Nitro/sidecar/catalog"
	"github.com/Nitro/sidecar/service"
	"github.com/containous/flaeg"
	"github.com/containous/traefik/log"
	"github.com/containous/traefik/safe"
	"github.com/containous/traefik/types"
)

const (
	method = "wrr"
	weight = 0
	sticky = false
)

var _ Provider = (*Sidecar)(nil)

// Sidecar holds configurations of the Sidecar provider
type Sidecar struct {
	BaseProvider      `mapstructure:",squash"`
	Endpoint          string `description:"Sidecar URL"`
	Frontend          string `description:"Configuration file for frontend"`
	configurationChan chan<- types.ConfigMessage
	RefreshConn       flaeg.Duration `description:"How often to refresh the connection to Sidecar backend"`
	connTimer         *time.Timer
}

type callback func(map[string][]*service.Service, error)

// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *Sidecar) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
	provider.configurationChan = configurationChan
	if provider.Watch {
		safe.Go(func() {
			provider.sidecarWatcher()
		})

		watcher, err := fsnotify.NewWatcher()
		if err != nil {
			log.Errorln("Error creating file watcher", err)
			return err
		}

		file, err := os.Open(provider.Frontend)
		if err != nil {
			log.Errorln("Error opening file", err)
			return err
		}
		defer file.Close()
		pool.Go(func(stop chan bool) {
			defer watcher.Close()
			for {
				select {
				case <-stop:
					return
				case event := <-watcher.Events:
					if strings.Contains(event.Name, file.Name()) {
						log.Debug("Sidecar Frontend File event:", event)
						states, errState := provider.fetchState()
						if errState != nil {
							log.Errorln("Error reloading Sidecar config", errState)
						}
						provider.loadSidecarConfig(states.ByService())
					}
				case errWatcher := <-watcher.Errors:
					log.Errorln("Watcher event error", errWatcher)
				}
			}
		})
		err = watcher.Add(filepath.Dir(file.Name()))
		if err != nil {
			log.Error("Error adding file watcher", err)
			return err
		}
	}
	states, err := provider.fetchState()
	if err != nil {
		log.Fatalln("Error reloading Sidecar config", err)
	}
	err = provider.loadSidecarConfig(states.ByService())
	if err != nil {
		return err
	}
	return nil
}

func (provider *Sidecar) constructConfig(sidecarStates map[string][]*service.Service) (*types.Configuration, error) {
	log.Infoln("loading sidecar config")
	sidecarConfig := types.Configuration{Backends: provider.makeBackends(sidecarStates)}
	var err error
	sidecarConfig.Frontends, err = provider.makeFrontend()
	if err != nil {
		return nil, err
	}
	return &sidecarConfig, nil
}

func (provider *Sidecar) loadSidecarConfig(sidecarStates map[string][]*service.Service) error {
	conf, err := provider.constructConfig(sidecarStates)
	if err != nil {
		return err
	}
	provider.configurationChan <- types.ConfigMessage{
		ProviderName:  "sidecar",
		Configuration: conf,
	}
	return nil
}

func (provider *Sidecar) sidecarWatcher() error {
	//set timeout to be just a bot more than connection refresh interval
	provider.connTimer = time.NewTimer(time.Duration(provider.RefreshConn))
	tr := &http.Transport{ResponseHeaderTimeout: 0}
	client := &http.Client{
		Timeout:   0,
		Transport: tr}
	log.Debugf("Using %s Sidecar connection refresh interval", provider.RefreshConn)
	provider.recycleConn(client, tr)
	return nil
}

func (provider *Sidecar) recycleConn(client *http.Client, tr *http.Transport) {
	var err error
	var resp *http.Response
	var req *http.Request
	for { //use refresh interval to occasionally reconnect to Sidecar in case the stream connection is lost
		req, err = http.NewRequest("GET", provider.Endpoint+"/watch", nil)
		if err != nil {
			log.Errorf("Error creating http request to Sidecar: %s, Error: %s", provider.Endpoint, err)
			continue
		}
		resp, err = client.Do(req)
		if err != nil {
			log.Errorf("Error connecting to Sidecar: %s, Error: %s", provider.Endpoint, err)
			time.Sleep(5 * time.Second)
			continue
		}
		safe.Go(func() { catalog.DecodeStream(resp.Body, provider.callbackLoader) })

		//wait on refresh connection timer.  If this expires we haven't seen an update in a
		//while and should cancel the request, reset the time, and reconnect just in case
		<-provider.connTimer.C
		provider.connTimer.Reset(time.Duration(provider.RefreshConn))
		tr.CancelRequest(req)
	}
}

func (provider *Sidecar) callbackLoader(sidecarStates map[string][]*service.Service, err error) {
	//load config regardless
	provider.loadSidecarConfig(sidecarStates)

	if err != nil {
		return
	}
	//else reset connection timer
	if !provider.connTimer.Stop() {
		<-provider.connTimer.C
	}
	provider.connTimer.Reset(time.Duration(provider.RefreshConn))
	return
}

func (provider *Sidecar) makeFrontend() (map[string]*types.Frontend, error) {
	configuration := new(types.Configuration)
	if _, err := toml.DecodeFile(provider.Frontend, configuration); err != nil {
		log.Errorf("Error reading file: %s", err)
		return nil, err
	}
	return configuration.Frontends, nil
}

func (provider *Sidecar) makeBackends(sidecarStates map[string][]*service.Service) map[string]*types.Backend {
	sidecarBacks := make(map[string]*types.Backend)
	for serviceName, services := range sidecarStates {
		newServers := make(map[string]types.Server)
		newBackend := &types.Backend{LoadBalancer: &types.LoadBalancer{Method: method, Sticky: sticky},
			Servers: newServers}
		for _, serv := range services {
			if serv.IsAlive() {
				for i := 0; i < len(serv.Ports); i++ {
					ipAddr, err := net.LookupIP(serv.Hostname)
					if err != nil {
						log.Errorln("Error resolving Ip address, ", err)
						newBackend.Servers[serv.Hostname] = types.Server{URL: "http://" + serv.Hostname + ":" + strconv.FormatInt(serv.Ports[i].Port, 10)}
					} else {
						newBackend.Servers[serv.Hostname] = types.Server{URL: "http://" + ipAddr[0].String() + ":" + strconv.FormatInt(serv.Ports[i].Port, 10)}
					}
				}
			}
		}
		sidecarBacks[serviceName] = newBackend
	}
	return sidecarBacks
}

func (provider *Sidecar) fetchState() (*catalog.ServicesState, error) {
	client := &http.Client{Timeout: 5 * time.Second}
	resp, err := client.Get(provider.Endpoint + "/state.json")
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	state, err := catalog.Decode(bytes)
	if err != nil {
		return nil, err
	}
	return state, nil
}
<|endoftext|>


<commit_before>package moxxiConf

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"strings"
	"text/template"
)

func LoadConfig() ([]string, []HandlerConfig, Err) {
	config, err := prepConfig()
	if err != nil {
		return []string{}, []HandlerConfig{}, err
	}

	if err = validateConfig(config); err != nil {
		return []string{}, []HandlerConfig{}, err
	}

	handlers, listens, err := loadConfig(config)
	if err != nil {
		return []string{}, []HandlerConfig{}, err
	}

	return handlers, listens, nil
}

func prepConfig() (*map[string]interface{}, Err) {

	possibleConfigs := []string{
		"./config.json",
		"/etc/moxxi/config.json",
		"$HOME/.moxxi/config.json",
		"./moxxi.config.json",
	}

	var data []byte
	var globErr error
	var c map[string]interface{}
ValidConfig:
	for _, configTry := range possibleConfigs {
		data, globErr = ioutil.ReadFile(configTry)
		switch {
		case os.IsNotExist(globErr):
		case globErr == nil:
			break ValidConfig
		default:
			return nil, NewErr{
				Code:    ErrConfigBadRead,
				value:   configTry,
				deepErr: globErr,
			}
		}
	}

	globErr = json.Unmarshal(data, &c)
	if globErr != nil {
		return nil, UpgradeError(globErr)
	}

	if _, ok := c["listen"]; !ok {
		c["listen"] = []string{"localhost:8080"}
	}

	return &c, nil
}

func validateConfig(dirtyConfig *map[string]interface{}) Err {
	c := *dirtyConfig

	// clean up array top level lines
	for _, part := range []string{"listen", "exclude"} {
		if _, ok := c[part]; ok {
			chkArray, ok := c[part].([]interface{})
			if !ok {
				c[part] = []interface{}{c[part]}
				chkArray = []interface{}{c[part]}
			}

			// cannot type assert to ([]string) so i have to go through the parts?
			for _, each := range chkArray {
				if _, ok = each.(string); !ok {
					return NewErr{
						Code:    ErrConfigBadStructure,
						value:   part,
						deepErr: fmt.Errorf("%T - %#v", c[part], c[part]),
					}
				}
			}
		}
	}

	// check that the following things are strings in the top level
	for _, part := range []string{
		"baseURL",
		"confPath",
		"confExt",
		"confFile",
		"resFile",
		"ipFile",
		"accessLog",
		"errorLog"
	} {
		if _, ok := c[part]; ok {
			if _, ok := c[part].(string); !ok {
				return NewErr{Code: ErrConfigBadStructure, value: part}
			}
		}
	}

	if _, ok := c["subdomainLen"]; ok {
		switch c["subdomainLen"].(type) {
		case int:
		case float64:
			c["subdomainLen"] = int(c["subdomainLen"].(float64))
		default:
			return NewErr{
				Code:  ErrConfigBadStructure,
				value: "subdomainLen",
				deepErr: fmt.Errorf("handler sub portion wrong type %T - %#v",
					c["subdomainLen"], c["subdomainLen"]),
			}
		}
		if c["subdomainLen"].(int) < 8 {
			c["subdomainLen"] = 8
		}
	}

	if _, ok := c["redirectTracing"]; ok {
		if _, ok = c["redirectTracing"].(bool); !ok {
			return NewErr{
				Code:    ErrConfigBadStructure,
				value:   "redirectTracing",
				deepErr: fmt.Errorf("%T - %#v", c["redirectTracing"], c["redirectTracing"]),
			}
		}
	}

	// verify the handlers are an array
	handlers, ok := c["handler"].([]interface{})
	if !ok {
		return NewErr{
			Code:  ErrConfigBadStructure,
			value: "handler",
		}
	}

	// test and propagate handlers
	for id, _ := range handlers {
		locErr := validateConfigHandler(&c, id)
		if locErr != nil {
			return locErr
		}
	}

	dirtyConfig = &c

	return nil
}

func validateConfigHandler(pConfig *map[string]interface{}, id int) Err {

	// unpack everything
	c := *pConfig
	var allHandlers []interface{}
	var h map[string]interface{}
	var ok bool

	if allHandlers, ok = c["handler"].([]interface{}); !ok {
		return NewErr{
			Code:  ErrConfigBadStructure,
			value: "handler",
		}
	}
	if h, ok = allHandlers[id].(map[string]interface{}); !ok {
		return NewErr{
			Code:  ErrConfigBadStructure,
			value: "handler",
		}
	}

	switch h["handlerType"] {
	case "static":
	case "form":
	case "json":
	default:
		return NewErr{
			Code:  ErrConfigBadStructure,
			value: "handlerType",
		}
	}

	if loc, ok := h["handlerRoute"].(string); !ok {
		return NewErr{
			Code:  ErrConfigBadStructure,
			value: "handlerRoute",
		}
	} else if !strings.HasSuffix(loc, "/") {
		h["handlerRoute"] = loc + "/"
	}

	// check the exclude
	if _, ok := h["exclude"]; ok {
		// if it exists, make it an array
		if _, ok := h["exclude"].([]interface{}); !ok {
			h["exclude"] = []interface{}{h["exclude"]}
		}

		// cannot type assert to ([]string) so have to go through the parts
		// check to make sure it's a string array
		if _, ok := h["exclude"].([]string); !ok {
			return NewErr{Code: ErrConfigBadStructure, value: "exclude"}
		}
	} else {
		// if it does not exist, propagate it from above
		if _, ok := c["exclude"]; ok {
			h["exclude"] = c["exclude"]
		} else {
			h["exclude"] = []string{}
		}
	}

	// check the following things - validate and propagate them
	for _, part := range []string{
		"baseURL",
		"confPath",
		"confExt",
		"confFile",
		"resFile",
		"ipFile",
	} {
		if _, ok := h[part]; ok {
			if _, ok := h[part].(string); !ok {
				return NewErr{
					Code:    ErrConfigBadStructure,
					value:   part,
					deepErr: fmt.Errorf("handler portion - %T - %#v", h[part], h[part]),
				}
			}
		} else {
			if _, ok := c[part]; ok {
				h[part] = c[part]
			} else {
				h[part] = ""
			}
		}
	}

	if _, ok = h["subdomainLen"]; ok {
		switch h["subdomainLen"].(type) {
		case int:
		case float64:
			h["subdomainLen"] = int(h["subdomainLen"].(float64))
		default:
			return NewErr{
				Code:  ErrConfigBadStructure,
				value: "subdomainLen",
				deepErr: fmt.Errorf("handler sub portion wrong type %T - %#v",
					h["subdomainLen"], h["subdomainLen"]),
			}
		}
		if h["subdomainLen"].(int) < 8 {
			h["subdomainLen"] = 8
		}
	} else {
		if _, ok := c["subdomainLen"]; ok {
			h["subdomainLen"] = c["subdomainLen"]
		} else {
			h["subdomainLen"] = 8
		}
	}

	if _, ok = h["redirectTracing"]; ok {
		if _, ok = h["redirectTracing"].(bool); !ok {
			return NewErr{
				Code:    ErrConfigBadStructure,
				value:   "redirectTracing",
				deepErr: fmt.Errorf("%T - %#v", c["redirectTracing"], c["redirectTracing"]),
			}
		}
	} else {
		if _, ok := c["redirectTracing"]; ok {
			h["redirectTracing"] = c["redirectTracing"]
		}
	}

	// pack things back in
	allHandlers[id] = h
	c["handler"] = allHandlers
	pConfig = &c
	return nil
}

func loadConfig(pConfig *map[string]interface{}) (
	[]string, string, string, []HandlerConfig, Err) {

	c := *pConfig
	var ok bool
	var listens []string
	var accessLog, errorLog string

	if untypedListens, ok := c["listen"].([]interface{}); ok {
		for _, each := range untypedListens {
			if oneListen, ok := each.(string); ok {
				listens = append(listens, oneListen)
			} else {
				return nil, []string{}, []HandlerConfig{}, NewErr{
					Code:    ErrConfigLoadType,
					value:   "listen",
					deepErr: fmt.Errorf("wrong type of %T - %#v", each, each),
				}
			}
		}
	} else {
		return nil, []string{}, []HandlerConfig{}, NewErr{
			Code:    ErrConfigLoadStructure,
			value:   "listen",
			deepErr: fmt.Errorf("wrong type of %T - %#v", untypedListens, untypedListens),
		}
	}

	if accessLog, ok = c["accessLog"].(string); !ok {
		accessLog = ""
	}
	if errorLog, ok = c["errorLog"].(string); !ok {
		accessLog = ""
	}

	var handlers []HandlerConfig
	var dirtyHandlers []interface{}

	if dirtyHandlers, ok = c["handler"].([]interface{}); !ok {
		return nil, []string{}, []HandlerConfig{}, NewErr{
			Code:    ErrConfigLoadStructure,
			value:   "handler",
			deepErr: fmt.Errorf("wrong type of %T", c["listen"]),
		}
	}

	for _, oneHandler := range dirtyHandlers {
		cleanHandler, err := decodeHandler(oneHandler)
		if err != nil {
			return nil, []string{}, []HandlerConfig{}, err
		} else {
			handlers = append(handlers, cleanHandler)
		}
	}

	return nil, listens, handlers, nil
}

func decodeHandler(dirtyHandler interface{}) (HandlerConfig, Err) {
	var h HandlerConfig

	var addressed map[string]interface{}
	var ok bool

	if addressed, ok = dirtyHandler.(map[string]interface{}); !ok {
		return HandlerConfig{}, NewErr{
			Code:    ErrConfigLoadStructure,
			value:   "handler",
			deepErr: fmt.Errorf("bad handler in config - %#v", dirtyHandler),
		}
	}

	if _, ok = addressed["handlerType"]; ok {
		if h.handlerType, ok = addressed["handlerType"].(string); !ok {
			return HandlerConfig{}, NewErr{
				Code:  ErrConfigLoadValue,
				value: "handlerType",
			}
		}
	}
	if _, ok = addressed["handlerRoute"]; ok {
		if h.handlerRoute, ok = addressed["handlerRoute"].(string); !ok {
			return HandlerConfig{}, NewErr{
				Code:  ErrConfigLoadValue,
				value: "handlerRoute",
			}
		}
	}

	if _, ok = addressed["baseURL"]; ok {
		if h.baseURL, ok = addressed["baseURL"].(string); !ok {
			return HandlerConfig{}, NewErr{
				Code:  ErrConfigLoadValue,
				value: "baseURl",
			}
		}
	}
	if _, ok = addressed["confPath"]; ok {
		if h.confPath, ok = addressed["confPath"].(string); !ok {
			return HandlerConfig{}, NewErr{
				Code:  ErrConfigLoadValue,
				value: "confPath",
			}
		}
	}
	if _, ok = addressed["confExt"]; ok {
		if h.confExt, ok = addressed["confExt"].(string); !ok {
			return HandlerConfig{}, NewErr{
				Code:  ErrConfigLoadValue,
				value: "confExt",
			}
		}
	}

	if _, ok = addressed["exclude"]; ok {
		if h.exclude, ok = addressed["exclude"].([]string); !ok {
			return HandlerConfig{}, NewErr{
				Code:  ErrConfigLoadValue,
				value: "exclude",
			}
		}
	}

	if _, ok = addressed["subdomainLen"]; ok {
		if h.subdomainLen, ok = addressed["subdomainLen"].(int); !ok {
			return HandlerConfig{}, NewErr{
				Code:  ErrConfigLoadValue,
				value: "subdomainLen",
			}
		}
	}

	if _, ok = addressed["redirectTracing"]; ok {
		if h.redirectTracing, ok = addressed["redirectTracing"].(bool); !ok {
			return HandlerConfig{}, NewErr{
				Code:  ErrConfigLoadValue,
				value: "redirectTracing",
			}
		}
	}

	var err error
	if _, ok = addressed["confFile"]; ok {
		if workFile, ok := addressed["confFile"].(string); !ok {
			return HandlerConfig{}, NewErr{
				Code:    ErrConfigLoadStructure,
				value:   "confFile",
				deepErr: fmt.Errorf("%#v", addressed["confFile"]),
			}
		} else if addressed["handlerType"] != "static" {
			h.confTempl, err = template.ParseFiles(workFile)
			if err != nil {
				return HandlerConfig{}, NewErr{
					Code:    ErrConfigLoadTemplate,
					value:   "confFile " + workFile,
					deepErr: err,
				}
			}
		}
	}
	if _, ok = addressed["resFile"]; ok {
		if workFile, ok := addressed["resFile"].(string); !ok {
			return HandlerConfig{}, NewErr{
				Code:  ErrConfigLoadStructure,
				value: "resFile " + workFile,
			}
		} else if addressed["handlerType"] != "static" {
			h.resTempl, err = template.ParseFiles(workFile)
			if err != nil {
				return HandlerConfig{}, NewErr{
					Code:    ErrConfigLoadTemplate,
					value:   "resFile",
					deepErr: err,
				}
			}
		} else {
			h.resFile = workFile
		}
	}

	if _, ok = addressed["ipFile"]; ok {
		if workFile, ok := addressed["ipFile"].(string); !ok {
			return HandlerConfig{}, NewErr{
				Code:  ErrConfigLoadStructure,
				value: "ipFile " + workFile,
			}
		} else if addressed["ipFile"].(string) == "" {
		} else if addressed["handlerType"] != "static" {
			// #TODO# fix this call?
			h.ipList, err = parseIPList(workFile)
			if err != nil {
				return HandlerConfig{}, NewErr{
					Code:    ErrConfigLoadTemplate,
					value:   "ipFile",
					deepErr: err,
				}
			}
		}
	}

	return h, nil
}
<commit_msg>fixed a bunch of stupid mistakes in `config.go`<commit_after>package moxxiConf

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"strings"
	"text/template"
)

func LoadConfig() ([]string, string, string, []HandlerConfig, Err) {
	config, err := prepConfig()
	if err != nil {
		return nil, "", "", []HandlerConfig{}, err
	}

	if err = validateConfig(config); err != nil {
		return nil, "", "", []HandlerConfig{}, err
	}

	listens, accessLog, errorLog, handlers, err := loadConfig(config)
	if err != nil {
		return nil, "", "", []HandlerConfig{}, err
	}

	return listens, accessLog, errorLog, handlers, nil
}

func prepConfig() (*map[string]interface{}, Err) {

	possibleConfigs := []string{
		"./config.json",
		"/etc/moxxi/config.json",
		"$HOME/.moxxi/config.json",
		"./moxxi.config.json",
	}

	var data []byte
	var globErr error
	var c map[string]interface{}
ValidConfig:
	for _, configTry := range possibleConfigs {
		data, globErr = ioutil.ReadFile(configTry)
		switch {
		case os.IsNotExist(globErr):
		case globErr == nil:
			break ValidConfig
		default:
			return nil, NewErr{
				Code:    ErrConfigBadRead,
				value:   configTry,
				deepErr: globErr,
			}
		}
	}

	globErr = json.Unmarshal(data, &c)
	if globErr != nil {
		return nil, UpgradeError(globErr)
	}

	if _, ok := c["listen"]; !ok {
		c["listen"] = []string{"localhost:8080"}
	}

	return &c, nil
}

func validateConfig(dirtyConfig *map[string]interface{}) Err {
	c := *dirtyConfig

	// clean up array top level lines
	for _, part := range []string{"listen", "exclude"} {
		if _, ok := c[part]; ok {
			chkArray, ok := c[part].([]interface{})
			if !ok {
				c[part] = []interface{}{c[part]}
				chkArray = []interface{}{c[part]}
			}

			// cannot type assert to ([]string) so i have to go through the parts?
			for _, each := range chkArray {
				if _, ok = each.(string); !ok {
					return NewErr{
						Code:    ErrConfigBadStructure,
						value:   part,
						deepErr: fmt.Errorf("%T - %#v", c[part], c[part]),
					}
				}
			}
		}
	}

	// check that the following things are strings in the top level
	for _, part := range []string{
		"baseURL",
		"confPath",
		"confExt",
		"confFile",
		"resFile",
		"ipFile",
		"accessLog",
		"errorLog",
	} {
		if _, ok := c[part]; ok {
			if _, ok := c[part].(string); !ok {
				return NewErr{Code: ErrConfigBadStructure, value: part}
			}
		}
	}

	if _, ok := c["subdomainLen"]; ok {
		switch c["subdomainLen"].(type) {
		case int:
		case float64:
			c["subdomainLen"] = int(c["subdomainLen"].(float64))
		default:
			return NewErr{
				Code:  ErrConfigBadStructure,
				value: "subdomainLen",
				deepErr: fmt.Errorf("handler sub portion wrong type %T - %#v",
					c["subdomainLen"], c["subdomainLen"]),
			}
		}
		if c["subdomainLen"].(int) < 8 {
			c["subdomainLen"] = 8
		}
	}

	if _, ok := c["redirectTracing"]; ok {
		if _, ok = c["redirectTracing"].(bool); !ok {
			return NewErr{
				Code:    ErrConfigBadStructure,
				value:   "redirectTracing",
				deepErr: fmt.Errorf("%T - %#v", c["redirectTracing"], c["redirectTracing"]),
			}
		}
	}

	// verify the handlers are an array
	handlers, ok := c["handler"].([]interface{})
	if !ok {
		return NewErr{
			Code:  ErrConfigBadStructure,
			value: "handler",
		}
	}

	// test and propagate handlers
	for id, _ := range handlers {
		locErr := validateConfigHandler(&c, id)
		if locErr != nil {
			return locErr
		}
	}

	dirtyConfig = &c

	return nil
}

func validateConfigHandler(pConfig *map[string]interface{}, id int) Err {

	// unpack everything
	c := *pConfig
	var allHandlers []interface{}
	var h map[string]interface{}
	var ok bool

	if allHandlers, ok = c["handler"].([]interface{}); !ok {
		return NewErr{
			Code:  ErrConfigBadStructure,
			value: "handler",
		}
	}
	if h, ok = allHandlers[id].(map[string]interface{}); !ok {
		return NewErr{
			Code:  ErrConfigBadStructure,
			value: "handler",
		}
	}

	switch h["handlerType"] {
	case "static":
	case "form":
	case "json":
	default:
		return NewErr{
			Code:  ErrConfigBadStructure,
			value: "handlerType",
		}
	}

	if loc, ok := h["handlerRoute"].(string); !ok {
		return NewErr{
			Code:  ErrConfigBadStructure,
			value: "handlerRoute",
		}
	} else if !strings.HasSuffix(loc, "/") {
		h["handlerRoute"] = loc + "/"
	}

	// check the exclude
	if _, ok := h["exclude"]; ok {
		// if it exists, make it an array
		if _, ok := h["exclude"].([]interface{}); !ok {
			h["exclude"] = []interface{}{h["exclude"]}
		}

		// cannot type assert to ([]string) so have to go through the parts
		// check to make sure it's a string array
		if _, ok := h["exclude"].([]string); !ok {
			return NewErr{Code: ErrConfigBadStructure, value: "exclude"}
		}
	} else {
		// if it does not exist, propagate it from above
		if _, ok := c["exclude"]; ok {
			h["exclude"] = c["exclude"]
		} else {
			h["exclude"] = []string{}
		}
	}

	// check the following things - validate and propagate them
	for _, part := range []string{
		"baseURL",
		"confPath",
		"confExt",
		"confFile",
		"resFile",
		"ipFile",
	} {
		if _, ok := h[part]; ok {
			if _, ok := h[part].(string); !ok {
				return NewErr{
					Code:    ErrConfigBadStructure,
					value:   part,
					deepErr: fmt.Errorf("handler portion - %T - %#v", h[part], h[part]),
				}
			}
		} else {
			if _, ok := c[part]; ok {
				h[part] = c[part]
			} else {
				h[part] = ""
			}
		}
	}

	if _, ok = h["subdomainLen"]; ok {
		switch h["subdomainLen"].(type) {
		case int:
		case float64:
			h["subdomainLen"] = int(h["subdomainLen"].(float64))
		default:
			return NewErr{
				Code:  ErrConfigBadStructure,
				value: "subdomainLen",
				deepErr: fmt.Errorf("handler sub portion wrong type %T - %#v",
					h["subdomainLen"], h["subdomainLen"]),
			}
		}
		if h["subdomainLen"].(int) < 8 {
			h["subdomainLen"] = 8
		}
	} else {
		if _, ok := c["subdomainLen"]; ok {
			h["subdomainLen"] = c["subdomainLen"]
		} else {
			h["subdomainLen"] = 8
		}
	}

	if _, ok = h["redirectTracing"]; ok {
		if _, ok = h["redirectTracing"].(bool); !ok {
			return NewErr{
				Code:    ErrConfigBadStructure,
				value:   "redirectTracing",
				deepErr: fmt.Errorf("%T - %#v", c["redirectTracing"], c["redirectTracing"]),
			}
		}
	} else {
		if _, ok := c["redirectTracing"]; ok {
			h["redirectTracing"] = c["redirectTracing"]
		}
	}

	// pack things back in
	allHandlers[id] = h
	c["handler"] = allHandlers
	pConfig = &c
	return nil
}

func loadConfig(pConfig *map[string]interface{}) (
	[]string, string, string, []HandlerConfig, Err) {

	c := *pConfig
	var ok bool
	var listens []string
	var accessLog, errorLog string

	if untypedListens, ok := c["listen"].([]interface{}); ok {
		for _, each := range untypedListens {
			if oneListen, ok := each.(string); ok {
				listens = append(listens, oneListen)
			} else {
				return nil, "", "", nil, NewErr{
					Code:    ErrConfigLoadType,
					value:   "listen",
					deepErr: fmt.Errorf("wrong type of %T - %#v", each, each),
				}
			}
		}
	} else {
		return nil, "", "", nil, NewErr{
			Code:    ErrConfigLoadStructure,
			value:   "listen",
			deepErr: fmt.Errorf("wrong type of %T - %#v", untypedListens, untypedListens),
		}
	}

	if accessLog, ok = c["accessLog"].(string); !ok {
		accessLog = ""
	}
	if errorLog, ok = c["errorLog"].(string); !ok {
		errorLog = ""
	}

	var handlers []HandlerConfig
	var dirtyHandlers []interface{}

	if dirtyHandlers, ok = c["handler"].([]interface{}); !ok {
		return nil, "", "", nil, NewErr{
			Code:    ErrConfigLoadStructure,
			value:   "handler",
			deepErr: fmt.Errorf("wrong type of %T", c["listen"]),
		}
	}

	for _, oneHandler := range dirtyHandlers {
		cleanHandler, err := decodeHandler(oneHandler)
		if err != nil {
			return nil, "", "", nil, err
		} else {
			handlers = append(handlers, cleanHandler)
		}
	}

	return listens, accessLog, errorLog, handlers, nil
}

func decodeHandler(dirtyHandler interface{}) (HandlerConfig, Err) {
	var h HandlerConfig

	var addressed map[string]interface{}
	var ok bool

	if addressed, ok = dirtyHandler.(map[string]interface{}); !ok {
		return HandlerConfig{}, NewErr{
			Code:    ErrConfigLoadStructure,
			value:   "handler",
			deepErr: fmt.Errorf("bad handler in config - %#v", dirtyHandler),
		}
	}

	if _, ok = addressed["handlerType"]; ok {
		if h.handlerType, ok = addressed["handlerType"].(string); !ok {
			return HandlerConfig{}, NewErr{
				Code:  ErrConfigLoadValue,
				value: "handlerType",
			}
		}
	}
	if _, ok = addressed["handlerRoute"]; ok {
		if h.handlerRoute, ok = addressed["handlerRoute"].(string); !ok {
			return HandlerConfig{}, NewErr{
				Code:  ErrConfigLoadValue,
				value: "handlerRoute",
			}
		}
	}

	if _, ok = addressed["baseURL"]; ok {
		if h.baseURL, ok = addressed["baseURL"].(string); !ok {
			return HandlerConfig{}, NewErr{
				Code:  ErrConfigLoadValue,
				value: "baseURl",
			}
		}
	}
	if _, ok = addressed["confPath"]; ok {
		if h.confPath, ok = addressed["confPath"].(string); !ok {
			return HandlerConfig{}, NewErr{
				Code:  ErrConfigLoadValue,
				value: "confPath",
			}
		}
	}
	if _, ok = addressed["confExt"]; ok {
		if h.confExt, ok = addressed["confExt"].(string); !ok {
			return HandlerConfig{}, NewErr{
				Code:  ErrConfigLoadValue,
				value: "confExt",
			}
		}
	}

	if _, ok = addressed["exclude"]; ok {
		if h.exclude, ok = addressed["exclude"].([]string); !ok {
			return HandlerConfig{}, NewErr{
				Code:  ErrConfigLoadValue,
				value: "exclude",
			}
		}
	}

	if _, ok = addressed["subdomainLen"]; ok {
		if h.subdomainLen, ok = addressed["subdomainLen"].(int); !ok {
			return HandlerConfig{}, NewErr{
				Code:  ErrConfigLoadValue,
				value: "subdomainLen",
			}
		}
	}

	if _, ok = addressed["redirectTracing"]; ok {
		if h.redirectTracing, ok = addressed["redirectTracing"].(bool); !ok {
			return HandlerConfig{}, NewErr{
				Code:  ErrConfigLoadValue,
				value: "redirectTracing",
			}
		}
	}

	var err error
	if _, ok = addressed["confFile"]; ok {
		if workFile, ok := addressed["confFile"].(string); !ok {
			return HandlerConfig{}, NewErr{
				Code:    ErrConfigLoadStructure,
				value:   "confFile",
				deepErr: fmt.Errorf("%#v", addressed["confFile"]),
			}
		} else if addressed["handlerType"] != "static" {
			h.confTempl, err = template.ParseFiles(workFile)
			if err != nil {
				return HandlerConfig{}, NewErr{
					Code:    ErrConfigLoadTemplate,
					value:   "confFile " + workFile,
					deepErr: err,
				}
			}
		}
	}
	if _, ok = addressed["resFile"]; ok {
		if workFile, ok := addressed["resFile"].(string); !ok {
			return HandlerConfig{}, NewErr{
				Code:  ErrConfigLoadStructure,
				value: "resFile " + workFile,
			}
		} else if addressed["handlerType"] != "static" {
			h.resTempl, err = template.ParseFiles(workFile)
			if err != nil {
				return HandlerConfig{}, NewErr{
					Code:    ErrConfigLoadTemplate,
					value:   "resFile",
					deepErr: err,
				}
			}
		} else {
			h.resFile = workFile
		}
	}

	if _, ok = addressed["ipFile"]; ok {
		if workFile, ok := addressed["ipFile"].(string); !ok {
			return HandlerConfig{}, NewErr{
				Code:  ErrConfigLoadStructure,
				value: "ipFile " + workFile,
			}
		} else if addressed["ipFile"].(string) == "" {
		} else if addressed["handlerType"] != "static" {
			// #TODO# fix this call?
			h.ipList, err = parseIPList(workFile)
			if err != nil {
				return HandlerConfig{}, NewErr{
					Code:    ErrConfigLoadTemplate,
					value:   "ipFile",
					deepErr: err,
				}
			}
		}
	}

	return h, nil
}
<|endoftext|>


<commit_before>package letsencrypt

import (
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/tls"
	"crypto/x509"
	"encoding/hex"
	"encoding/pem"
	"errors"
	"fmt"
	"math/big"
	"net/http"
	"path"
	"strings"
	"time"

	"github.com/ericchiang/letsencrypt/Godeps/_workspace/src/github.com/square/go-jose"
	"github.com/ericchiang/letsencrypt/internal/base64"
)

const (
	ChallengeDNS    = "dns-01"
	ChallengeHTTP   = "http-01"
	ChallengeTLSSNI = "tls-sni-01"
)

// HTTP returns a URL path and HTTP response body that the ACME server will
// check when verifying the challenge.
func (chal Challenge) HTTP(accountKey interface{}) (urlPath, resource string, err error) {
	if chal.Type != "http-01" {
		return "", "", fmt.Errorf("challenge type is %s not %s", chal.Type, "http-01")
	}

	urlPath = path.Join("/.well-known/acme-challenge", chal.Token)
	resource, err = keyAuth(accountKey, chal.Token)
	return
}

// TLSSNI returns TLS certificates for a set of server names.
// The ACME server will make a TLS Server Name Indication handshake with the
// given domain. The domain must present the returned certifiate for each name.
func (chal Challenge) TLSSNI(accountKey interface{}) (map[string]*tls.Certificate, error) {
	if chal.Type != "tls-sni-01" {
		return nil, fmt.Errorf("challenge type is %s not %s", chal.Type, "tls-sni-01")
	}

	auth, err := keyAuth(accountKey, chal.Token)
	if err != nil {
		return nil, err
	}

	// private key for creating self-signed certificates
	// TODO: Make configurable?
	tlsKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return nil, err
	}

	compute := func(content string) string {
		hash := sha256.Sum256([]byte(content))
		return hex.EncodeToString(hash[:])
	}

	// compute z0 ... zN+1 for the challenge
	z := make([]string, chal.N+1)
	z[0] = compute(auth)
	for i := 0; i < chal.N; i++ {
		z[i+1] = compute(z[i])
	}

	// crypto/tls library takes a PEM and DER encoded private key
	certKeyPEM := pemEncode(x509.MarshalPKCS1PrivateKey(tlsKey), "RSA PRIVATE KEY")

	certs := make(map[string]*tls.Certificate)
	for _, zi := range z {
		name := zi[:32] + "." + zi[32:64] + ".acme.invalid"

		// initialize server certificate template with sane defaults
		tmpl, err := certTmpl()
		if err != nil {
			return nil, err
		}

		tmpl.SignatureAlgorithm = x509.SHA256WithRSA
		tmpl.DNSNames = []string{name}

		derCert, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &tlsKey.PublicKey, tlsKey)
		if err != nil {
			return nil, fmt.Errorf("create self-signed certificate: %v", err)
		}
		certPEM := pemEncode(derCert, "CERTIFICATE")
		cert, err := tls.X509KeyPair(certPEM, certKeyPEM)
		if err != nil {
			return nil, fmt.Errorf("loading x509 key pair: %v", err)
		}

		certs[name] = &cert
	}
	return certs, nil
}

// Not yet implemented
func (chal Challenge) DNS(accountKey interface{}) (domain, txt string, err error) {
	return "", "", errors.New("dns challenges not implemented")
}

// Not yet implemented
func (chal Challenge) ProofOfPossession(accountKey, certKey interface{}) (Challenge, error) {
	return Challenge{}, errors.New("proof of possession not implemented")
}

// ChallengeReady informs the server that the provided challenge is ready
// for verification.
//
// The client then begins polling the server for confirmation on the
// result of the status.
func (c *Client) ChallengeReady(accountKey interface{}, chal Challenge) error {
	switch chal.Type {
	case "http-01", "tls-sni-01":
	default:
		return fmt.Errorf("unsupported challenge type '%s'", chal.Type)
	}
	auth, err := keyAuth(accountKey, chal.Token)
	if err != nil {
		return err
	}
	data := struct {
		Resource string `json:"resource"`
		KeyAuth  string `json:"keyAuthorization"`
		Type     string `json:"type"`
		Token    string `json:"token"`
	}{resourceChallenge, auth, chal.Type, chal.Token}
	sig, err := c.signObject(accountKey, &data)
	if err != nil {
		return err
	}
	resp, err := c.client.Post(chal.URI, jwsContentType, strings.NewReader(sig))
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if err := checkHTTPError(resp, http.StatusAccepted); err != nil {
		return err
	}

	// Begin polling the server and check if the challenge is completed.
	pollInterval := c.PollInterval
	if pollInterval == 0 {
		pollInterval = 500 * time.Millisecond
	}
	pollTimeout := c.PollTimeout
	if pollTimeout == 0 {
		pollTimeout = 30 * time.Second
	}
	start := time.Now()
	for {
		if time.Now().Sub(start) > pollTimeout {
			return errors.New("polling pending challenge timed out")
		}
		chal, err := c.Challenge(chal.URI)
		if err != nil {
			return err
		}
		switch chal.Status {
		case StatusPending, "":
			time.Sleep(pollInterval)
		case StatusInvalid:
			if err.Error == nil {
				return errors.New("challenge returned status 'invalid' without explicit error")
			}
			return chal.Error
		case StatusValid:
			return nil
		default:
			return fmt.Errorf("unexpected challenge status: %s", chal.Status)
		}
	}
}

func keyAuth(key interface{}, token string) (string, error) {
	thumbprint, err := (&jose.JsonWebKey{Key: key}).Thumbprint(crypto.SHA256)
	if err != nil {
		return "", fmt.Errorf("compute key thumbprint: %v", err)
	}
	return token + "." + base64.RawURLEncoding.EncodeToString(thumbprint), nil
}

func certTmpl() (*x509.Certificate, error) {
	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
	if err != nil {
		return nil, errors.New("failed to generate serial number: " + err.Error())
	}

	// TODO: make certificate expire when the authorization challenge expires
	tmpl := x509.Certificate{
		SerialNumber:          serialNumber,
		NotBefore:             time.Now(),
		NotAfter:              time.Now().Add(time.Hour * 24),
		BasicConstraintsValid: true,
		KeyUsage:              x509.KeyUsageDigitalSignature,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
	}
	return &tmpl, nil
}

func pemEncode(block []byte, typ string) []byte {
	b := pem.Block{Type: typ, Bytes: block}
	return pem.EncodeToMemory(&b)
}
<commit_msg>Fix invalid memory address crash on invalid challenges<commit_after>package letsencrypt

import (
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/tls"
	"crypto/x509"
	"encoding/hex"
	"encoding/pem"
	"errors"
	"fmt"
	"math/big"
	"net/http"
	"path"
	"strings"
	"time"

	"github.com/ericchiang/letsencrypt/Godeps/_workspace/src/github.com/square/go-jose"
	"github.com/ericchiang/letsencrypt/internal/base64"
)

const (
	ChallengeDNS    = "dns-01"
	ChallengeHTTP   = "http-01"
	ChallengeTLSSNI = "tls-sni-01"
)

// HTTP returns a URL path and HTTP response body that the ACME server will
// check when verifying the challenge.
func (chal Challenge) HTTP(accountKey interface{}) (urlPath, resource string, err error) {
	if chal.Type != "http-01" {
		return "", "", fmt.Errorf("challenge type is %s not %s", chal.Type, "http-01")
	}

	urlPath = path.Join("/.well-known/acme-challenge", chal.Token)
	resource, err = keyAuth(accountKey, chal.Token)
	return
}

// TLSSNI returns TLS certificates for a set of server names.
// The ACME server will make a TLS Server Name Indication handshake with the
// given domain. The domain must present the returned certifiate for each name.
func (chal Challenge) TLSSNI(accountKey interface{}) (map[string]*tls.Certificate, error) {
	if chal.Type != "tls-sni-01" {
		return nil, fmt.Errorf("challenge type is %s not %s", chal.Type, "tls-sni-01")
	}

	auth, err := keyAuth(accountKey, chal.Token)
	if err != nil {
		return nil, err
	}

	// private key for creating self-signed certificates
	// TODO: Make configurable?
	tlsKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return nil, err
	}

	compute := func(content string) string {
		hash := sha256.Sum256([]byte(content))
		return hex.EncodeToString(hash[:])
	}

	// compute z0 ... zN+1 for the challenge
	z := make([]string, chal.N+1)
	z[0] = compute(auth)
	for i := 0; i < chal.N; i++ {
		z[i+1] = compute(z[i])
	}

	// crypto/tls library takes a PEM and DER encoded private key
	certKeyPEM := pemEncode(x509.MarshalPKCS1PrivateKey(tlsKey), "RSA PRIVATE KEY")

	certs := make(map[string]*tls.Certificate)
	for _, zi := range z {
		name := zi[:32] + "." + zi[32:64] + ".acme.invalid"

		// initialize server certificate template with sane defaults
		tmpl, err := certTmpl()
		if err != nil {
			return nil, err
		}

		tmpl.SignatureAlgorithm = x509.SHA256WithRSA
		tmpl.DNSNames = []string{name}

		derCert, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &tlsKey.PublicKey, tlsKey)
		if err != nil {
			return nil, fmt.Errorf("create self-signed certificate: %v", err)
		}
		certPEM := pemEncode(derCert, "CERTIFICATE")
		cert, err := tls.X509KeyPair(certPEM, certKeyPEM)
		if err != nil {
			return nil, fmt.Errorf("loading x509 key pair: %v", err)
		}

		certs[name] = &cert
	}
	return certs, nil
}

// Not yet implemented
func (chal Challenge) DNS(accountKey interface{}) (domain, txt string, err error) {
	return "", "", errors.New("dns challenges not implemented")
}

// Not yet implemented
func (chal Challenge) ProofOfPossession(accountKey, certKey interface{}) (Challenge, error) {
	return Challenge{}, errors.New("proof of possession not implemented")
}

// ChallengeReady informs the server that the provided challenge is ready
// for verification.
//
// The client then begins polling the server for confirmation on the
// result of the status.
func (c *Client) ChallengeReady(accountKey interface{}, chal Challenge) error {
	switch chal.Type {
	case "http-01", "tls-sni-01":
	default:
		return fmt.Errorf("unsupported challenge type '%s'", chal.Type)
	}
	auth, err := keyAuth(accountKey, chal.Token)
	if err != nil {
		return err
	}
	data := struct {
		Resource string `json:"resource"`
		KeyAuth  string `json:"keyAuthorization"`
		Type     string `json:"type"`
		Token    string `json:"token"`
	}{resourceChallenge, auth, chal.Type, chal.Token}
	sig, err := c.signObject(accountKey, &data)
	if err != nil {
		return err
	}
	resp, err := c.client.Post(chal.URI, jwsContentType, strings.NewReader(sig))
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if err := checkHTTPError(resp, http.StatusAccepted); err != nil {
		return err
	}

	// Begin polling the server and check if the challenge is completed.
	pollInterval := c.PollInterval
	if pollInterval == 0 {
		pollInterval = 500 * time.Millisecond
	}
	pollTimeout := c.PollTimeout
	if pollTimeout == 0 {
		pollTimeout = 30 * time.Second
	}
	start := time.Now()
	for {
		if time.Now().Sub(start) > pollTimeout {
			return errors.New("polling pending challenge timed out")
		}
		chal, err := c.Challenge(chal.URI)
		if err != nil {
			return err
		}
		switch chal.Status {
		case StatusPending, "":
			time.Sleep(pollInterval)
		case StatusInvalid:
			if chal.Error == nil {
				return errors.New("challenge returned status 'invalid' without explicit error")
			}
			return chal.Error
		case StatusValid:
			return nil
		default:
			return fmt.Errorf("unexpected challenge status: %s", chal.Status)
		}
	}
}

func keyAuth(key interface{}, token string) (string, error) {
	thumbprint, err := (&jose.JsonWebKey{Key: key}).Thumbprint(crypto.SHA256)
	if err != nil {
		return "", fmt.Errorf("compute key thumbprint: %v", err)
	}
	return token + "." + base64.RawURLEncoding.EncodeToString(thumbprint), nil
}

func certTmpl() (*x509.Certificate, error) {
	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
	if err != nil {
		return nil, errors.New("failed to generate serial number: " + err.Error())
	}

	// TODO: make certificate expire when the authorization challenge expires
	tmpl := x509.Certificate{
		SerialNumber:          serialNumber,
		NotBefore:             time.Now(),
		NotAfter:              time.Now().Add(time.Hour * 24),
		BasicConstraintsValid: true,
		KeyUsage:              x509.KeyUsageDigitalSignature,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
	}
	return &tmpl, nil
}

func pemEncode(block []byte, typ string) []byte {
	b := pem.Block{Type: typ, Bytes: block}
	return pem.EncodeToMemory(&b)
}
<|endoftext|>