<commit_before>package aws

import (
	"bytes"
	"fmt"
	"log"
	"sort"
	"strings"

	"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/hashicorp/terraform/helper/hashcode"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsSecurityGroupRule() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsSecurityGroupRuleCreate,
		Read:   resourceAwsSecurityGroupRuleRead,
		Delete: resourceAwsSecurityGroupRuleDelete,

		SchemaVersion: 2,
		MigrateState:  resourceAwsSecurityGroupRuleMigrateState,

		Schema: map[string]*schema.Schema{
			"type": &schema.Schema{
				Type:        schema.TypeString,
				Required:    true,
				ForceNew:    true,
				Description: "Type of rule, ingress (inbound) or egress (outbound).",
			},

			"from_port": &schema.Schema{
				Type:     schema.TypeInt,
				Required: true,
				ForceNew: true,
			},

			"to_port": &schema.Schema{
				Type:     schema.TypeInt,
				Required: true,
				ForceNew: true,
			},

			"protocol": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"cidr_blocks": &schema.Schema{
				Type:     schema.TypeList,
				Optional: true,
				ForceNew: true,
				Elem:     &schema.Schema{Type: schema.TypeString},
			},

			"security_group_id": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"source_security_group_id": &schema.Schema{
				Type:          schema.TypeString,
				Optional:      true,
				ForceNew:      true,
				Computed:      true,
				ConflictsWith: []string{"cidr_blocks", "self"},
			},

			"self": &schema.Schema{
				Type:          schema.TypeBool,
				Optional:      true,
				Default:       false,
				ForceNew:      true,
				ConflictsWith: []string{"cidr_blocks"},
			},
		},
	}
}

func resourceAwsSecurityGroupRuleCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn
	sg_id := d.Get("security_group_id").(string)

	awsMutexKV.Lock(sg_id)
	defer awsMutexKV.Unlock(sg_id)

	sg, err := findResourceSecurityGroup(conn, sg_id)
	if err != nil {
		return err
	}

	perm := expandIPPerm(d, sg)

	ruleType := d.Get("type").(string)

	var autherr error
	switch ruleType {
	case "ingress":
		log.Printf("[DEBUG] Authorizing security group %s %s rule: %s",
			sg_id, "Ingress", perm)

		req := &ec2.AuthorizeSecurityGroupIngressInput{
			GroupId:       sg.GroupId,
			IpPermissions: []*ec2.IpPermission{perm},
		}

		if sg.VpcId == nil || *sg.VpcId == "" {
			req.GroupId = nil
			req.GroupName = sg.GroupName
		}

		_, autherr = conn.AuthorizeSecurityGroupIngress(req)

	case "egress":
		log.Printf("[DEBUG] Authorizing security group %s %s rule: %#v",
			sg_id, "Egress", perm)

		req := &ec2.AuthorizeSecurityGroupEgressInput{
			GroupId:       sg.GroupId,
			IpPermissions: []*ec2.IpPermission{perm},
		}

		_, autherr = conn.AuthorizeSecurityGroupEgress(req)

	default:
		return fmt.Errorf("Security Group Rule must be type 'ingress' or type 'egress'")
	}

	if autherr != nil {
		if awsErr, ok := autherr.(awserr.Error); ok {
			if awsErr.Code() == "InvalidPermission.Duplicate" {
				return fmt.Errorf(`[WARN] A duplicate Security Group rule was found. This may be
a side effect of a now-fixed Terraform issue causing two security groups with
identical attributes but different source_security_group_ids to overwrite each
other in the state. See https://github.com/hashicorp/terraform/pull/2376 for more
information and instructions for recovery. Error message: %s`, awsErr.Message())
			}
		}

		return fmt.Errorf(
			"Error authorizing security group rule type %s: %s",
			ruleType, autherr)
	}

	d.SetId(ipPermissionIDHash(sg_id, ruleType, perm))

	return resourceAwsSecurityGroupRuleRead(d, meta)
}

func resourceAwsSecurityGroupRuleRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn
	sg_id := d.Get("security_group_id").(string)
	sg, err := findResourceSecurityGroup(conn, sg_id)
	if err != nil {
		log.Printf("[DEBUG] Error finding Secuirty Group (%s) for Rule (%s): %s", sg_id, d.Id(), err)
		d.SetId("")
		return nil
	}

	var rule *ec2.IpPermission
	var rules []*ec2.IpPermission
	ruleType := d.Get("type").(string)
	switch ruleType {
	case "ingress":
		rules = sg.IpPermissions
	default:
		rules = sg.IpPermissionsEgress
	}

	p := expandIPPerm(d, sg)

	if len(rules) == 0 {
		log.Printf("[WARN] No %s rules were found for Security Group (%s) looking for Security Group Rule (%s)",
			ruleType, *sg.GroupName, d.Id())
		d.SetId("")
		return nil
	}

	for _, r := range rules {
		if r.ToPort != nil && *p.ToPort != *r.ToPort {
			continue
		}

		if r.FromPort != nil && *p.FromPort != *r.FromPort {
			continue
		}

		if r.IpProtocol != nil && *p.IpProtocol != *r.IpProtocol {
			continue
		}

		remaining := len(p.IpRanges)
		for _, ip := range p.IpRanges {
			for _, rip := range r.IpRanges {
				if *ip.CidrIp == *rip.CidrIp {
					remaining--
				}
			}
		}

		if remaining > 0 {
			continue
		}

		remaining = len(p.UserIdGroupPairs)
		for _, ip := range p.UserIdGroupPairs {
			for _, rip := range r.UserIdGroupPairs {
				if *ip.GroupId == *rip.GroupId {
					remaining--
				}
			}
		}

		if remaining > 0 {
			continue
		}

		log.Printf("[DEBUG] Found rule for Security Group Rule (%s): %s", d.Id(), r)
		rule = r
	}

	if rule == nil {
		log.Printf("[DEBUG] Unable to find matching %s Security Group Rule (%s) for Group %s",
			ruleType, d.Id(), sg_id)
		d.SetId("")
		return nil
	}

	d.Set("from_port", rule.FromPort)
	d.Set("to_port", rule.ToPort)
	d.Set("protocol", rule.IpProtocol)
	d.Set("type", ruleType)

	var cb []string
	for _, c := range p.IpRanges {
		cb = append(cb, *c.CidrIp)
	}

	d.Set("cidr_blocks", cb)

	if len(p.UserIdGroupPairs) > 0 {
		s := p.UserIdGroupPairs[0]
		d.Set("source_security_group_id", *s.GroupId)
	}

	return nil
}

func resourceAwsSecurityGroupRuleDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn
	sg_id := d.Get("security_group_id").(string)

	awsMutexKV.Lock(sg_id)
	defer awsMutexKV.Unlock(sg_id)

	sg, err := findResourceSecurityGroup(conn, sg_id)
	if err != nil {
		return err
	}

	perm := expandIPPerm(d, sg)
	ruleType := d.Get("type").(string)
	switch ruleType {
	case "ingress":
		log.Printf("[DEBUG] Revoking rule (%s) from security group %s:\n%s",
			"ingress", sg_id, perm)
		req := &ec2.RevokeSecurityGroupIngressInput{
			GroupId:       sg.GroupId,
			IpPermissions: []*ec2.IpPermission{perm},
		}

		_, err = conn.RevokeSecurityGroupIngress(req)

		if err != nil {
			return fmt.Errorf(
				"Error revoking security group %s rules: %s",
				sg_id, err)
		}
	case "egress":

		log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v",
			sg_id, "egress", perm)
		req := &ec2.RevokeSecurityGroupEgressInput{
			GroupId:       sg.GroupId,
			IpPermissions: []*ec2.IpPermission{perm},
		}

		_, err = conn.RevokeSecurityGroupEgress(req)

		if err != nil {
			return fmt.Errorf(
				"Error revoking security group %s rules: %s",
				sg_id, err)
		}
	}

	d.SetId("")

	return nil
}

func findResourceSecurityGroup(conn *ec2.EC2, id string) (*ec2.SecurityGroup, error) {
	req := &ec2.DescribeSecurityGroupsInput{
		GroupIds: []*string{aws.String(id)},
	}
	resp, err := conn.DescribeSecurityGroups(req)
	if err != nil {
		return nil, err
	}

	if resp == nil || len(resp.SecurityGroups) != 1 || resp.SecurityGroups[0] == nil {
		return nil, fmt.Errorf(
			"Expected to find one security group with ID %q, got: %#v",
			id, resp.SecurityGroups)
	}

	return resp.SecurityGroups[0], nil
}

// ByGroupPair implements sort.Interface for []*ec2.UserIDGroupPairs based on
// GroupID or GroupName field (only one should be set).
type ByGroupPair []*ec2.UserIdGroupPair

func (b ByGroupPair) Len() int      { return len(b) }
func (b ByGroupPair) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func (b ByGroupPair) Less(i, j int) bool {
	if b[i].GroupId != nil && b[j].GroupId != nil {
		return *b[i].GroupId < *b[j].GroupId
	}
	if b[i].GroupName != nil && b[j].GroupName != nil {
		return *b[i].GroupName < *b[j].GroupName
	}

	panic("mismatched security group rules, may be a terraform bug")
}

func ipPermissionIDHash(sg_id, ruleType string, ip *ec2.IpPermission) string {
	var buf bytes.Buffer
	buf.WriteString(fmt.Sprintf("%s-", sg_id))
	if ip.FromPort != nil && *ip.FromPort > 0 {
		buf.WriteString(fmt.Sprintf("%d-", *ip.FromPort))
	}
	if ip.ToPort != nil && *ip.ToPort > 0 {
		buf.WriteString(fmt.Sprintf("%d-", *ip.ToPort))
	}
	buf.WriteString(fmt.Sprintf("%s-", *ip.IpProtocol))
	buf.WriteString(fmt.Sprintf("%s-", ruleType))

	// We need to make sure to sort the strings below so that we always
	// generate the same hash code no matter what is in the set.
	if len(ip.IpRanges) > 0 {
		s := make([]string, len(ip.IpRanges))
		for i, r := range ip.IpRanges {
			s[i] = *r.CidrIp
		}
		sort.Strings(s)

		for _, v := range s {
			buf.WriteString(fmt.Sprintf("%s-", v))
		}
	}

	if len(ip.UserIdGroupPairs) > 0 {
		sort.Sort(ByGroupPair(ip.UserIdGroupPairs))
		for _, pair := range ip.UserIdGroupPairs {
			if pair.GroupId != nil {
				buf.WriteString(fmt.Sprintf("%s-", *pair.GroupId))
			} else {
				buf.WriteString("-")
			}
			if pair.GroupName != nil {
				buf.WriteString(fmt.Sprintf("%s-", *pair.GroupName))
			} else {
				buf.WriteString("-")
			}
		}
	}

	return fmt.Sprintf("sgrule-%d", hashcode.String(buf.String()))
}

func expandIPPerm(d *schema.ResourceData, sg *ec2.SecurityGroup) *ec2.IpPermission {
	var perm ec2.IpPermission

	perm.FromPort = aws.Int64(int64(d.Get("from_port").(int)))
	perm.ToPort = aws.Int64(int64(d.Get("to_port").(int)))
	perm.IpProtocol = aws.String(d.Get("protocol").(string))

	// build a group map that behaves like a set
	groups := make(map[string]bool)
	if raw, ok := d.GetOk("source_security_group_id"); ok {
		groups[raw.(string)] = true
	}

	if v, ok := d.GetOk("self"); ok && v.(bool) {
		if sg.VpcId != nil && *sg.VpcId != "" {
			groups[*sg.GroupId] = true
		} else {
			groups[*sg.GroupName] = true
		}
	}

	if len(groups) > 0 {
		perm.UserIdGroupPairs = make([]*ec2.UserIdGroupPair, len(groups))
		// build string list of group name/ids
		var gl []string
		for k, _ := range groups {
			gl = append(gl, k)
		}

		for i, name := range gl {
			ownerId, id := "", name
			if items := strings.Split(id, "/"); len(items) > 1 {
				ownerId, id = items[0], items[1]
			}

			perm.UserIdGroupPairs[i] = &ec2.UserIdGroupPair{
				GroupId: aws.String(id),
				UserId:  aws.String(ownerId),
			}

			if sg.VpcId == nil || *sg.VpcId == "" {
				perm.UserIdGroupPairs[i].GroupId = nil
				perm.UserIdGroupPairs[i].GroupName = aws.String(id)
				perm.UserIdGroupPairs[i].UserId = nil
			}
		}
	}

	if raw, ok := d.GetOk("cidr_blocks"); ok {
		list := raw.([]interface{})
		perm.IpRanges = make([]*ec2.IpRange, len(list))
		for i, v := range list {
			perm.IpRanges[i] = &ec2.IpRange{CidrIp: aws.String(v.(string))}
		}
	}

	return &perm
}
<commit_msg>provider/aws: error with empty list item on sg<commit_after>package aws

import (
	"bytes"
	"fmt"
	"log"
	"sort"
	"strings"

	"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/hashicorp/terraform/helper/hashcode"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsSecurityGroupRule() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsSecurityGroupRuleCreate,
		Read:   resourceAwsSecurityGroupRuleRead,
		Delete: resourceAwsSecurityGroupRuleDelete,

		SchemaVersion: 2,
		MigrateState:  resourceAwsSecurityGroupRuleMigrateState,

		Schema: map[string]*schema.Schema{
			"type": &schema.Schema{
				Type:        schema.TypeString,
				Required:    true,
				ForceNew:    true,
				Description: "Type of rule, ingress (inbound) or egress (outbound).",
			},

			"from_port": &schema.Schema{
				Type:     schema.TypeInt,
				Required: true,
				ForceNew: true,
			},

			"to_port": &schema.Schema{
				Type:     schema.TypeInt,
				Required: true,
				ForceNew: true,
			},

			"protocol": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"cidr_blocks": &schema.Schema{
				Type:     schema.TypeList,
				Optional: true,
				ForceNew: true,
				Elem:     &schema.Schema{Type: schema.TypeString},
			},

			"security_group_id": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"source_security_group_id": &schema.Schema{
				Type:          schema.TypeString,
				Optional:      true,
				ForceNew:      true,
				Computed:      true,
				ConflictsWith: []string{"cidr_blocks", "self"},
			},

			"self": &schema.Schema{
				Type:          schema.TypeBool,
				Optional:      true,
				Default:       false,
				ForceNew:      true,
				ConflictsWith: []string{"cidr_blocks"},
			},
		},
	}
}

func resourceAwsSecurityGroupRuleCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn
	sg_id := d.Get("security_group_id").(string)

	awsMutexKV.Lock(sg_id)
	defer awsMutexKV.Unlock(sg_id)

	sg, err := findResourceSecurityGroup(conn, sg_id)
	if err != nil {
		return err
	}

	perm, err := expandIPPerm(d, sg)
	if err != nil {
		return err
	}

	ruleType := d.Get("type").(string)

	var autherr error
	switch ruleType {
	case "ingress":
		log.Printf("[DEBUG] Authorizing security group %s %s rule: %s",
			sg_id, "Ingress", perm)

		req := &ec2.AuthorizeSecurityGroupIngressInput{
			GroupId:       sg.GroupId,
			IpPermissions: []*ec2.IpPermission{perm},
		}

		if sg.VpcId == nil || *sg.VpcId == "" {
			req.GroupId = nil
			req.GroupName = sg.GroupName
		}

		_, autherr = conn.AuthorizeSecurityGroupIngress(req)

	case "egress":
		log.Printf("[DEBUG] Authorizing security group %s %s rule: %#v",
			sg_id, "Egress", perm)

		req := &ec2.AuthorizeSecurityGroupEgressInput{
			GroupId:       sg.GroupId,
			IpPermissions: []*ec2.IpPermission{perm},
		}

		_, autherr = conn.AuthorizeSecurityGroupEgress(req)

	default:
		return fmt.Errorf("Security Group Rule must be type 'ingress' or type 'egress'")
	}

	if autherr != nil {
		if awsErr, ok := autherr.(awserr.Error); ok {
			if awsErr.Code() == "InvalidPermission.Duplicate" {
				return fmt.Errorf(`[WARN] A duplicate Security Group rule was found. This may be
a side effect of a now-fixed Terraform issue causing two security groups with
identical attributes but different source_security_group_ids to overwrite each
other in the state. See https://github.com/hashicorp/terraform/pull/2376 for more
information and instructions for recovery. Error message: %s`, awsErr.Message())
			}
		}

		return fmt.Errorf(
			"Error authorizing security group rule type %s: %s",
			ruleType, autherr)
	}

	d.SetId(ipPermissionIDHash(sg_id, ruleType, perm))

	return resourceAwsSecurityGroupRuleRead(d, meta)
}

func resourceAwsSecurityGroupRuleRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn
	sg_id := d.Get("security_group_id").(string)
	sg, err := findResourceSecurityGroup(conn, sg_id)
	if err != nil {
		log.Printf("[DEBUG] Error finding Secuirty Group (%s) for Rule (%s): %s", sg_id, d.Id(), err)
		d.SetId("")
		return nil
	}

	var rule *ec2.IpPermission
	var rules []*ec2.IpPermission
	ruleType := d.Get("type").(string)
	switch ruleType {
	case "ingress":
		rules = sg.IpPermissions
	default:
		rules = sg.IpPermissionsEgress
	}

	p, err := expandIPPerm(d, sg)
	if err != nil {
		return err
	}

	if len(rules) == 0 {
		log.Printf("[WARN] No %s rules were found for Security Group (%s) looking for Security Group Rule (%s)",
			ruleType, *sg.GroupName, d.Id())
		d.SetId("")
		return nil
	}

	for _, r := range rules {
		if r.ToPort != nil && *p.ToPort != *r.ToPort {
			continue
		}

		if r.FromPort != nil && *p.FromPort != *r.FromPort {
			continue
		}

		if r.IpProtocol != nil && *p.IpProtocol != *r.IpProtocol {
			continue
		}

		remaining := len(p.IpRanges)
		for _, ip := range p.IpRanges {
			for _, rip := range r.IpRanges {
				if *ip.CidrIp == *rip.CidrIp {
					remaining--
				}
			}
		}

		if remaining > 0 {
			continue
		}

		remaining = len(p.UserIdGroupPairs)
		for _, ip := range p.UserIdGroupPairs {
			for _, rip := range r.UserIdGroupPairs {
				if *ip.GroupId == *rip.GroupId {
					remaining--
				}
			}
		}

		if remaining > 0 {
			continue
		}

		log.Printf("[DEBUG] Found rule for Security Group Rule (%s): %s", d.Id(), r)
		rule = r
	}

	if rule == nil {
		log.Printf("[DEBUG] Unable to find matching %s Security Group Rule (%s) for Group %s",
			ruleType, d.Id(), sg_id)
		d.SetId("")
		return nil
	}

	d.Set("from_port", rule.FromPort)
	d.Set("to_port", rule.ToPort)
	d.Set("protocol", rule.IpProtocol)
	d.Set("type", ruleType)

	var cb []string
	for _, c := range p.IpRanges {
		cb = append(cb, *c.CidrIp)
	}

	d.Set("cidr_blocks", cb)

	if len(p.UserIdGroupPairs) > 0 {
		s := p.UserIdGroupPairs[0]
		d.Set("source_security_group_id", *s.GroupId)
	}

	return nil
}

func resourceAwsSecurityGroupRuleDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn
	sg_id := d.Get("security_group_id").(string)

	awsMutexKV.Lock(sg_id)
	defer awsMutexKV.Unlock(sg_id)

	sg, err := findResourceSecurityGroup(conn, sg_id)
	if err != nil {
		return err
	}

	perm, err := expandIPPerm(d, sg)
	if err != nil {
		return err
	}
	ruleType := d.Get("type").(string)
	switch ruleType {
	case "ingress":
		log.Printf("[DEBUG] Revoking rule (%s) from security group %s:\n%s",
			"ingress", sg_id, perm)
		req := &ec2.RevokeSecurityGroupIngressInput{
			GroupId:       sg.GroupId,
			IpPermissions: []*ec2.IpPermission{perm},
		}

		_, err = conn.RevokeSecurityGroupIngress(req)

		if err != nil {
			return fmt.Errorf(
				"Error revoking security group %s rules: %s",
				sg_id, err)
		}
	case "egress":

		log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v",
			sg_id, "egress", perm)
		req := &ec2.RevokeSecurityGroupEgressInput{
			GroupId:       sg.GroupId,
			IpPermissions: []*ec2.IpPermission{perm},
		}

		_, err = conn.RevokeSecurityGroupEgress(req)

		if err != nil {
			return fmt.Errorf(
				"Error revoking security group %s rules: %s",
				sg_id, err)
		}
	}

	d.SetId("")

	return nil
}

func findResourceSecurityGroup(conn *ec2.EC2, id string) (*ec2.SecurityGroup, error) {
	req := &ec2.DescribeSecurityGroupsInput{
		GroupIds: []*string{aws.String(id)},
	}
	resp, err := conn.DescribeSecurityGroups(req)
	if err != nil {
		return nil, err
	}

	if resp == nil || len(resp.SecurityGroups) != 1 || resp.SecurityGroups[0] == nil {
		return nil, fmt.Errorf(
			"Expected to find one security group with ID %q, got: %#v",
			id, resp.SecurityGroups)
	}

	return resp.SecurityGroups[0], nil
}

// ByGroupPair implements sort.Interface for []*ec2.UserIDGroupPairs based on
// GroupID or GroupName field (only one should be set).
type ByGroupPair []*ec2.UserIdGroupPair

func (b ByGroupPair) Len() int      { return len(b) }
func (b ByGroupPair) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func (b ByGroupPair) Less(i, j int) bool {
	if b[i].GroupId != nil && b[j].GroupId != nil {
		return *b[i].GroupId < *b[j].GroupId
	}
	if b[i].GroupName != nil && b[j].GroupName != nil {
		return *b[i].GroupName < *b[j].GroupName
	}

	panic("mismatched security group rules, may be a terraform bug")
}

func ipPermissionIDHash(sg_id, ruleType string, ip *ec2.IpPermission) string {
	var buf bytes.Buffer
	buf.WriteString(fmt.Sprintf("%s-", sg_id))
	if ip.FromPort != nil && *ip.FromPort > 0 {
		buf.WriteString(fmt.Sprintf("%d-", *ip.FromPort))
	}
	if ip.ToPort != nil && *ip.ToPort > 0 {
		buf.WriteString(fmt.Sprintf("%d-", *ip.ToPort))
	}
	buf.WriteString(fmt.Sprintf("%s-", *ip.IpProtocol))
	buf.WriteString(fmt.Sprintf("%s-", ruleType))

	// We need to make sure to sort the strings below so that we always
	// generate the same hash code no matter what is in the set.
	if len(ip.IpRanges) > 0 {
		s := make([]string, len(ip.IpRanges))
		for i, r := range ip.IpRanges {
			s[i] = *r.CidrIp
		}
		sort.Strings(s)

		for _, v := range s {
			buf.WriteString(fmt.Sprintf("%s-", v))
		}
	}

	if len(ip.UserIdGroupPairs) > 0 {
		sort.Sort(ByGroupPair(ip.UserIdGroupPairs))
		for _, pair := range ip.UserIdGroupPairs {
			if pair.GroupId != nil {
				buf.WriteString(fmt.Sprintf("%s-", *pair.GroupId))
			} else {
				buf.WriteString("-")
			}
			if pair.GroupName != nil {
				buf.WriteString(fmt.Sprintf("%s-", *pair.GroupName))
			} else {
				buf.WriteString("-")
			}
		}
	}

	return fmt.Sprintf("sgrule-%d", hashcode.String(buf.String()))
}

func expandIPPerm(d *schema.ResourceData, sg *ec2.SecurityGroup) (*ec2.IpPermission, error) {
	var perm ec2.IpPermission

	perm.FromPort = aws.Int64(int64(d.Get("from_port").(int)))
	perm.ToPort = aws.Int64(int64(d.Get("to_port").(int)))
	perm.IpProtocol = aws.String(d.Get("protocol").(string))

	// build a group map that behaves like a set
	groups := make(map[string]bool)
	if raw, ok := d.GetOk("source_security_group_id"); ok {
		groups[raw.(string)] = true
	}

	if v, ok := d.GetOk("self"); ok && v.(bool) {
		if sg.VpcId != nil && *sg.VpcId != "" {
			groups[*sg.GroupId] = true
		} else {
			groups[*sg.GroupName] = true
		}
	}

	if len(groups) > 0 {
		perm.UserIdGroupPairs = make([]*ec2.UserIdGroupPair, len(groups))
		// build string list of group name/ids
		var gl []string
		for k, _ := range groups {
			gl = append(gl, k)
		}

		for i, name := range gl {
			ownerId, id := "", name
			if items := strings.Split(id, "/"); len(items) > 1 {
				ownerId, id = items[0], items[1]
			}

			perm.UserIdGroupPairs[i] = &ec2.UserIdGroupPair{
				GroupId: aws.String(id),
				UserId:  aws.String(ownerId),
			}

			if sg.VpcId == nil || *sg.VpcId == "" {
				perm.UserIdGroupPairs[i].GroupId = nil
				perm.UserIdGroupPairs[i].GroupName = aws.String(id)
				perm.UserIdGroupPairs[i].UserId = nil
			}
		}
	}

	if raw, ok := d.GetOk("cidr_blocks"); ok {
		list := raw.([]interface{})
		perm.IpRanges = make([]*ec2.IpRange, len(list))
		for i, v := range list {
			cidrIP, ok := v.(string)
			if !ok {
				return nil, fmt.Errorf("empty element found in cidr_blocks - consider using the compact function")
			}
			perm.IpRanges[i] = &ec2.IpRange{CidrIp: aws.String(cidrIP)}
		}
	}

	return &perm, nil
}
<|endoftext|>


<commit_before>package jobs

import (
	"fmt"

	"github.com/hyperledger/burrow/binary"
	"github.com/hyperledger/burrow/crypto"
	"github.com/hyperledger/burrow/deploy/def"
	"github.com/hyperledger/burrow/deploy/proposals"
	"github.com/hyperledger/burrow/deploy/util"
	"github.com/hyperledger/burrow/txs"
	"github.com/hyperledger/burrow/txs/payload"

	log "github.com/sirupsen/logrus"
)

func recurseJobs(proposeBatch *payload.BatchTx, jobs []*def.Job, prop *def.Proposal, do *def.DeployArgs, parentScript *def.Playbook, client *def.Client) error {
	script := def.Playbook{Jobs: jobs, Account: useDefault(prop.Source, parentScript.Account), Parent: parentScript}

	for _, job := range script.Jobs {
		load, err := job.Payload()
		if err != nil {
			return fmt.Errorf("could not get Job payload: %v", load)
		}

		err = util.PreProcessFields(load, do, &script, client)
		if err != nil {
			return err
		}
		// Revalidate with possible replacements
		err = load.Validate()
		if err != nil {
			return fmt.Errorf("error validating job %s after pre-processing variables: %v", job.Name, err)
		}

		switch load.(type) {
		case *def.Meta:
			announceProposalJob(job.Name, "Meta")
			// load the package
			err = recurseJobs(proposeBatch, job.Meta.Playbook.Jobs, prop, do, &script, client)
			if err != nil {
				return err
			}

		case *def.UpdateAccount:
			announceProposalJob(job.Name, "UpdateAccount")
			tx, _, err := FormulateUpdateAccountJob(job.UpdateAccount, script.Account, client)
			if err != nil {
				return err
			}
			proposeBatch.Txs = append(proposeBatch.Txs, &payload.Any{GovTx: tx})

		case *def.RegisterName:
			announceProposalJob(job.Name, "RegisterName")
			txs, err := FormulateRegisterNameJob(job.RegisterName, do, script.Account, client)
			if err != nil {
				return err
			}
			for _, tx := range txs {
				proposeBatch.Txs = append(proposeBatch.Txs, &payload.Any{NameTx: tx})
			}
		case *def.Call:
			announceProposalJob(job.Name, "Call")
			tx, err := FormulateCallJob(job.Call, do, &script, client)
			if err != nil {
				return err
			}
			proposeBatch.Txs = append(proposeBatch.Txs, &payload.Any{CallTx: tx})
		case *def.Deploy:
			announceProposalJob(job.Name, "Deploy")
			deployTxs, _, err := FormulateDeployJob(job.Deploy, do, &script, client, job.Intermediate)
			if err != nil {
				return err
			}
			var deployAddress crypto.Address
			// Predict address
			callee, err := crypto.AddressFromHexString(job.Deploy.Source)
			if err != nil {
				return err
			}
			for _, tx := range deployTxs {
				proposeBatch.Txs = append(proposeBatch.Txs, &payload.Any{CallTx: tx})
				txEnv := txs.NewTx(tx)

				deployAddress = crypto.NewContractAddress(callee, txEnv.Hash())
			}
			job.Result = deployAddress.String()
		case *def.Permission:
			announceProposalJob(job.Name, "Permission")
			tx, err := FormulatePermissionJob(job.Permission, script.Account, client)
			if err != nil {
				return err
			}
			proposeBatch.Txs = append(proposeBatch.Txs, &payload.Any{PermsTx: tx})
		case *def.Send:
			announceProposalJob(job.Name, "Send")
			tx, err := FormulateSendJob(job.Send, script.Account, client)
			if err != nil {
				return err
			}
			proposeBatch.Txs = append(proposeBatch.Txs, &payload.Any{SendTx: tx})
		default:
			return fmt.Errorf("jobs %s illegal job type for proposal", job.Name)
		}
	}

	return nil
}

func ProposalJob(prop *def.Proposal, do *def.DeployArgs, parentScript *def.Playbook, client *def.Client) (string, error) {
	var proposeBatch payload.BatchTx

	err := recurseJobs(&proposeBatch, prop.Jobs, prop, do, parentScript, client)
	if err != nil {
		return "", err
	}

	proposal := payload.Proposal{Name: prop.Name, Description: prop.Description, BatchTx: &proposeBatch}

	proposalInput, err := client.TxInput(prop.ProposalAddress, "", prop.ProposalSequence, false)
	if err != nil {
		return "", err
	}
	proposal.BatchTx.Inputs = []*payload.TxInput{proposalInput}
	proposalHash := proposal.Hash()

	var proposalTx *payload.ProposalTx
	if do.ProposeVerify {
		ballot, err := client.GetProposal(proposalHash)
		if err != nil {
			log.Warnf("Proposal could NOT be verified, error %v", err)
			return "", err
		}

		err = proposals.ProposalExpired(ballot.Proposal, client)
		if err != nil {
			log.Warnf("Proposal verify FAILED: %v", err)
			return "", err
		}

		log.Warnf("Proposal VERIFY SUCCESSFUL")
		log.Warnf("Proposal has %d votes:", len(ballot.Votes))
		for _, v := range ballot.Votes {
			log.Warnf("\t%s\n", v.Address)
		}

		return "", err
	} else if do.ProposeVote {
		ballot, err := client.GetProposal(proposalHash)
		if err != nil {
			log.Warnf("Proposal could not be found: %v", err)
			return "", err
		}

		err = proposals.ProposalExpired(ballot.Proposal, client)
		if err != nil {
			log.Warnf("Proposal error: %v", err)
			return "", err
		}

		// proposal is there and current, let's vote for it
		input, err := client.TxInput(parentScript.Account, "", prop.Sequence, true)
		if err != nil {
			return "", err
		}

		log.Warnf("Voting for proposal with hash: %x\n", proposalHash)

		h := binary.HexBytes(proposalHash)
		proposalTx = &payload.ProposalTx{ProposalHash: &h, VotingWeight: 1, Input: input}
	} else if do.ProposeCreate {
		input, err := client.TxInput(useDefault(prop.Source, parentScript.Account), "", prop.Sequence, true)
		if err != nil {
			return "", err
		}
		log.Warnf("Creating Proposal with hash: %x\n", proposalHash)

		proposalTx = &payload.ProposalTx{VotingWeight: 1, Input: input, Proposal: &proposal}
	} else {
		log.Errorf("please specify one of --proposal-create, --proposal-vote, --proposal-verify")
		return "", nil
	}

	txe, err := client.SignAndBroadcast(proposalTx)
	if err != nil {
		var err = util.ChainErrorHandler(proposalTx.Input.Address.String(), err)
		return "", err
	}

	result := fmt.Sprintf("%X", txe.Receipt.TxHash)

	return result, nil
}
<commit_msg>Ignore some jobs types<commit_after>package jobs

import (
	"encoding/json"
	"fmt"

	"github.com/hyperledger/burrow/binary"
	"github.com/hyperledger/burrow/crypto"
	"github.com/hyperledger/burrow/deploy/def"
	"github.com/hyperledger/burrow/deploy/proposals"
	"github.com/hyperledger/burrow/deploy/util"
	"github.com/hyperledger/burrow/txs"
	"github.com/hyperledger/burrow/txs/payload"

	log "github.com/sirupsen/logrus"
)

func recurseJobs(proposeBatch *payload.BatchTx, jobs []*def.Job, prop *def.Proposal, do *def.DeployArgs, parentScript *def.Playbook, client *def.Client) error {
	script := def.Playbook{Jobs: jobs, Account: useDefault(prop.Source, parentScript.Account), Parent: parentScript}

	for _, job := range script.Jobs {
		load, err := job.Payload()
		if err != nil {
			return fmt.Errorf("could not get Job payload: %v", load)
		}

		err = util.PreProcessFields(load, do, &script, client)
		if err != nil {
			return err
		}
		// Revalidate with possible replacements
		err = load.Validate()
		if err != nil {
			return fmt.Errorf("error validating job %s after pre-processing variables: %v", job.Name, err)
		}

		switch load.(type) {
		case *def.Meta:
			announceProposalJob(job.Name, "Meta")
			// load the package
			err = recurseJobs(proposeBatch, job.Meta.Playbook.Jobs, prop, do, &script, client)
			if err != nil {
				return err
			}

		case *def.UpdateAccount:
			announceProposalJob(job.Name, "UpdateAccount")
			tx, _, err := FormulateUpdateAccountJob(job.UpdateAccount, script.Account, client)
			if err != nil {
				return err
			}
			proposeBatch.Txs = append(proposeBatch.Txs, &payload.Any{GovTx: tx})

		case *def.RegisterName:
			announceProposalJob(job.Name, "RegisterName")
			txs, err := FormulateRegisterNameJob(job.RegisterName, do, script.Account, client)
			if err != nil {
				return err
			}
			for _, tx := range txs {
				proposeBatch.Txs = append(proposeBatch.Txs, &payload.Any{NameTx: tx})
			}
		case *def.Call:
			announceProposalJob(job.Name, "Call")
			tx, err := FormulateCallJob(job.Call, do, &script, client)
			if err != nil {
				return err
			}
			proposeBatch.Txs = append(proposeBatch.Txs, &payload.Any{CallTx: tx})
		case *def.Deploy:
			announceProposalJob(job.Name, "Deploy")
			deployTxs, _, err := FormulateDeployJob(job.Deploy, do, &script, client, job.Intermediate)
			if err != nil {
				return err
			}
			var deployAddress crypto.Address
			// Predict address
			callee, err := crypto.AddressFromHexString(job.Deploy.Source)
			if err != nil {
				return err
			}
			for _, tx := range deployTxs {
				proposeBatch.Txs = append(proposeBatch.Txs, &payload.Any{CallTx: tx})
				txEnv := txs.NewTx(tx)

				deployAddress = crypto.NewContractAddress(callee, txEnv.Hash())
			}
			job.Result = deployAddress.String()
		case *def.Permission:
			announceProposalJob(job.Name, "Permission")
			tx, err := FormulatePermissionJob(job.Permission, script.Account, client)
			if err != nil {
				return err
			}
			proposeBatch.Txs = append(proposeBatch.Txs, &payload.Any{PermsTx: tx})
		case *def.Send:
			announceProposalJob(job.Name, "Send")
			tx, err := FormulateSendJob(job.Send, script.Account, client)
			if err != nil {
				return err
			}
			proposeBatch.Txs = append(proposeBatch.Txs, &payload.Any{SendTx: tx})
		case *def.QueryContract:
			announceProposalJob(job.Name, "Query Contract")
			log.Warnf("Query Contract jobs are IGNORED in proposals")

		case *def.Assert:
			announceProposalJob(job.Name, "Assert")
			log.Warnf("Assert jobs are IGNORED in proposals")
		default:
			return fmt.Errorf("jobs %s illegal job type for proposal", job.Name)
		}
	}

	return nil
}

func ProposalJob(prop *def.Proposal, do *def.DeployArgs, parentScript *def.Playbook, client *def.Client) (string, error) {
	var proposeBatch payload.BatchTx

	err := recurseJobs(&proposeBatch, prop.Jobs, prop, do, parentScript, client)
	if err != nil {
		return "", err
	}

	proposal := payload.Proposal{Name: prop.Name, Description: prop.Description, BatchTx: &proposeBatch}

	proposalInput, err := client.TxInput(prop.ProposalAddress, "", prop.ProposalSequence, false)
	if err != nil {
		return "", err
	}
	proposal.BatchTx.Inputs = []*payload.TxInput{proposalInput}
	proposalHash := proposal.Hash()

	var proposalTx *payload.ProposalTx
	if do.ProposeVerify {
		ballot, err := client.GetProposal(proposalHash)
		if err != nil {
			log.Warnf("Proposal could NOT be verified, error %v", err)
			return "", err
		}

		err = proposals.ProposalExpired(ballot.Proposal, client)
		if err != nil {
			log.Warnf("Proposal verify FAILED: %v", err)
			return "", err
		}

		log.Warnf("Proposal VERIFY SUCCESSFUL")
		log.Warnf("Proposal has %d votes:", len(ballot.Votes))
		for _, v := range ballot.Votes {
			log.Warnf("\t%s\n", v.Address)
		}

		return "", err
	} else if do.ProposeVote {
		ballot, err := client.GetProposal(proposalHash)
		if err != nil {
			log.Warnf("Proposal could not be found: %v", err)
			return "", err
		}

		err = proposals.ProposalExpired(ballot.Proposal, client)
		if err != nil {
			log.Warnf("Proposal error: %v", err)
			return "", err
		}

		// proposal is there and current, let's vote for it
		input, err := client.TxInput(parentScript.Account, "", prop.Sequence, true)
		if err != nil {
			return "", err
		}

		log.Warnf("Voting for proposal with hash: %x\n", proposalHash)

		h := binary.HexBytes(proposalHash)
		proposalTx = &payload.ProposalTx{ProposalHash: &h, VotingWeight: 1, Input: input}
	} else if do.ProposeCreate {
		input, err := client.TxInput(useDefault(prop.Source, parentScript.Account), "", prop.Sequence, true)
		if err != nil {
			return "", err
		}
		log.Warnf("Creating Proposal with hash: %x\n", proposalHash)

		bs, _ := json.Marshal(proposal)
		log.Debugf("Proposal json: %s\n", string(bs))
		proposalTx = &payload.ProposalTx{VotingWeight: 1, Input: input, Proposal: &proposal}
	} else {
		log.Errorf("please specify one of --proposal-create, --proposal-vote, --proposal-verify")
		return "", nil
	}

	txe, err := client.SignAndBroadcast(proposalTx)
	if err != nil {
		var err = util.ChainErrorHandler(proposalTx.Input.Address.String(), err)
		return "", err
	}

	result := fmt.Sprintf("%X", txe.Receipt.TxHash)

	return result, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes 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 ipam

import (
	"fmt"
	"net"
	"sync"
	"time"

	"github.com/golang/glog"

	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	informers "k8s.io/client-go/informers/core/v1"
	corelisters "k8s.io/client-go/listers/core/v1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/record"

	clientset "k8s.io/client-go/kubernetes"
	"k8s.io/client-go/kubernetes/scheme"
	v1core "k8s.io/client-go/kubernetes/typed/core/v1"
	v1node "k8s.io/kubernetes/pkg/api/v1/node"
	"k8s.io/kubernetes/pkg/cloudprovider"
	"k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
	"k8s.io/kubernetes/pkg/controller"
	nodeutil "k8s.io/kubernetes/pkg/controller/util/node"
	"k8s.io/kubernetes/pkg/scheduler/algorithm"
	utilnode "k8s.io/kubernetes/pkg/util/node"
	utiltaints "k8s.io/kubernetes/pkg/util/taints"
)

// nodeProcessingInfo tracks information related to current nodes in processing
type nodeProcessingInfo struct {
	retries int
}

// cloudCIDRAllocator allocates node CIDRs according to IP address aliases
// assigned by the cloud provider. In this case, the allocation and
// deallocation is delegated to the external provider, and the controller
// merely takes the assignment and updates the node spec.
type cloudCIDRAllocator struct {
	client clientset.Interface
	cloud  *gce.GCECloud

	// nodeLister is able to list/get nodes and is populated by the shared informer passed to
	// NewCloudCIDRAllocator.
	nodeLister corelisters.NodeLister
	// nodesSynced returns true if the node shared informer has been synced at least once.
	nodesSynced cache.InformerSynced

	// Channel that is used to pass updating Nodes to the background.
	// This increases the throughput of CIDR assignment by parallelization
	// and not blocking on long operations (which shouldn't be done from
	// event handlers anyway).
	nodeUpdateChannel chan string
	recorder          record.EventRecorder

	// Keep a set of nodes that are currectly being processed to avoid races in CIDR allocation
	lock              sync.Mutex
	nodesInProcessing map[string]*nodeProcessingInfo
}

var _ CIDRAllocator = (*cloudCIDRAllocator)(nil)

// NewCloudCIDRAllocator creates a new cloud CIDR allocator.
func NewCloudCIDRAllocator(client clientset.Interface, cloud cloudprovider.Interface, nodeInformer informers.NodeInformer) (CIDRAllocator, error) {
	if client == nil {
		glog.Fatalf("kubeClient is nil when starting NodeController")
	}

	eventBroadcaster := record.NewBroadcaster()
	recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "cidrAllocator"})
	eventBroadcaster.StartLogging(glog.Infof)
	glog.V(0).Infof("Sending events to api server.")
	eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: client.CoreV1().Events("")})

	gceCloud, ok := cloud.(*gce.GCECloud)
	if !ok {
		err := fmt.Errorf("cloudCIDRAllocator does not support %v provider", cloud.ProviderName())
		return nil, err
	}

	ca := &cloudCIDRAllocator{
		client:            client,
		cloud:             gceCloud,
		nodeLister:        nodeInformer.Lister(),
		nodesSynced:       nodeInformer.Informer().HasSynced,
		nodeUpdateChannel: make(chan string, cidrUpdateQueueSize),
		recorder:          recorder,
		nodesInProcessing: map[string]*nodeProcessingInfo{},
	}

	nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: nodeutil.CreateAddNodeHandler(ca.AllocateOrOccupyCIDR),
		UpdateFunc: nodeutil.CreateUpdateNodeHandler(func(_, newNode *v1.Node) error {
			if newNode.Spec.PodCIDR == "" {
				return ca.AllocateOrOccupyCIDR(newNode)
			}
			// Even if PodCIDR is assigned, but NetworkUnavailable condition is
			// set to true, we need to process the node to set the condition.
			networkUnavailableTaint := &v1.Taint{Key: algorithm.TaintNodeNetworkUnavailable, Effect: v1.TaintEffectNoSchedule}
			_, cond := v1node.GetNodeCondition(&newNode.Status, v1.NodeNetworkUnavailable)
			if cond == nil || cond.Status != v1.ConditionFalse || utiltaints.TaintExists(newNode.Spec.Taints, networkUnavailableTaint) {
				return ca.AllocateOrOccupyCIDR(newNode)
			}
			return nil
		}),
		DeleteFunc: nodeutil.CreateDeleteNodeHandler(ca.ReleaseCIDR),
	})

	glog.V(0).Infof("Using cloud CIDR allocator (provider: %v)", cloud.ProviderName())
	return ca, nil
}

func (ca *cloudCIDRAllocator) Run(stopCh <-chan struct{}) {
	defer utilruntime.HandleCrash()

	glog.Infof("Starting cloud CIDR allocator")
	defer glog.Infof("Shutting down cloud CIDR allocator")

	if !controller.WaitForCacheSync("cidrallocator", stopCh, ca.nodesSynced) {
		return
	}

	for i := 0; i < cidrUpdateWorkers; i++ {
		go ca.worker(stopCh)
	}

	<-stopCh
}

func (ca *cloudCIDRAllocator) worker(stopChan <-chan struct{}) {
	for {
		select {
		case workItem, ok := <-ca.nodeUpdateChannel:
			if !ok {
				glog.Warning("Channel nodeCIDRUpdateChannel was unexpectedly closed")
				return
			}
			if err := ca.updateCIDRAllocation(workItem); err == nil {
				glog.V(3).Infof("Updated CIDR for %q", workItem)
				ca.removeNodeFromProcessing(workItem)
			} else {
				glog.Errorf("Error updating CIDR for %q: %v", workItem, err)
				if canRetry, timeout := ca.retryParams(workItem); canRetry {
					glog.V(2).Infof("Retrying update for %q after %v", workItem, timeout)
					time.AfterFunc(timeout, func() {
						// Requeue the failed node for update again.
						ca.nodeUpdateChannel <- workItem
					})
					continue
				}
				glog.Errorf("Exceeded retry count for %q, dropping from queue", workItem)
			}
		case <-stopChan:
			return
		}
	}
}

func (ca *cloudCIDRAllocator) insertNodeToProcessing(nodeName string) bool {
	ca.lock.Lock()
	defer ca.lock.Unlock()
	if _, found := ca.nodesInProcessing[nodeName]; found {
		return false
	}
	ca.nodesInProcessing[nodeName] = &nodeProcessingInfo{}
	return true
}

func (ca *cloudCIDRAllocator) retryParams(nodeName string) (bool, time.Duration) {
	ca.lock.Lock()
	defer ca.lock.Unlock()

	entry, ok := ca.nodesInProcessing[nodeName]
	if !ok {
		glog.Errorf("Cannot get retryParams for %q as entry does not exist", nodeName)
		return false, 0
	}

	count := entry.retries + 1
	if count > updateMaxRetries {
		return false, 0
	}
	ca.nodesInProcessing[nodeName].retries = count

	return true, nodeUpdateRetryTimeout(count)
}

func nodeUpdateRetryTimeout(count int) time.Duration {
	timeout := updateRetryTimeout
	for i := 0; i < count && timeout < maxUpdateRetryTimeout; i++ {
		timeout *= 2
	}
	if timeout > maxUpdateRetryTimeout {
		return maxUpdateRetryTimeout
	}
	return timeout
}

func (ca *cloudCIDRAllocator) removeNodeFromProcessing(nodeName string) {
	ca.lock.Lock()
	defer ca.lock.Unlock()
	delete(ca.nodesInProcessing, nodeName)
}

// WARNING: If you're adding any return calls or defer any more work from this
// function you have to make sure to update nodesInProcessing properly with the
// disposition of the node when the work is done.
func (ca *cloudCIDRAllocator) AllocateOrOccupyCIDR(node *v1.Node) error {
	if node == nil {
		return nil
	}
	if !ca.insertNodeToProcessing(node.Name) {
		glog.V(2).Infof("Node %v is already in a process of CIDR assignment.", node.Name)
		return nil
	}

	glog.V(4).Infof("Putting node %s into the work queue", node.Name)
	ca.nodeUpdateChannel <- node.Name
	return nil
}

// updateCIDRAllocation assigns CIDR to Node and sends an update to the API server.
func (ca *cloudCIDRAllocator) updateCIDRAllocation(nodeName string) error {
	node, err := ca.nodeLister.Get(nodeName)
	if err != nil {
		if errors.IsNotFound(err) {
			return nil // node no longer available, skip processing
		}
		glog.Errorf("Failed while getting node %v for updating Node.Spec.PodCIDR: %v", nodeName, err)
		return err
	}

	cidrs, err := ca.cloud.AliasRanges(types.NodeName(nodeName))
	if err != nil {
		nodeutil.RecordNodeStatusChange(ca.recorder, node, "CIDRNotAvailable")
		return fmt.Errorf("failed to allocate cidr: %v", err)
	}
	if len(cidrs) == 0 {
		nodeutil.RecordNodeStatusChange(ca.recorder, node, "CIDRNotAvailable")
		return fmt.Errorf("failed to allocate cidr: Node %v has no CIDRs", node.Name)
	}
	_, cidr, err := net.ParseCIDR(cidrs[0])
	if err != nil {
		return fmt.Errorf("failed to parse string '%s' as a CIDR: %v", cidrs[0], err)
	}
	podCIDR := cidr.String()

	if node.Spec.PodCIDR == podCIDR {
		glog.V(4).Infof("Node %v already has allocated CIDR %v. It matches the proposed one.", node.Name, podCIDR)
		// We don't return here, in order to set the NetworkUnavailable condition later below.
	} else {
		if node.Spec.PodCIDR != "" {
			glog.Errorf("PodCIDR being reassigned! Node %v spec has %v, but cloud provider has assigned %v", node.Name, node.Spec.PodCIDR, podCIDR)
			// We fall through and set the CIDR despite this error. This
			// implements the same logic as implemented in the
			// rangeAllocator.
			//
			// See https://github.com/kubernetes/kubernetes/pull/42147#discussion_r103357248
		}
		for i := 0; i < cidrUpdateRetries; i++ {
			if err = utilnode.PatchNodeCIDR(ca.client, types.NodeName(node.Name), podCIDR); err == nil {
				glog.Infof("Set node %v PodCIDR to %v", node.Name, podCIDR)
				break
			}
		}
	}
	if err != nil {
		nodeutil.RecordNodeStatusChange(ca.recorder, node, "CIDRAssignmentFailed")
		glog.Errorf("Failed to update node %v PodCIDR to %v after multiple attempts: %v", node.Name, podCIDR, err)
		return err
	}

	err = utilnode.SetNodeCondition(ca.client, types.NodeName(node.Name), v1.NodeCondition{
		Type:               v1.NodeNetworkUnavailable,
		Status:             v1.ConditionFalse,
		Reason:             "RouteCreated",
		Message:            "NodeController create implicit route",
		LastTransitionTime: metav1.Now(),
	})
	if err != nil {
		glog.Errorf("Error setting route status for node %v: %v", node.Name, err)
	}
	return err
}

func (ca *cloudCIDRAllocator) ReleaseCIDR(node *v1.Node) error {
	glog.V(2).Infof("Node %v PodCIDR (%v) will be released by external cloud provider (not managed by controller)",
		node.Name, node.Spec.PodCIDR)
	return nil
}
<commit_msg>Fix retrying in ipam controller<commit_after>/*
Copyright 2016 The Kubernetes 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 ipam

import (
	"fmt"
	"net"
	"sync"
	"time"

	"github.com/golang/glog"

	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	informers "k8s.io/client-go/informers/core/v1"
	corelisters "k8s.io/client-go/listers/core/v1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/record"

	clientset "k8s.io/client-go/kubernetes"
	"k8s.io/client-go/kubernetes/scheme"
	v1core "k8s.io/client-go/kubernetes/typed/core/v1"
	v1node "k8s.io/kubernetes/pkg/api/v1/node"
	"k8s.io/kubernetes/pkg/cloudprovider"
	"k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
	"k8s.io/kubernetes/pkg/controller"
	nodeutil "k8s.io/kubernetes/pkg/controller/util/node"
	"k8s.io/kubernetes/pkg/scheduler/algorithm"
	utilnode "k8s.io/kubernetes/pkg/util/node"
	utiltaints "k8s.io/kubernetes/pkg/util/taints"
)

// nodeProcessingInfo tracks information related to current nodes in processing
type nodeProcessingInfo struct {
	retries int
}

// cloudCIDRAllocator allocates node CIDRs according to IP address aliases
// assigned by the cloud provider. In this case, the allocation and
// deallocation is delegated to the external provider, and the controller
// merely takes the assignment and updates the node spec.
type cloudCIDRAllocator struct {
	client clientset.Interface
	cloud  *gce.GCECloud

	// nodeLister is able to list/get nodes and is populated by the shared informer passed to
	// NewCloudCIDRAllocator.
	nodeLister corelisters.NodeLister
	// nodesSynced returns true if the node shared informer has been synced at least once.
	nodesSynced cache.InformerSynced

	// Channel that is used to pass updating Nodes to the background.
	// This increases the throughput of CIDR assignment by parallelization
	// and not blocking on long operations (which shouldn't be done from
	// event handlers anyway).
	nodeUpdateChannel chan string
	recorder          record.EventRecorder

	// Keep a set of nodes that are currectly being processed to avoid races in CIDR allocation
	lock              sync.Mutex
	nodesInProcessing map[string]*nodeProcessingInfo
}

var _ CIDRAllocator = (*cloudCIDRAllocator)(nil)

// NewCloudCIDRAllocator creates a new cloud CIDR allocator.
func NewCloudCIDRAllocator(client clientset.Interface, cloud cloudprovider.Interface, nodeInformer informers.NodeInformer) (CIDRAllocator, error) {
	if client == nil {
		glog.Fatalf("kubeClient is nil when starting NodeController")
	}

	eventBroadcaster := record.NewBroadcaster()
	recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "cidrAllocator"})
	eventBroadcaster.StartLogging(glog.Infof)
	glog.V(0).Infof("Sending events to api server.")
	eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: client.CoreV1().Events("")})

	gceCloud, ok := cloud.(*gce.GCECloud)
	if !ok {
		err := fmt.Errorf("cloudCIDRAllocator does not support %v provider", cloud.ProviderName())
		return nil, err
	}

	ca := &cloudCIDRAllocator{
		client:            client,
		cloud:             gceCloud,
		nodeLister:        nodeInformer.Lister(),
		nodesSynced:       nodeInformer.Informer().HasSynced,
		nodeUpdateChannel: make(chan string, cidrUpdateQueueSize),
		recorder:          recorder,
		nodesInProcessing: map[string]*nodeProcessingInfo{},
	}

	nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: nodeutil.CreateAddNodeHandler(ca.AllocateOrOccupyCIDR),
		UpdateFunc: nodeutil.CreateUpdateNodeHandler(func(_, newNode *v1.Node) error {
			if newNode.Spec.PodCIDR == "" {
				return ca.AllocateOrOccupyCIDR(newNode)
			}
			// Even if PodCIDR is assigned, but NetworkUnavailable condition is
			// set to true, we need to process the node to set the condition.
			networkUnavailableTaint := &v1.Taint{Key: algorithm.TaintNodeNetworkUnavailable, Effect: v1.TaintEffectNoSchedule}
			_, cond := v1node.GetNodeCondition(&newNode.Status, v1.NodeNetworkUnavailable)
			if cond == nil || cond.Status != v1.ConditionFalse || utiltaints.TaintExists(newNode.Spec.Taints, networkUnavailableTaint) {
				return ca.AllocateOrOccupyCIDR(newNode)
			}
			return nil
		}),
		DeleteFunc: nodeutil.CreateDeleteNodeHandler(ca.ReleaseCIDR),
	})

	glog.V(0).Infof("Using cloud CIDR allocator (provider: %v)", cloud.ProviderName())
	return ca, nil
}

func (ca *cloudCIDRAllocator) Run(stopCh <-chan struct{}) {
	defer utilruntime.HandleCrash()

	glog.Infof("Starting cloud CIDR allocator")
	defer glog.Infof("Shutting down cloud CIDR allocator")

	if !controller.WaitForCacheSync("cidrallocator", stopCh, ca.nodesSynced) {
		return
	}

	for i := 0; i < cidrUpdateWorkers; i++ {
		go ca.worker(stopCh)
	}

	<-stopCh
}

func (ca *cloudCIDRAllocator) worker(stopChan <-chan struct{}) {
	for {
		select {
		case workItem, ok := <-ca.nodeUpdateChannel:
			if !ok {
				glog.Warning("Channel nodeCIDRUpdateChannel was unexpectedly closed")
				return
			}
			if err := ca.updateCIDRAllocation(workItem); err == nil {
				glog.V(3).Infof("Updated CIDR for %q", workItem)
			} else {
				glog.Errorf("Error updating CIDR for %q: %v", workItem, err)
				if canRetry, timeout := ca.retryParams(workItem); canRetry {
					glog.V(2).Infof("Retrying update for %q after %v", workItem, timeout)
					time.AfterFunc(timeout, func() {
						// Requeue the failed node for update again.
						ca.nodeUpdateChannel <- workItem
					})
					continue
				}
				glog.Errorf("Exceeded retry count for %q, dropping from queue", workItem)
			}
			ca.removeNodeFromProcessing(workItem)
		case <-stopChan:
			return
		}
	}
}

func (ca *cloudCIDRAllocator) insertNodeToProcessing(nodeName string) bool {
	ca.lock.Lock()
	defer ca.lock.Unlock()
	if _, found := ca.nodesInProcessing[nodeName]; found {
		return false
	}
	ca.nodesInProcessing[nodeName] = &nodeProcessingInfo{}
	return true
}

func (ca *cloudCIDRAllocator) retryParams(nodeName string) (bool, time.Duration) {
	ca.lock.Lock()
	defer ca.lock.Unlock()

	entry, ok := ca.nodesInProcessing[nodeName]
	if !ok {
		glog.Errorf("Cannot get retryParams for %q as entry does not exist", nodeName)
		return false, 0
	}

	count := entry.retries + 1
	if count > updateMaxRetries {
		return false, 0
	}
	ca.nodesInProcessing[nodeName].retries = count

	return true, nodeUpdateRetryTimeout(count)
}

func nodeUpdateRetryTimeout(count int) time.Duration {
	timeout := updateRetryTimeout
	for i := 0; i < count && timeout < maxUpdateRetryTimeout; i++ {
		timeout *= 2
	}
	if timeout > maxUpdateRetryTimeout {
		return maxUpdateRetryTimeout
	}
	return timeout
}

func (ca *cloudCIDRAllocator) removeNodeFromProcessing(nodeName string) {
	ca.lock.Lock()
	defer ca.lock.Unlock()
	delete(ca.nodesInProcessing, nodeName)
}

// WARNING: If you're adding any return calls or defer any more work from this
// function you have to make sure to update nodesInProcessing properly with the
// disposition of the node when the work is done.
func (ca *cloudCIDRAllocator) AllocateOrOccupyCIDR(node *v1.Node) error {
	if node == nil {
		return nil
	}
	if !ca.insertNodeToProcessing(node.Name) {
		glog.V(2).Infof("Node %v is already in a process of CIDR assignment.", node.Name)
		return nil
	}

	glog.V(4).Infof("Putting node %s into the work queue", node.Name)
	ca.nodeUpdateChannel <- node.Name
	return nil
}

// updateCIDRAllocation assigns CIDR to Node and sends an update to the API server.
func (ca *cloudCIDRAllocator) updateCIDRAllocation(nodeName string) error {
	node, err := ca.nodeLister.Get(nodeName)
	if err != nil {
		if errors.IsNotFound(err) {
			return nil // node no longer available, skip processing
		}
		glog.Errorf("Failed while getting node %v for updating Node.Spec.PodCIDR: %v", nodeName, err)
		return err
	}

	cidrs, err := ca.cloud.AliasRanges(types.NodeName(nodeName))
	if err != nil {
		nodeutil.RecordNodeStatusChange(ca.recorder, node, "CIDRNotAvailable")
		return fmt.Errorf("failed to allocate cidr: %v", err)
	}
	if len(cidrs) == 0 {
		nodeutil.RecordNodeStatusChange(ca.recorder, node, "CIDRNotAvailable")
		return fmt.Errorf("failed to allocate cidr: Node %v has no CIDRs", node.Name)
	}
	_, cidr, err := net.ParseCIDR(cidrs[0])
	if err != nil {
		return fmt.Errorf("failed to parse string '%s' as a CIDR: %v", cidrs[0], err)
	}
	podCIDR := cidr.String()

	if node.Spec.PodCIDR == podCIDR {
		glog.V(4).Infof("Node %v already has allocated CIDR %v. It matches the proposed one.", node.Name, podCIDR)
		// We don't return here, in order to set the NetworkUnavailable condition later below.
	} else {
		if node.Spec.PodCIDR != "" {
			glog.Errorf("PodCIDR being reassigned! Node %v spec has %v, but cloud provider has assigned %v", node.Name, node.Spec.PodCIDR, podCIDR)
			// We fall through and set the CIDR despite this error. This
			// implements the same logic as implemented in the
			// rangeAllocator.
			//
			// See https://github.com/kubernetes/kubernetes/pull/42147#discussion_r103357248
		}
		for i := 0; i < cidrUpdateRetries; i++ {
			if err = utilnode.PatchNodeCIDR(ca.client, types.NodeName(node.Name), podCIDR); err == nil {
				glog.Infof("Set node %v PodCIDR to %v", node.Name, podCIDR)
				break
			}
		}
	}
	if err != nil {
		nodeutil.RecordNodeStatusChange(ca.recorder, node, "CIDRAssignmentFailed")
		glog.Errorf("Failed to update node %v PodCIDR to %v after multiple attempts: %v", node.Name, podCIDR, err)
		return err
	}

	err = utilnode.SetNodeCondition(ca.client, types.NodeName(node.Name), v1.NodeCondition{
		Type:               v1.NodeNetworkUnavailable,
		Status:             v1.ConditionFalse,
		Reason:             "RouteCreated",
		Message:            "NodeController create implicit route",
		LastTransitionTime: metav1.Now(),
	})
	if err != nil {
		glog.Errorf("Error setting route status for node %v: %v", node.Name, err)
	}
	return err
}

func (ca *cloudCIDRAllocator) ReleaseCIDR(node *v1.Node) error {
	glog.V(2).Infof("Node %v PodCIDR (%v) will be released by external cloud provider (not managed by controller)",
		node.Name, node.Spec.PodCIDR)
	return nil
}
<|endoftext|>


<commit_before>package dnsr

import (
	"fmt"
	"strings"
	"sync"

	lru "github.com/hashicorp/golang-lru"
	"github.com/miekg/dns"
)

type Resolver struct {
	cache  *lru.Cache
	client *dns.Client
}

func New(size int) *Resolver {
	if size <= 0 {
		size = 10000
	}
	cache, _ := lru.New(size)
	r := &Resolver{
		client: &dns.Client{},
		cache:  cache,
	}
	r.cacheRoot()
	return r
}

func (r *Resolver) Resolve(qname string, qtype string) <-chan *RR {
	c := make(chan *RR, 20)
	go func() {
		qname = toLowerFQDN(qname)
		defer close(c)
		if rrs := r.cacheGet(qname, qtype); rrs != nil {
			inject(c, rrs...)
			return
		}
		pname, ok := qname, true
		if qtype == "NS" {
			pname, ok = parent(qname)
			if !ok {
				return
			}
		}
	outer:
		for ; ok; pname, ok = parent(pname) {
			for nrr := range r.Resolve(pname, "NS") {
				if nrr.Type != "NS" {
					continue
				}
				for arr := range r.Resolve(nrr.Value, "A") {
					if arr.Type != "A" { // FIXME: support AAAA records?
						continue
					}
					addr := arr.Value + ":53"
					dtype, ok := dns.StringToType[qtype]
					if !ok {
						dtype = dns.TypeA
					}
					qmsg := &dns.Msg{}
					qmsg.SetQuestion(qname, dtype)
					qmsg.MsgHdr.RecursionDesired = false
					// fmt.Printf(";; dig +norecurse @%s %s %s\n", a.A.String(), qname, dns.TypeToString[qtype])
					rmsg, dur, err := r.client.Exchange(qmsg, addr)
					if err != nil {
						continue // FIXME: handle errors better from flaky/failing NS servers
					}
					fmt.Printf("Exchange in %s: dig @%s %s %s\n", dur.String(), arr.Value, qname, qtype)
					r.saveDNSRR(rmsg.Answer...)
					r.saveDNSRR(rmsg.Ns...)
					r.saveDNSRR(rmsg.Extra...)
					if rmsg.Rcode == dns.RcodeNameError {
						r.cacheAdd(qname, nil) // FIXME: cache NXDOMAIN responses responsibly
						return
					}
					break outer
				}
			}
		}

		if rrs := r.cacheGet(qname, ""); rrs != nil {
			inject(c, rrs...)
			//return
			// for _, rr := range rrs {
			// 	c <- rr
			// 	if qtype == "CNAME" || rr.Type != "CNAME" {
			// 		continue
			// 	}
			// 	fmt.Printf("Checking CNAME: %s\n", rr.String())
			// 	for qrr := range r.Resolve(rr.Value, qtype) {
			// 		r.cacheAdd(qname, qrr)
			// 		c <- qrr
			// 		break
			// 	}
			// }
			// return
		}

		// r.cacheAdd(qname, nil)
		// fmt.Printf("Checking for CNAMES! %s\n", qname)

		// FIXME: will it ever make it here?
		for _, crr := range r.cacheGet(qname, "CNAME") {
			fmt.Printf("Checking CNAME: %s\n", crr.String())
			for rr := range r.Resolve(crr.Value, qtype) {
				r.cacheAdd(qname, rr)
				c <- rr
			}
		}
	}()
	return c
}

type RR struct {
	Name  string
	Type  string
	Value string
}

func (rr *RR) String() string {
	return rr.Name + "\t      3600\tIN\t" + rr.Type + "\t" + rr.Value
}

func convertRR(drr dns.RR) *RR {
	switch t := drr.(type) {
	case *dns.NS:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], t.Ns}
	case *dns.CNAME:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], t.Target}
	case *dns.A:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], t.A.String()}
	case *dns.AAAA:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], t.AAAA.String()}
	case *dns.TXT:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], strings.Join(t.Txt, "\t")}
	default:
		// fmt.Printf("%s\n", drr.String())
	}
	return nil
}

func inject(c chan<- *RR, rrs ...*RR) {
	for _, rr := range rrs {
		c <- rr
	}
}

func parent(name string) (string, bool) {
	labels := dns.SplitDomainName(name)
	if labels == nil {
		return "", false
	}
	return toLowerFQDN(strings.Join(labels[1:], ".")), true
}

func toLowerFQDN(name string) string {
	return dns.Fqdn(strings.ToLower(name))
}

type key struct {
	Name string
	Type string
}

type entry struct {
	m   sync.RWMutex
	rrs map[RR]struct{}
}

func (r *Resolver) cacheRoot() {
	for t := range dns.ParseZone(strings.NewReader(root), "", "") {
		if t.Error == nil {
			r.saveDNSRR(t.RR)
		}
	}
}

// saveDNSRR saves 1 or more DNS records to the resolver cache.
func (r *Resolver) saveDNSRR(drrs ...dns.RR) {
	for _, drr := range drrs {
		if rr := convertRR(drr); rr != nil {
			r.cacheAdd(rr.Name, rr)
		}
	}
}

// cacheAdd adds 0 or more DNS records to the resolver cache for a specific
// domain name and record type. This ensures the cache entry exists, even
// if empty, for NXDOMAIN responses.
func (r *Resolver) cacheAdd(qname string, rr *RR) {
	qname = toLowerFQDN(qname)
	e := r.getEntry(qname)
	if e == nil {
		e = &entry{rrs: make(map[RR]struct{}, 0)}
		e.m.Lock()
		r.cache.Add(qname, e)
	} else {
		e.m.Lock()
	}
	defer e.m.Unlock()
	if rr != nil {
		e.rrs[*rr] = struct{}{}
	}
}

// cacheGet returns a randomly ordered slice of DNS records.
func (r *Resolver) cacheGet(qname string, qtype string) []*RR {
	e := r.getEntry(qname)
	if e == nil {
		return nil
	}
	e.m.RLock()
	defer e.m.RUnlock()
	if len(e.rrs) == 0 {
		return []*RR{}
	}
	rrs := make([]*RR, 0, len(e.rrs))
	for rr, _ := range e.rrs {
		// fmt.Printf("%s\n", rr.String())
		if qtype == "" || rr.Type == qtype {
			rrs = append(rrs, &RR{rr.Name, rr.Type, rr.Value})
		}
	}
	// if len(rrs) == 0 {
	// 	return nil
	// }
	return rrs
}

// getEntry returns a single cache entry or nil if an entry does not exist in the cache.
func (r *Resolver) getEntry(qname string) *entry {
	c, ok := r.cache.Get(qname)
	if !ok {
		return nil
	}
	e, ok := c.(*entry)
	if !ok {
		return nil
	}
	return e
}
<commit_msg>move root zone into separate non-volatile Resolver<commit_after>package dnsr

import (
	"strings"
	"sync"

	lru "github.com/hashicorp/golang-lru"
	"github.com/miekg/dns"
)

var Root *Resolver

func init() {
	Root = New(strings.Count(root, "\n"))
	for t := range dns.ParseZone(strings.NewReader(root), "", "") {
		if t.Error == nil {
			Root.saveDNSRR(t.RR)
		}
	}
}

type Resolver struct {
	cache  *lru.Cache
	client *dns.Client
}

func New(size int) *Resolver {
	if size <= 0 {
		size = 10000
	}
	cache, _ := lru.New(size)
	r := &Resolver{
		client: &dns.Client{},
		cache:  cache,
	}
	return r
}

func (r *Resolver) Resolve(qname string, qtype string) <-chan *RR {
	c := make(chan *RR, 20)
	go func() {
		qname = toLowerFQDN(qname)
		defer close(c)
		if rrs := r.cacheGet(qname, qtype); rrs != nil {
			inject(c, rrs...)
			return
		}
		pname, ok := qname, true
		if qtype == "NS" {
			pname, ok = parent(qname)
			if !ok {
				return
			}
		}
	outer:
		for ; ok; pname, ok = parent(pname) {
			for nrr := range r.Resolve(pname, "NS") {
				if nrr.Type != "NS" {
					continue
				}
				for arr := range r.Resolve(nrr.Value, "A") {
					if arr.Type != "A" { // FIXME: support AAAA records?
						continue
					}
					addr := arr.Value + ":53"
					dtype, ok := dns.StringToType[qtype]
					if !ok {
						dtype = dns.TypeA
					}
					qmsg := &dns.Msg{}
					qmsg.SetQuestion(qname, dtype)
					qmsg.MsgHdr.RecursionDesired = false
					// fmt.Printf(";; dig +norecurse @%s %s %s\n", a.A.String(), qname, dns.TypeToString[qtype])
					rmsg, _, err := r.client.Exchange(qmsg, addr)
					if err != nil {
						continue // FIXME: handle errors better from flaky/failing NS servers
					}
					// fmt.Printf("Exchange in %s: dig @%s %s %s\n", dur.String(), arr.Value, qname, qtype)
					r.saveDNSRR(rmsg.Answer...)
					r.saveDNSRR(rmsg.Ns...)
					r.saveDNSRR(rmsg.Extra...)
					if rmsg.Rcode == dns.RcodeNameError {
						r.cacheAdd(qname, nil) // FIXME: cache NXDOMAIN responses responsibly
						return
					}
					break outer
				}
			}
		}

		if rrs := r.cacheGet(qname, ""); rrs != nil {
			inject(c, rrs...)
			//return
		}

		for _, crr := range r.cacheGet(qname, "CNAME") {
			// fmt.Printf("Checking CNAME: %s\n", crr.String())
			for rr := range r.Resolve(crr.Value, qtype) {
				r.cacheAdd(qname, rr)
				c <- rr
			}
		}
	}()
	return c
}

type RR struct {
	Name  string
	Type  string
	Value string
}

func (rr *RR) String() string {
	return rr.Name + "\t      3600\tIN\t" + rr.Type + "\t" + rr.Value
}

func convertRR(drr dns.RR) *RR {
	switch t := drr.(type) {
	case *dns.NS:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], t.Ns}
	case *dns.CNAME:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], t.Target}
	case *dns.A:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], t.A.String()}
	case *dns.AAAA:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], t.AAAA.String()}
	case *dns.TXT:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], strings.Join(t.Txt, "\t")}
	default:
		// fmt.Printf("%s\n", drr.String())
	}
	return nil
}

func inject(c chan<- *RR, rrs ...*RR) {
	for _, rr := range rrs {
		c <- rr
	}
}

func parent(name string) (string, bool) {
	labels := dns.SplitDomainName(name)
	if labels == nil {
		return "", false
	}
	return toLowerFQDN(strings.Join(labels[1:], ".")), true
}

func toLowerFQDN(name string) string {
	return dns.Fqdn(strings.ToLower(name))
}

type key struct {
	Name string
	Type string
}

type entry struct {
	m   sync.RWMutex
	rrs map[RR]struct{}
}

// saveDNSRR saves 1 or more DNS records to the resolver cache.
func (r *Resolver) saveDNSRR(drrs ...dns.RR) {
	for _, drr := range drrs {
		if rr := convertRR(drr); rr != nil {
			r.cacheAdd(rr.Name, rr)
		}
	}
}

// cacheAdd adds 0 or more DNS records to the resolver cache for a specific
// domain name and record type. This ensures the cache entry exists, even
// if empty, for NXDOMAIN responses.
func (r *Resolver) cacheAdd(qname string, rr *RR) {
	qname = toLowerFQDN(qname)
	e := r.getEntry(qname)
	if e == nil {
		e = &entry{rrs: make(map[RR]struct{}, 0)}
		e.m.Lock()
		r.cache.Add(qname, e)
	} else {
		e.m.Lock()
	}
	defer e.m.Unlock()
	if rr != nil {
		e.rrs[*rr] = struct{}{}
	}
}

// cacheGet returns a randomly ordered slice of DNS records.
func (r *Resolver) cacheGet(qname string, qtype string) []*RR {
	e := r.getEntry(qname)
	if e == nil && r != Root {
		e = Root.getEntry(qname)
	}
	if e == nil {
		return nil
	}
	e.m.RLock()
	defer e.m.RUnlock()
	if len(e.rrs) == 0 {
		return []*RR{}
	}
	rrs := make([]*RR, 0, len(e.rrs))
	for rr, _ := range e.rrs {
		// fmt.Printf("%s\n", rr.String())
		if qtype == "" || rr.Type == qtype {
			rrs = append(rrs, &RR{rr.Name, rr.Type, rr.Value})
		}
	}
	return rrs
}

// getEntry returns a single cache entry or nil if an entry does not exist in the cache.
func (r *Resolver) getEntry(qname string) *entry {
	c, ok := r.cache.Get(qname)
	if !ok {
		return nil
	}
	e, ok := c.(*entry)
	if !ok {
		return nil
	}
	return e
}
<|endoftext|>


<commit_before>package weatherupdate

import (
	"time"

	"golang.org/x/net/context"
	"google.golang.org/appengine/datastore"
	"google.golang.org/appengine/log"
)

/*
	history := &weatherHistory{r.Weather[0].Main, r.Weather[0].Description}
	history.update(ctx)
*/

type weatherHistory struct {
	Main        string
	Description string
}

func (wh *weatherHistory) load(ctx context.Context) {
	key := datastore.NewKey(ctx, "WeatherHistory", weatherHistoryKey, 0, nil)
	if err := datastore.Get(ctx, key, wh); err != nil {
		log.Errorf(ctx, "could not load weather history from datastore: %v", err)
	}
}

func (wh *weatherHistory) update(ctx context.Context, results weatherResponse) {
	if wh == nil {
		log.Errorf(ctx, "invalid weather history")
		return
	}

	for _, r := range results.List {
		t := time.Unix(r.Dt, 0)
		if isWeddingDay(t) {
			wh.Main = r.Weather[0].Main
			wh.Description = r.Weather[0].Description
		}
	}

	key := datastore.NewKey(ctx, "WeatherHistory", weatherHistoryKey, 0, nil)
	// key := datastore.NewIncompleteKey(ctx, "WeatherHistory", nil)
	if _, err := datastore.Put(ctx, key, wh); err != nil {
		log.Errorf(ctx, "could not update weather history in datastore: %v", err)
	}
}

func (wh *weatherHistory) changed(results weatherResponse) bool {
	if wh == nil {
		return false
	}

	for _, r := range results.List {
		t := time.Unix(r.Dt, 0)
		if isWeddingDay(t) {
			return r.Weather[0].Main != wh.Main || r.Weather[0].Description != wh.Description
		}
	}
	return false
}
<commit_msg>Removed comments.<commit_after>package weatherupdate

import (
	"time"

	"golang.org/x/net/context"
	"google.golang.org/appengine/datastore"
	"google.golang.org/appengine/log"
)

type weatherHistory struct {
	Main        string
	Description string
}

func (wh *weatherHistory) load(ctx context.Context) {
	key := datastore.NewKey(ctx, "WeatherHistory", weatherHistoryKey, 0, nil)
	if err := datastore.Get(ctx, key, wh); err != nil {
		log.Errorf(ctx, "could not load weather history from datastore: %v", err)
	}
}

func (wh *weatherHistory) update(ctx context.Context, results weatherResponse) {
	if wh == nil {
		log.Errorf(ctx, "invalid weather history")
		return
	}

	for _, r := range results.List {
		t := time.Unix(r.Dt, 0)
		if isWeddingDay(t) {
			wh.Main = r.Weather[0].Main
			wh.Description = r.Weather[0].Description
		}
	}

	key := datastore.NewKey(ctx, "WeatherHistory", weatherHistoryKey, 0, nil)
	if _, err := datastore.Put(ctx, key, wh); err != nil {
		log.Errorf(ctx, "could not update weather history in datastore: %v", err)
	}
}

func (wh *weatherHistory) changed(results weatherResponse) bool {
	if wh == nil {
		return false
	}

	for _, r := range results.List {
		t := time.Unix(r.Dt, 0)
		if isWeddingDay(t) {
			return r.Weather[0].Main != wh.Main || r.Weather[0].Description != wh.Description
		}
	}
	return false
}
<|endoftext|>


<commit_before><commit_msg>accept string for slice of bytes<commit_after><|endoftext|>


<commit_before>// Copyright 2013 The go-instagram AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package instagram

import (
	"fmt"
	"net/url"
	"strconv"
)

// MediaService handles communication with the media related
// methods of the Instagram API.
//
// Instagram API docs: http://instagram.com/developer/endpoints/media/
type MediaService struct {
	client *Client
}

// Media represents a single media (image or video) on Instagram.
type Media struct {
	Type         string         `json:"type,omitempty"`
	UsersInPhoto []*UserInPhoto `json:"users_in_photo,omitempty"`
	Filter       string         `json:"filter,omitempty"`
	Tags         []string       `json:"tags,omitempty"`
	Comments     *MediaComments `json:"comments,omitempty"`
	Caption      *MediaCaption  `json:"caption,omitempty"`
	Likes        *MediaLikes    `json:"likes,omitempty"`
	Link         string         `json:"link,omitempty"`
	User         *User          `json:"user,omitempty"`
	UserHasLiked bool           `json:"user_has_liked,omitempty"`
	CreatedTime  int64          `json:"created_time,string,omitempty"`
	Images       *MediaImages   `json:"images,omitempty"`
	ID           string         `json:"id,omitempty"`
	Location     *Location      `json:"location,omitempty"`
}

// MediaComments represents comments on Instagram's media.
type MediaComments struct {
	Count int        `json:"count,omitempty"`
	Data  []*Comment `json:"data,omitempty"`
}

// MediaLikes represents likes on Instagram's media.
type MediaLikes struct {
	Count int `json:"count,omitempty"`
	Data  []*User
}

// MediaCaption represents caption on Instagram's media.
type MediaCaption struct {
	CreatedTime int64  `json:"created_time,string,omitempty"`
	Text        string `json:"text,omitempty"`
	From        *User  `json:"from,omitempty"`
	ID          string `json:"id,omitempty"`
}

// UserInPhoto represents a single user, with its position, on Instagram photo.
type UserInPhoto struct {
	User     *User                `json:"user,omitempty"`
	Position *UserInPhotoPosition `json:"position,omitempty"`
}

// UserInPhotoPosition represents position of the user on Instagram photo.
type UserInPhotoPosition struct {
	x float64 `json:"x,omitempty"`
	y float64 `json:"y,omitempty"`
}

// MediaImages represents MediaImage with various resolutions.
type MediaImages struct {
	LowResolution      *MediaImage `json:"low_resolution,omitempty"`
	Thumbnail          *MediaImage `json:"thumbnail,omitempty"`
	StandardResolution *MediaImage `json:"standard_resolution,omitempty"`
}

// MediaImage represents Instagram media with type image.
type MediaImage struct {
	URL    string `json:"url,omitempty"`
	Width  int    `json:"width,omitempty"`
	Height int    `json:"height,omitempty"`
}

// MediaVideos represents MediaVideo with various resolutions.
type MediaVideos struct {
	LowResolution      *MediaVideo `json:"low_resolution,omitempty"`
	Thumbnail          *MediaVideo `json:"thumbnail,omitempty"`
	StandardResolution *MediaVideo `json:"standard_resolution,omitempty"`
}

// MediaVideo represents Instagram media with type video.
type MediaVideo struct {
	URL    string `json:"url,omitempty"`
	Width  int    `json:"width,omitempty"`
	Height int    `json:"height,omitempty"`
}

// Get information about a media object.
//
// Instagram API docs: http://instagram.com/developer/endpoints/media/#get_media
func (s *MediaService) Get(mediaId string) (*Media, error) {
	u := fmt.Sprintf("media/%v", mediaId)
	req, err := s.client.NewRequest("GET", u, "")
	if err != nil {
		return nil, err
	}

	media := new(Media)
	_, err = s.client.Do(req, media)
	return media, err
}

// Search return search results for media in a given area.
//
// http://instagram.com/developer/endpoints/media/#get_media_search
func (s *MediaService) Search(opt *Parameters) ([]Media, *ResponsePagination, error) {
	u := "media/search"
	if opt != nil {
		params := url.Values{}
		if opt.Lat != 0 {
			params.Add("lat", strconv.FormatFloat(opt.Lat, 'f', 7, 64))
		}
		if opt.Lng != 0 {
			params.Add("lng", strconv.FormatFloat(opt.Lng, 'f', 7, 64))
		}
		if opt.MinTimestamp != 0 {
			params.Add("min_timestamp", strconv.FormatInt(opt.MinTimestamp, 10))
		}
		if opt.MaxTimestamp != 0 {
			params.Add("max_timestamp", strconv.FormatInt(opt.MaxTimestamp, 10))
		}
		if opt.Distance != 0 {
			params.Add("distance", strconv.FormatFloat(opt.Distance, 'f', 7, 64))
		}
		u += "?" + params.Encode()
	}

	req, err := s.client.NewRequest("GET", u, "")
	if err != nil {
		return nil, nil, err
	}

	media := new([]Media)

	_, err = s.client.Do(req, media)

	page := new(ResponsePagination)
	if s.client.Response.Pagination != nil {
		page = s.client.Response.Pagination
	}

	return *media, page, err
}

// Popular gets a list of what media is most popular at the moment.
//
// Instagram API docs: http://instagram.com/developer/endpoints/media/#get_media_popular
func (s *MediaService) Popular() ([]Media, *ResponsePagination, error) {
	u := "media/popular"
	req, err := s.client.NewRequest("GET", u, "")
	if err != nil {
		return nil, nil, err
	}

	media := new([]Media)
	_, err = s.client.Do(req, media)

	page := new(ResponsePagination)
	if s.client.Response.Pagination != nil {
		page = s.client.Response.Pagination
	}

	return *media, page, err
}
<commit_msg>Added MediaLocation to MediaService.<commit_after>// Copyright 2013 The go-instagram AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package instagram

import (
	"fmt"
	"net/url"
	"strconv"
)

// MediaService handles communication with the media related
// methods of the Instagram API.
//
// Instagram API docs: http://instagram.com/developer/endpoints/media/
type MediaService struct {
	client *Client
}

// Media represents a single media (image or video) on Instagram.
type Media struct {
	Type         string         `json:"type,omitempty"`
	UsersInPhoto []*UserInPhoto `json:"users_in_photo,omitempty"`
	Filter       string         `json:"filter,omitempty"`
	Tags         []string       `json:"tags,omitempty"`
	Comments     *MediaComments `json:"comments,omitempty"`
	Caption      *MediaCaption  `json:"caption,omitempty"`
	Likes        *MediaLikes    `json:"likes,omitempty"`
	Link         string         `json:"link,omitempty"`
	User         *User          `json:"user,omitempty"`
	UserHasLiked bool           `json:"user_has_liked,omitempty"`
	CreatedTime  int64          `json:"created_time,string,omitempty"`
	Images       *MediaImages   `json:"images,omitempty"`
	ID           string         `json:"id,omitempty"`
	Location     *MediaLocation `json:"location,omitempty"`
}

// MediaComments represents comments on Instagram's media.
type MediaComments struct {
	Count int        `json:"count,omitempty"`
	Data  []*Comment `json:"data,omitempty"`
}

// MediaLikes represents likes on Instagram's media.
type MediaLikes struct {
	Count int `json:"count,omitempty"`
	Data  []*User
}

// MediaCaption represents caption on Instagram's media.
type MediaCaption struct {
	CreatedTime int64  `json:"created_time,string,omitempty"`
	Text        string `json:"text,omitempty"`
	From        *User  `json:"from,omitempty"`
	ID          string `json:"id,omitempty"`
}

// UserInPhoto represents a single user, with its position, on Instagram photo.
type UserInPhoto struct {
	User     *User                `json:"user,omitempty"`
	Position *UserInPhotoPosition `json:"position,omitempty"`
}

// UserInPhotoPosition represents position of the user on Instagram photo.
type UserInPhotoPosition struct {
	x float64 `json:"x,omitempty"`
	y float64 `json:"y,omitempty"`
}

// MediaImages represents MediaImage with various resolutions.
type MediaImages struct {
	LowResolution      *MediaImage `json:"low_resolution,omitempty"`
	Thumbnail          *MediaImage `json:"thumbnail,omitempty"`
	StandardResolution *MediaImage `json:"standard_resolution,omitempty"`
}

// MediaImage represents Instagram media with type image.
type MediaImage struct {
	URL    string `json:"url,omitempty"`
	Width  int    `json:"width,omitempty"`
	Height int    `json:"height,omitempty"`
}

// MediaVideos represents MediaVideo with various resolutions.
type MediaVideos struct {
	LowResolution      *MediaVideo `json:"low_resolution,omitempty"`
	Thumbnail          *MediaVideo `json:"thumbnail,omitempty"`
	StandardResolution *MediaVideo `json:"standard_resolution,omitempty"`
}

// MediaVideo represents Instagram media with type video.
type MediaVideo struct {
	URL    string `json:"url,omitempty"`
	Width  int    `json:"width,omitempty"`
	Height int    `json:"height,omitempty"`
}

// MediaLocation represents information about a location.
//
// There's Location type under LocationsService, the different is
// the ID type. I've reported this inconsistency to Instagram
// https://groups.google.com/forum/#!topic/instagram-api-developers/Fty5lOsOGEg
type MediaLocation struct {
	ID        int     `json:"id,omitempty"`
	Name      string  `json:"name,omitempty"`
	Latitude  float64 `json:"latitude,omitempty"`
	Longitude float64 `json:"longitude,omitempty"`
}

// Get information about a media object.
//
// Instagram API docs: http://instagram.com/developer/endpoints/media/#get_media
func (s *MediaService) Get(mediaId string) (*Media, error) {
	u := fmt.Sprintf("media/%v", mediaId)
	req, err := s.client.NewRequest("GET", u, "")
	if err != nil {
		return nil, err
	}

	media := new(Media)
	_, err = s.client.Do(req, media)
	return media, err
}

// Search return search results for media in a given area.
//
// http://instagram.com/developer/endpoints/media/#get_media_search
func (s *MediaService) Search(opt *Parameters) ([]Media, *ResponsePagination, error) {
	u := "media/search"
	if opt != nil {
		params := url.Values{}
		if opt.Lat != 0 {
			params.Add("lat", strconv.FormatFloat(opt.Lat, 'f', 7, 64))
		}
		if opt.Lng != 0 {
			params.Add("lng", strconv.FormatFloat(opt.Lng, 'f', 7, 64))
		}
		if opt.MinTimestamp != 0 {
			params.Add("min_timestamp", strconv.FormatInt(opt.MinTimestamp, 10))
		}
		if opt.MaxTimestamp != 0 {
			params.Add("max_timestamp", strconv.FormatInt(opt.MaxTimestamp, 10))
		}
		if opt.Distance != 0 {
			params.Add("distance", strconv.FormatFloat(opt.Distance, 'f', 7, 64))
		}
		u += "?" + params.Encode()
	}

	req, err := s.client.NewRequest("GET", u, "")
	if err != nil {
		return nil, nil, err
	}

	media := new([]Media)

	_, err = s.client.Do(req, media)

	page := new(ResponsePagination)
	if s.client.Response.Pagination != nil {
		page = s.client.Response.Pagination
	}

	return *media, page, err
}

// Popular gets a list of what media is most popular at the moment.
//
// Instagram API docs: http://instagram.com/developer/endpoints/media/#get_media_popular
func (s *MediaService) Popular() ([]Media, *ResponsePagination, error) {
	u := "media/popular"
	req, err := s.client.NewRequest("GET", u, "")
	if err != nil {
		return nil, nil, err
	}

	media := new([]Media)
	_, err = s.client.Do(req, media)

	page := new(ResponsePagination)
	if s.client.Response.Pagination != nil {
		page = s.client.Response.Pagination
	}

	return *media, page, err
}
<|endoftext|>


<commit_before>package frame

import (
	"image"

	"github.com/as/frame/box"
)

// Refresh renders the entire frame, including the underlying
// bitmap. Refresh should not be called after insertion and deletion
// unless the frame's RGBA bitmap was painted over by another
// draw operation.
func (f *Frame) Refresh() {
	cols := f.Color
	if f.p0 == f.p1 {
		ticked := f.Ticked
		if ticked {
			f.tickat(f.PointOf(f.p0), false)
		}
		f.drawsel(f.PointOf(0), 0, f.Nchars, cols.Back, cols.Text)
		if ticked {
			f.tickat(f.PointOf(f.p0), true)
		}
		return
	}
	pt := f.PointOf(0)
	pt = f.drawsel(pt, 0, f.p0, cols.Back, cols.Text)
	pt = f.drawsel(pt, f.p0, f.p1, cols.Hi.Back, cols.Hi.Text)
	f.drawsel(pt, f.p1, f.Nchars, cols.Back, cols.Text)
}

// RedrawAt renders the frame's bitmap starting at pt and working downwards.
func (f *Frame) RedrawAt(pt image.Point, text, back image.Image) {
	f.redrawRun0(&(f.Run), pt, text, back)
}

// Redraw draws the range [p0:p1] at the given pt.
func (f *Frame) Redraw(pt image.Point, p0, p1 int64, issel bool) {
	if f.Ticked {
		f.tickat(f.PointOf(f.p0), false)
	}

	if p0 == p1 {
		f.tickat(pt, issel)
		return
	}

	pal := f.Color.Palette
	if issel {
		pal = f.Color.Hi
	}
	f.drawsel(pt, p0, p1, pal.Back, pal.Text)
}

// Recolor redraws the range p0:p1 with the given palette
func (f *Frame) Recolor(pt image.Point, p0, p1 int64, cols Palette) {
	f.drawsel(pt, p0, p1, cols.Back, cols.Text)
	f.modified = true
}

func (f *Frame) redrawRun0(r *box.Run, pt image.Point, text, back image.Image) image.Point {
	nb := 0
	for ; nb < r.Nbox; nb++ {
		b := &r.Box[nb]
		pt = f.wrapMax(pt, b)
		//if !f.noredraw && b.nrune >= 0 {
		if b.Nrune >= 0 {
			f.StringBG(f.b, pt, text, image.ZP, f.Font, b.Ptr, back, image.ZP)
		}
		pt.X += b.Width
	}
	return pt
}

func (f *Frame) drawsel(pt image.Point, p0, p1 int64, back, text image.Image) image.Point {
	{ // doubled
		p0, p1 := int(p0), int(p1)
		q0 := 0
		trim := false
		defer f.Flush()

		// Step into box, start coloring it
		// How much does this lambda slow things down?
		stepFill := func(bn int) {
			qt := pt
			if pt = f.wrapMax(pt, (&f.Box[bn])); pt.Y > qt.Y {
				r := image.Rect(qt.X, qt.Y, f.r.Max.X, pt.Y)
				f.Draw(f.b, r, back, qt, f.op)
				//f.Flush(r)
			}
		}
		nb := 0
		for ; nb < f.Nbox && q0+f.LenBox(nb) <= p0; nb++ {
			// region -2: skip
			q0 += f.LenBox(nb)
		}

		for ; nb < f.Nbox && q0 < p1; nb++ {
			if q0 >= p0 { // region 0 or 1 or 2
				stepFill(nb)
			}
			ptr := f.BoxBytes(nb)
			if q0 < p0 {
				// region -1: shift p right inside the selection
				ptr = ptr[p0-q0:]
				q0 = p0
			}

			trim = false
			if q1 := q0 + len(ptr); q1 >= p1 {
				// region 1: would draw too much, retract the selection
				lim := len(ptr) - (q1 - p1)
				ptr = ptr[:lim]
				trim = true
			}
			w := f.WidthBox(nb, ptr)
			f.Draw(f.b, image.Rect(pt.X, pt.Y, min(pt.X+w, f.r.Max.X), pt.Y+f.Font.Dy()), back, pt, f.op)
			if f.PlainBox(nb) {
				f.StringBG(f.b, pt, text, image.ZP, f.Font, ptr, nil, image.ZP)
			}
			pt.X += w

			if q0 += len(ptr); q0 >= p1 {
				break
			}
		}

		if p1 > p0 && nb != 0 && nb < f.Nbox && f.LenBox(nb-1) > 0 && !trim {
			stepFill(nb)
		}
		return pt
	}
}
<commit_msg>try to avoid invoking stringNBG<commit_after>package frame

import (
	"image"

	"github.com/as/frame/box"
)

// Refresh renders the entire frame, including the underlying
// bitmap. Refresh should not be called after insertion and deletion
// unless the frame's RGBA bitmap was painted over by another
// draw operation.
func (f *Frame) Refresh() {
	cols := f.Color
	if f.p0 == f.p1 {
		ticked := f.Ticked
		if ticked {
			f.tickat(f.PointOf(f.p0), false)
		}
		f.drawsel(f.PointOf(0), 0, f.Nchars, cols.Back, cols.Text)
		if ticked {
			f.tickat(f.PointOf(f.p0), true)
		}
		return
	}
	pt := f.PointOf(0)
	pt = f.drawsel(pt, 0, f.p0, cols.Back, cols.Text)
	pt = f.drawsel(pt, f.p0, f.p1, cols.Hi.Back, cols.Hi.Text)
	f.drawsel(pt, f.p1, f.Nchars, cols.Back, cols.Text)
}

// RedrawAt renders the frame's bitmap starting at pt and working downwards.
func (f *Frame) RedrawAt(pt image.Point, text, back image.Image) {
	f.redrawRun0(&(f.Run), pt, text, back)
}

// Redraw draws the range [p0:p1] at the given pt.
func (f *Frame) Redraw(pt image.Point, p0, p1 int64, issel bool) {
	if f.Ticked {
		f.tickat(f.PointOf(f.p0), false)
	}

	if p0 == p1 {
		f.tickat(pt, issel)
		return
	}

	pal := f.Color.Palette
	if issel {
		pal = f.Color.Hi
	}
	f.drawsel(pt, p0, p1, pal.Back, pal.Text)
}

// Recolor redraws the range p0:p1 with the given palette
func (f *Frame) Recolor(pt image.Point, p0, p1 int64, cols Palette) {
	f.drawsel(pt, p0, p1, cols.Back, cols.Text)
	f.modified = true
}

func (f *Frame) redrawRun0(r *box.Run, pt image.Point, text, back image.Image) image.Point {
	nb := 0
	for ; nb < r.Nbox; nb++ {
		b := &r.Box[nb]
		pt = f.wrapMax(pt, b)
		//if !f.noredraw && b.nrune >= 0 {
		if b.Nrune >= 0 {
			f.StringBG(f.b, pt, text, image.ZP, f.Font, b.Ptr, back, image.ZP)
		}
		pt.X += b.Width
	}
	return pt
}

func (f *Frame) drawsel(pt image.Point, p0, p1 int64, back, text image.Image) image.Point {
	{ // doubled
		p0, p1 := int(p0), int(p1)
		q0 := 0
		trim := false
		defer f.Flush()

		// Step into box, start coloring it
		// How much does this lambda slow things down?
		stepFill := func(bn int) {
			qt := pt
			if pt = f.wrapMax(pt, (&f.Box[bn])); pt.Y > qt.Y {
				r := image.Rect(qt.X, qt.Y, f.r.Max.X, pt.Y)
				f.Draw(f.b, r, back, qt, f.op)
				//f.Flush(r)
			}
		}
		nb := 0
		for ; nb < f.Nbox && q0+f.LenBox(nb) <= p0; nb++ {
			// region -2: skip
			q0 += f.LenBox(nb)
		}

		for ; nb < f.Nbox && q0 < p1; nb++ {
			if q0 >= p0 { // region 0 or 1 or 2
				stepFill(nb)
			}
			ptr := f.BoxBytes(nb)
			if q0 < p0 {
				// region -1: shift p right inside the selection
				ptr = ptr[p0-q0:]
				q0 = p0
			}

			trim = false
			if q1 := q0 + len(ptr); q1 >= p1 {
				// region 1: would draw too much, retract the selection
				lim := len(ptr) - (q1 - p1)
				ptr = ptr[:lim]
				trim = true
			}
			w := f.WidthBox(nb, ptr)
			if f.PlainBox(nb) {
				f.StringBG(f.b, pt, text, image.ZP, f.Font, ptr, back, image.ZP) //TODO(as): bug is that when back==nil, StringBG turns into StringNBG and that doesn't work with replacement runes.
			} else {
				f.Draw(f.b, image.Rect(pt.X, pt.Y, min(pt.X+w, f.r.Max.X), pt.Y+f.Font.Dy()), back, pt, f.op)

			}
			pt.X += w

			if q0 += len(ptr); q0 >= p1 {
				break
			}
		}

		if p1 > p0 && nb != 0 && nb < f.Nbox && f.LenBox(nb-1) > 0 && !trim {
			stepFill(nb)
		}
		return pt
	}
}
<|endoftext|>


<commit_before>package client

const (
	AzureCloudProviderType                              = "azureCloudProvider"
	AzureCloudProviderFieldAADClientCertPassword        = "aadClientCertPassword"
	AzureCloudProviderFieldAADClientCertPath            = "aadClientCertPath"
	AzureCloudProviderFieldAADClientID                  = "aadClientId"
	AzureCloudProviderFieldAADClientSecret              = "aadClientSecret"
	AzureCloudProviderFieldCloud                        = "cloud"
	AzureCloudProviderFieldCloudProviderBackoff         = "cloudProviderBackoff"
	AzureCloudProviderFieldCloudProviderBackoffDuration = "cloudProviderBackoffDuration"
	AzureCloudProviderFieldCloudProviderBackoffExponent = "cloudProviderBackoffExponent"
	AzureCloudProviderFieldCloudProviderBackoffJitter   = "cloudProviderBackoffJitter"
	AzureCloudProviderFieldCloudProviderBackoffRetries  = "cloudProviderBackoffRetries"
	AzureCloudProviderFieldCloudProviderRateLimit       = "cloudProviderRateLimit"
	AzureCloudProviderFieldCloudProviderRateLimitBucket = "cloudProviderRateLimitBucket"
	AzureCloudProviderFieldCloudProviderRateLimitQPS    = "cloudProviderRateLimitQPS"
	AzureCloudProviderFieldExcludeMasterFromStandardLB  = "excludeMasterFromStandardLB"
	AzureCloudProviderFieldLoadBalancerSku              = "loadBalancerSku"
	AzureCloudProviderFieldLocation                     = "location"
	AzureCloudProviderFieldMaximumLoadBalancerRuleCount = "maximumLoadBalancerRuleCount"
	AzureCloudProviderFieldPrimaryAvailabilitySetName   = "primaryAvailabilitySetName"
	AzureCloudProviderFieldPrimaryScaleSetName          = "primaryScaleSetName"
	AzureCloudProviderFieldResourceGroup                = "resourceGroup"
	AzureCloudProviderFieldRouteTableName               = "routeTableName"
	AzureCloudProviderFieldSecurityGroupName            = "securityGroupName"
	AzureCloudProviderFieldSubnetName                   = "subnetName"
	AzureCloudProviderFieldSubscriptionID               = "subscriptionId"
	AzureCloudProviderFieldTenantID                     = "tenantId"
	AzureCloudProviderFieldUseInstanceMetadata          = "useInstanceMetadata"
	AzureCloudProviderFieldUseManagedIdentityExtension  = "useManagedIdentityExtension"
	AzureCloudProviderFieldUserAssignedIdentityID       = "userAssignedIdentityID"
	AzureCloudProviderFieldVMType                       = "vmType"
	AzureCloudProviderFieldVnetName                     = "vnetName"
	AzureCloudProviderFieldVnetResourceGroup            = "vnetResourceGroup"
)

type AzureCloudProvider struct {
	AADClientCertPassword        string `json:"aadClientCertPassword,omitempty" yaml:"aadClientCertPassword,omitempty"`
	AADClientCertPath            string `json:"aadClientCertPath,omitempty" yaml:"aadClientCertPath,omitempty"`
	AADClientID                  string `json:"aadClientId,omitempty" yaml:"aadClientId,omitempty"`
	AADClientSecret              string `json:"aadClientSecret,omitempty" yaml:"aadClientSecret,omitempty"`
	Cloud                        string `json:"cloud,omitempty" yaml:"cloud,omitempty"`
	CloudProviderBackoff         bool   `json:"cloudProviderBackoff,omitempty" yaml:"cloudProviderBackoff,omitempty"`
	CloudProviderBackoffDuration int64  `json:"cloudProviderBackoffDuration,omitempty" yaml:"cloudProviderBackoffDuration,omitempty"`
	CloudProviderBackoffExponent int64  `json:"cloudProviderBackoffExponent,omitempty" yaml:"cloudProviderBackoffExponent,omitempty"`
	CloudProviderBackoffJitter   int64  `json:"cloudProviderBackoffJitter,omitempty" yaml:"cloudProviderBackoffJitter,omitempty"`
	CloudProviderBackoffRetries  int64  `json:"cloudProviderBackoffRetries,omitempty" yaml:"cloudProviderBackoffRetries,omitempty"`
	CloudProviderRateLimit       bool   `json:"cloudProviderRateLimit,omitempty" yaml:"cloudProviderRateLimit,omitempty"`
	CloudProviderRateLimitBucket int64  `json:"cloudProviderRateLimitBucket,omitempty" yaml:"cloudProviderRateLimitBucket,omitempty"`
	CloudProviderRateLimitQPS    int64  `json:"cloudProviderRateLimitQPS,omitempty" yaml:"cloudProviderRateLimitQPS,omitempty"`
	ExcludeMasterFromStandardLB  *bool  `json:"excludeMasterFromStandardLB,omitempty" yaml:"excludeMasterFromStandardLB,omitempty"`
	LoadBalancerSku              string `json:"loadBalancerSku,omitempty" yaml:"loadBalancerSku,omitempty"`
	Location                     string `json:"location,omitempty" yaml:"location,omitempty"`
	MaximumLoadBalancerRuleCount int64  `json:"maximumLoadBalancerRuleCount,omitempty" yaml:"maximumLoadBalancerRuleCount,omitempty"`
	PrimaryAvailabilitySetName   string `json:"primaryAvailabilitySetName,omitempty" yaml:"primaryAvailabilitySetName,omitempty"`
	PrimaryScaleSetName          string `json:"primaryScaleSetName,omitempty" yaml:"primaryScaleSetName,omitempty"`
	ResourceGroup                string `json:"resourceGroup,omitempty" yaml:"resourceGroup,omitempty"`
	RouteTableName               string `json:"routeTableName,omitempty" yaml:"routeTableName,omitempty"`
	SecurityGroupName            string `json:"securityGroupName,omitempty" yaml:"securityGroupName,omitempty"`
	SubnetName                   string `json:"subnetName,omitempty" yaml:"subnetName,omitempty"`
	SubscriptionID               string `json:"subscriptionId,omitempty" yaml:"subscriptionId,omitempty"`
	TenantID                     string `json:"tenantId,omitempty" yaml:"tenantId,omitempty"`
	UseInstanceMetadata          bool   `json:"useInstanceMetadata,omitempty" yaml:"useInstanceMetadata,omitempty"`
	UseManagedIdentityExtension  bool   `json:"useManagedIdentityExtension,omitempty" yaml:"useManagedIdentityExtension,omitempty"`
	UserAssignedIdentityID       string `json:"userAssignedIdentityID,omitempty" yaml:"userAssignedIdentityID,omitempty"`
	VMType                       string `json:"vmType,omitempty" yaml:"vmType,omitempty"`
	VnetName                     string `json:"vnetName,omitempty" yaml:"vnetName,omitempty"`
	VnetResourceGroup            string `json:"vnetResourceGroup,omitempty" yaml:"vnetResourceGroup,omitempty"`
}
<commit_msg>go generate files added after updating rke v1.4.0-rc4<commit_after>package client

const (
	AzureCloudProviderType                              = "azureCloudProvider"
	AzureCloudProviderFieldAADClientCertPassword        = "aadClientCertPassword"
	AzureCloudProviderFieldAADClientCertPath            = "aadClientCertPath"
	AzureCloudProviderFieldAADClientID                  = "aadClientId"
	AzureCloudProviderFieldAADClientSecret              = "aadClientSecret"
	AzureCloudProviderFieldCloud                        = "cloud"
	AzureCloudProviderFieldCloudProviderBackoff         = "cloudProviderBackoff"
	AzureCloudProviderFieldCloudProviderBackoffDuration = "cloudProviderBackoffDuration"
	AzureCloudProviderFieldCloudProviderBackoffExponent = "cloudProviderBackoffExponent"
	AzureCloudProviderFieldCloudProviderBackoffJitter   = "cloudProviderBackoffJitter"
	AzureCloudProviderFieldCloudProviderBackoffRetries  = "cloudProviderBackoffRetries"
	AzureCloudProviderFieldCloudProviderRateLimit       = "cloudProviderRateLimit"
	AzureCloudProviderFieldCloudProviderRateLimitBucket = "cloudProviderRateLimitBucket"
	AzureCloudProviderFieldCloudProviderRateLimitQPS    = "cloudProviderRateLimitQPS"
	AzureCloudProviderFieldExcludeMasterFromStandardLB  = "excludeMasterFromStandardLB"
	AzureCloudProviderFieldLoadBalancerSku              = "loadBalancerSku"
	AzureCloudProviderFieldLocation                     = "location"
	AzureCloudProviderFieldMaximumLoadBalancerRuleCount = "maximumLoadBalancerRuleCount"
	AzureCloudProviderFieldPrimaryAvailabilitySetName   = "primaryAvailabilitySetName"
	AzureCloudProviderFieldPrimaryScaleSetName          = "primaryScaleSetName"
	AzureCloudProviderFieldResourceGroup                = "resourceGroup"
	AzureCloudProviderFieldRouteTableName               = "routeTableName"
	AzureCloudProviderFieldSecurityGroupName            = "securityGroupName"
	AzureCloudProviderFieldSecurityGroupResourceGroup   = "securityGroupResourceGroup"
	AzureCloudProviderFieldSubnetName                   = "subnetName"
	AzureCloudProviderFieldSubscriptionID               = "subscriptionId"
	AzureCloudProviderFieldTags                         = "tags"
	AzureCloudProviderFieldTenantID                     = "tenantId"
	AzureCloudProviderFieldUseInstanceMetadata          = "useInstanceMetadata"
	AzureCloudProviderFieldUseManagedIdentityExtension  = "useManagedIdentityExtension"
	AzureCloudProviderFieldUserAssignedIdentityID       = "userAssignedIdentityID"
	AzureCloudProviderFieldVMType                       = "vmType"
	AzureCloudProviderFieldVnetName                     = "vnetName"
	AzureCloudProviderFieldVnetResourceGroup            = "vnetResourceGroup"
)

type AzureCloudProvider struct {
	AADClientCertPassword        string `json:"aadClientCertPassword,omitempty" yaml:"aadClientCertPassword,omitempty"`
	AADClientCertPath            string `json:"aadClientCertPath,omitempty" yaml:"aadClientCertPath,omitempty"`
	AADClientID                  string `json:"aadClientId,omitempty" yaml:"aadClientId,omitempty"`
	AADClientSecret              string `json:"aadClientSecret,omitempty" yaml:"aadClientSecret,omitempty"`
	Cloud                        string `json:"cloud,omitempty" yaml:"cloud,omitempty"`
	CloudProviderBackoff         bool   `json:"cloudProviderBackoff,omitempty" yaml:"cloudProviderBackoff,omitempty"`
	CloudProviderBackoffDuration int64  `json:"cloudProviderBackoffDuration,omitempty" yaml:"cloudProviderBackoffDuration,omitempty"`
	CloudProviderBackoffExponent int64  `json:"cloudProviderBackoffExponent,omitempty" yaml:"cloudProviderBackoffExponent,omitempty"`
	CloudProviderBackoffJitter   int64  `json:"cloudProviderBackoffJitter,omitempty" yaml:"cloudProviderBackoffJitter,omitempty"`
	CloudProviderBackoffRetries  int64  `json:"cloudProviderBackoffRetries,omitempty" yaml:"cloudProviderBackoffRetries,omitempty"`
	CloudProviderRateLimit       bool   `json:"cloudProviderRateLimit,omitempty" yaml:"cloudProviderRateLimit,omitempty"`
	CloudProviderRateLimitBucket int64  `json:"cloudProviderRateLimitBucket,omitempty" yaml:"cloudProviderRateLimitBucket,omitempty"`
	CloudProviderRateLimitQPS    int64  `json:"cloudProviderRateLimitQPS,omitempty" yaml:"cloudProviderRateLimitQPS,omitempty"`
	ExcludeMasterFromStandardLB  *bool  `json:"excludeMasterFromStandardLB,omitempty" yaml:"excludeMasterFromStandardLB,omitempty"`
	LoadBalancerSku              string `json:"loadBalancerSku,omitempty" yaml:"loadBalancerSku,omitempty"`
	Location                     string `json:"location,omitempty" yaml:"location,omitempty"`
	MaximumLoadBalancerRuleCount int64  `json:"maximumLoadBalancerRuleCount,omitempty" yaml:"maximumLoadBalancerRuleCount,omitempty"`
	PrimaryAvailabilitySetName   string `json:"primaryAvailabilitySetName,omitempty" yaml:"primaryAvailabilitySetName,omitempty"`
	PrimaryScaleSetName          string `json:"primaryScaleSetName,omitempty" yaml:"primaryScaleSetName,omitempty"`
	ResourceGroup                string `json:"resourceGroup,omitempty" yaml:"resourceGroup,omitempty"`
	RouteTableName               string `json:"routeTableName,omitempty" yaml:"routeTableName,omitempty"`
	SecurityGroupName            string `json:"securityGroupName,omitempty" yaml:"securityGroupName,omitempty"`
	SecurityGroupResourceGroup   string `json:"securityGroupResourceGroup,omitempty" yaml:"securityGroupResourceGroup,omitempty"`
	SubnetName                   string `json:"subnetName,omitempty" yaml:"subnetName,omitempty"`
	SubscriptionID               string `json:"subscriptionId,omitempty" yaml:"subscriptionId,omitempty"`
	Tags                         string `json:"tags,omitempty" yaml:"tags,omitempty"`
	TenantID                     string `json:"tenantId,omitempty" yaml:"tenantId,omitempty"`
	UseInstanceMetadata          bool   `json:"useInstanceMetadata,omitempty" yaml:"useInstanceMetadata,omitempty"`
	UseManagedIdentityExtension  bool   `json:"useManagedIdentityExtension,omitempty" yaml:"useManagedIdentityExtension,omitempty"`
	UserAssignedIdentityID       string `json:"userAssignedIdentityID,omitempty" yaml:"userAssignedIdentityID,omitempty"`
	VMType                       string `json:"vmType,omitempty" yaml:"vmType,omitempty"`
	VnetName                     string `json:"vnetName,omitempty" yaml:"vnetName,omitempty"`
	VnetResourceGroup            string `json:"vnetResourceGroup,omitempty" yaml:"vnetResourceGroup,omitempty"`
}
<|endoftext|>