<commit_before>package aws

import (
	"bytes"
	"fmt"
	"log"
	"sort"
	"time"

	"github.com/hashicorp/terraform/helper/hashcode"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/mitchellh/goamz/ec2"
)

func resourceAwsSecurityGroup() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsSecurityGroupCreate,
		Read:   resourceAwsSecurityGroupRead,
		Update: resourceAwsSecurityGroupUpdate,
		Delete: resourceAwsSecurityGroupDelete,

		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

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

			"vpc_id": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
				Computed: true,
			},

			"ingress": &schema.Schema{
				Type:     schema.TypeSet,
				Optional: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"from_port": &schema.Schema{
							Type:     schema.TypeInt,
							Required: true,
						},

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

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

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

						"security_groups": &schema.Schema{
							Type:     schema.TypeSet,
							Optional: true,
							Elem:     &schema.Schema{Type: schema.TypeString},
							Set: func(v interface{}) int {
								return hashcode.String(v.(string))
							},
						},

						"self": &schema.Schema{
							Type:     schema.TypeBool,
							Optional: true,
							Default:  false,
						},
					},
				},
				Set: resourceAwsSecurityGroupRuleHash,
			},

			"egress": &schema.Schema{
				Type:     schema.TypeSet,
				Optional: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"from_port": &schema.Schema{
							Type:     schema.TypeInt,
							Required: true,
						},

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

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

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

						"security_groups": &schema.Schema{
							Type:     schema.TypeSet,
							Optional: true,
							Elem:     &schema.Schema{Type: schema.TypeString},
							Set: func(v interface{}) int {
								return hashcode.String(v.(string))
							},
						},

						"self": &schema.Schema{
							Type:     schema.TypeBool,
							Optional: true,
							Default:  false,
						},
					},
				},
				Set: resourceAwsSecurityGroupRuleHash,
			},

			"owner_id": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"tags": tagsSchema(),
		},
	}
}

func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error {
	ec2conn := meta.(*AWSClient).ec2conn

	securityGroupOpts := ec2.SecurityGroup{
		Name: d.Get("name").(string),
	}

	if v := d.Get("vpc_id"); v != nil {
		securityGroupOpts.VpcId = v.(string)
	}

	if v := d.Get("description"); v != nil {
		securityGroupOpts.Description = v.(string)
	}

	log.Printf(
		"[DEBUG] Security Group create configuration: %#v", securityGroupOpts)
	createResp, err := ec2conn.CreateSecurityGroup(securityGroupOpts)
	if err != nil {
		return fmt.Errorf("Error creating Security Group: %s", err)
	}

	d.SetId(createResp.Id)

	log.Printf("[INFO] Security Group ID: %s", d.Id())

	// Wait for the security group to truly exist
	log.Printf(
		"[DEBUG] Waiting for Security Group (%s) to exist",
		d.Id())
	stateConf := &resource.StateChangeConf{
		Pending: []string{""},
		Target:  "exists",
		Refresh: SGStateRefreshFunc(ec2conn, d.Id()),
		Timeout: 1 * time.Minute,
	}
	if _, err := stateConf.WaitForState(); err != nil {
		return fmt.Errorf(
			"Error waiting for Security Group (%s) to become available: %s",
			d.Id(), err)
	}

	return resourceAwsSecurityGroupUpdate(d, meta)
}

func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) error {
	ec2conn := meta.(*AWSClient).ec2conn

	sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())()
	if err != nil {
		return err
	}
	if sgRaw == nil {
		d.SetId("")
		return nil
	}

	sg := sgRaw.(*ec2.SecurityGroupInfo)

	ingressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPerms)
	egressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPermsEgress)

	d.Set("description", sg.Description)
	d.Set("name", sg.Name)
	d.Set("vpc_id", sg.VpcId)
	d.Set("owner_id", sg.OwnerId)
	d.Set("ingress", ingressRules)
	d.Set("egress", egressRules)
	d.Set("tags", tagsToMap(sg.Tags))

	return nil
}

func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error {
	ec2conn := meta.(*AWSClient).ec2conn

	sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())()
	if err != nil {
		return err
	}
	if sgRaw == nil {
		d.SetId("")
		return nil
	}
	group := sgRaw.(*ec2.SecurityGroupInfo).SecurityGroup

	err = resourceAwsSecurityGroupUpdateRules(d, "ingress", meta, group)
	if err != nil {
		return err
	}

	if d.Get("vpc_id") != nil {
		err = resourceAwsSecurityGroupUpdateRules(d, "egress", meta, group)
		if err != nil {
			return err
		}
	}

	if err := setTags(ec2conn, d); err != nil {
		return err
	}

	d.SetPartial("tags")

	return resourceAwsSecurityGroupRead(d, meta)
}

func resourceAwsSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error {
	ec2conn := meta.(*AWSClient).ec2conn

	log.Printf("[DEBUG] Security Group destroy: %v", d.Id())

	return resource.Retry(5*time.Minute, func() error {
		_, err := ec2conn.DeleteSecurityGroup(ec2.SecurityGroup{Id: d.Id()})
		if err != nil {
			ec2err, ok := err.(*ec2.Error)
			if !ok {
				return err
			}

			switch ec2err.Code {
			case "InvalidGroup.NotFound":
				return nil
			case "DependencyViolation":
				// If it is a dependency violation, we want to retry
				return err
			default:
				// Any other error, we want to quit the retry loop immediately
				return resource.RetryError{err}
			}
		}

		return nil
	})
}

func resourceAwsSecurityGroupRuleHash(v interface{}) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})
	buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int)))
	buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int)))
	buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string)))

	// 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 v, ok := m["cidr_blocks"]; ok {
		vs := v.([]interface{})
		s := make([]string, len(vs))
		for i, raw := range vs {
			s[i] = raw.(string)
		}
		sort.Strings(s)

		for _, v := range s {
			buf.WriteString(fmt.Sprintf("%s-", v))
		}
	}
	if v, ok := m["security_groups"]; ok {
		vs := v.(*schema.Set).List()
		s := make([]string, len(vs))
		for i, raw := range vs {
			s[i] = raw.(string)
		}
		sort.Strings(s)

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

	return hashcode.String(buf.String())
}

func resourceAwsSecurityGroupIPPermGather(d *schema.ResourceData, permissions []ec2.IPPerm) []map[string]interface{} {
	ruleMap := make(map[string]map[string]interface{})
	for _, perm := range permissions {
		k := fmt.Sprintf("%s-%d-%d", perm.Protocol, perm.FromPort, perm.ToPort)
		m, ok := ruleMap[k]
		if !ok {
			m = make(map[string]interface{})
			ruleMap[k] = m
		}

		m["from_port"] = perm.FromPort
		m["to_port"] = perm.ToPort
		m["protocol"] = perm.Protocol

		if len(perm.SourceIPs) > 0 {
			raw, ok := m["cidr_blocks"]
			if !ok {
				raw = make([]string, 0, len(perm.SourceIPs))
			}
			list := raw.([]string)

			list = append(list, perm.SourceIPs...)
			m["cidr_blocks"] = list
		}

		var groups []string
		if len(perm.SourceGroups) > 0 {
			groups = flattenSecurityGroups(perm.SourceGroups)
		}
		for i, id := range groups {
			if id == d.Id() {
				groups[i], groups = groups[len(groups)-1], groups[:len(groups)-1]
				m["self"] = true
			}
		}

		if len(groups) > 0 {
			raw, ok := m["security_groups"]
			if !ok {
				raw = make([]string, 0, len(groups))
			}
			list := raw.([]string)

			list = append(list, groups...)
			m["security_groups"] = list
		}
	}
	rules := make([]map[string]interface{}, 0, len(ruleMap))
	for _, m := range ruleMap {
		rules = append(rules, m)
	}

	return rules
}

func resourceAwsSecurityGroupUpdateRules(
	d *schema.ResourceData, ruleset string,
	meta interface{}, group ec2.SecurityGroup) error {
	if d.HasChange(ruleset) {
		o, n := d.GetChange(ruleset)
		if o == nil {
			o = new(schema.Set)
		}
		if n == nil {
			n = new(schema.Set)
		}

		os := o.(*schema.Set)
		ns := n.(*schema.Set)

		remove := expandIPPerms(d.Id(), os.Difference(ns).List())
		add := expandIPPerms(d.Id(), ns.Difference(os).List())

		// TODO: We need to handle partial state better in the in-between
		// in this update.

		// TODO: It'd be nicer to authorize before removing, but then we have
		// to deal with complicated unrolling to get individual CIDR blocks
		// to avoid authorizing already authorized sources. Removing before
		// adding is easier here, and Terraform should be fast enough to
		// not have service issues.

		if len(remove) > 0 || len(add) > 0 {
			ec2conn := meta.(*AWSClient).ec2conn

			if len(remove) > 0 {
				// Revoke the old rules
				revoke := ec2conn.RevokeSecurityGroup
				if ruleset == "egress" {
					revoke = ec2conn.RevokeSecurityGroupEgress
				}
				if _, err := revoke(group, remove); err != nil {
					return fmt.Errorf(
						"Error revoking security group %s rules: %s",
						ruleset, err)
				}
			}

			if len(add) > 0 {
				// Authorize the new rules
				authorize := ec2conn.AuthorizeSecurityGroup
				if ruleset == "egress" {
					authorize = ec2conn.AuthorizeSecurityGroupEgress
				}
				if _, err := authorize(group, add); err != nil {
					return fmt.Errorf(
						"Error authorizing security group %s rules: %s",
						ruleset, err)
				}
			}
		}
	}

	return nil
}

// SGStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
// a security group.
func SGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		sgs := []ec2.SecurityGroup{ec2.SecurityGroup{Id: id}}
		resp, err := conn.SecurityGroups(sgs, nil)
		if err != nil {
			if ec2err, ok := err.(*ec2.Error); ok {
				if ec2err.Code == "InvalidSecurityGroupID.NotFound" ||
					ec2err.Code == "InvalidGroup.NotFound" {
					resp = nil
					err = nil
				}
			}

			if err != nil {
				log.Printf("Error on SGStateRefresh: %s", err)
				return nil, "", err
			}
		}

		if resp == nil {
			return nil, "", nil
		}

		group := &resp.Groups[0]
		return group, "exists", nil
	}
}
<commit_msg>providers/aws: egress should be computed<commit_after>package aws

import (
	"bytes"
	"fmt"
	"log"
	"sort"
	"time"

	"github.com/hashicorp/terraform/helper/hashcode"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/mitchellh/goamz/ec2"
)

func resourceAwsSecurityGroup() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsSecurityGroupCreate,
		Read:   resourceAwsSecurityGroupRead,
		Update: resourceAwsSecurityGroupUpdate,
		Delete: resourceAwsSecurityGroupDelete,

		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

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

			"vpc_id": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
				Computed: true,
			},

			"ingress": &schema.Schema{
				Type:     schema.TypeSet,
				Optional: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"from_port": &schema.Schema{
							Type:     schema.TypeInt,
							Required: true,
						},

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

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

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

						"security_groups": &schema.Schema{
							Type:     schema.TypeSet,
							Optional: true,
							Elem:     &schema.Schema{Type: schema.TypeString},
							Set: func(v interface{}) int {
								return hashcode.String(v.(string))
							},
						},

						"self": &schema.Schema{
							Type:     schema.TypeBool,
							Optional: true,
							Default:  false,
						},
					},
				},
				Set: resourceAwsSecurityGroupRuleHash,
			},

			"egress": &schema.Schema{
				Type:     schema.TypeSet,
				Optional: true,
				Computed: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"from_port": &schema.Schema{
							Type:     schema.TypeInt,
							Required: true,
						},

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

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

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

						"security_groups": &schema.Schema{
							Type:     schema.TypeSet,
							Optional: true,
							Elem:     &schema.Schema{Type: schema.TypeString},
							Set: func(v interface{}) int {
								return hashcode.String(v.(string))
							},
						},

						"self": &schema.Schema{
							Type:     schema.TypeBool,
							Optional: true,
							Default:  false,
						},
					},
				},
				Set: resourceAwsSecurityGroupRuleHash,
			},

			"owner_id": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"tags": tagsSchema(),
		},
	}
}

func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error {
	ec2conn := meta.(*AWSClient).ec2conn

	securityGroupOpts := ec2.SecurityGroup{
		Name: d.Get("name").(string),
	}

	if v := d.Get("vpc_id"); v != nil {
		securityGroupOpts.VpcId = v.(string)
	}

	if v := d.Get("description"); v != nil {
		securityGroupOpts.Description = v.(string)
	}

	log.Printf(
		"[DEBUG] Security Group create configuration: %#v", securityGroupOpts)
	createResp, err := ec2conn.CreateSecurityGroup(securityGroupOpts)
	if err != nil {
		return fmt.Errorf("Error creating Security Group: %s", err)
	}

	d.SetId(createResp.Id)

	log.Printf("[INFO] Security Group ID: %s", d.Id())

	// Wait for the security group to truly exist
	log.Printf(
		"[DEBUG] Waiting for Security Group (%s) to exist",
		d.Id())
	stateConf := &resource.StateChangeConf{
		Pending: []string{""},
		Target:  "exists",
		Refresh: SGStateRefreshFunc(ec2conn, d.Id()),
		Timeout: 1 * time.Minute,
	}
	if _, err := stateConf.WaitForState(); err != nil {
		return fmt.Errorf(
			"Error waiting for Security Group (%s) to become available: %s",
			d.Id(), err)
	}

	return resourceAwsSecurityGroupUpdate(d, meta)
}

func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) error {
	ec2conn := meta.(*AWSClient).ec2conn

	sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())()
	if err != nil {
		return err
	}
	if sgRaw == nil {
		d.SetId("")
		return nil
	}

	sg := sgRaw.(*ec2.SecurityGroupInfo)

	ingressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPerms)
	egressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPermsEgress)

	d.Set("description", sg.Description)
	d.Set("name", sg.Name)
	d.Set("vpc_id", sg.VpcId)
	d.Set("owner_id", sg.OwnerId)
	d.Set("ingress", ingressRules)
	d.Set("egress", egressRules)
	d.Set("tags", tagsToMap(sg.Tags))

	return nil
}

func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error {
	ec2conn := meta.(*AWSClient).ec2conn

	sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())()
	if err != nil {
		return err
	}
	if sgRaw == nil {
		d.SetId("")
		return nil
	}
	group := sgRaw.(*ec2.SecurityGroupInfo).SecurityGroup

	err = resourceAwsSecurityGroupUpdateRules(d, "ingress", meta, group)
	if err != nil {
		return err
	}

	if d.Get("vpc_id") != nil {
		err = resourceAwsSecurityGroupUpdateRules(d, "egress", meta, group)
		if err != nil {
			return err
		}
	}

	if err := setTags(ec2conn, d); err != nil {
		return err
	}

	d.SetPartial("tags")

	return resourceAwsSecurityGroupRead(d, meta)
}

func resourceAwsSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error {
	ec2conn := meta.(*AWSClient).ec2conn

	log.Printf("[DEBUG] Security Group destroy: %v", d.Id())

	return resource.Retry(5*time.Minute, func() error {
		_, err := ec2conn.DeleteSecurityGroup(ec2.SecurityGroup{Id: d.Id()})
		if err != nil {
			ec2err, ok := err.(*ec2.Error)
			if !ok {
				return err
			}

			switch ec2err.Code {
			case "InvalidGroup.NotFound":
				return nil
			case "DependencyViolation":
				// If it is a dependency violation, we want to retry
				return err
			default:
				// Any other error, we want to quit the retry loop immediately
				return resource.RetryError{err}
			}
		}

		return nil
	})
}

func resourceAwsSecurityGroupRuleHash(v interface{}) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})
	buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int)))
	buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int)))
	buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string)))

	// 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 v, ok := m["cidr_blocks"]; ok {
		vs := v.([]interface{})
		s := make([]string, len(vs))
		for i, raw := range vs {
			s[i] = raw.(string)
		}
		sort.Strings(s)

		for _, v := range s {
			buf.WriteString(fmt.Sprintf("%s-", v))
		}
	}
	if v, ok := m["security_groups"]; ok {
		vs := v.(*schema.Set).List()
		s := make([]string, len(vs))
		for i, raw := range vs {
			s[i] = raw.(string)
		}
		sort.Strings(s)

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

	return hashcode.String(buf.String())
}

func resourceAwsSecurityGroupIPPermGather(d *schema.ResourceData, permissions []ec2.IPPerm) []map[string]interface{} {
	ruleMap := make(map[string]map[string]interface{})
	for _, perm := range permissions {
		k := fmt.Sprintf("%s-%d-%d", perm.Protocol, perm.FromPort, perm.ToPort)
		m, ok := ruleMap[k]
		if !ok {
			m = make(map[string]interface{})
			ruleMap[k] = m
		}

		m["from_port"] = perm.FromPort
		m["to_port"] = perm.ToPort
		m["protocol"] = perm.Protocol

		if len(perm.SourceIPs) > 0 {
			raw, ok := m["cidr_blocks"]
			if !ok {
				raw = make([]string, 0, len(perm.SourceIPs))
			}
			list := raw.([]string)

			list = append(list, perm.SourceIPs...)
			m["cidr_blocks"] = list
		}

		var groups []string
		if len(perm.SourceGroups) > 0 {
			groups = flattenSecurityGroups(perm.SourceGroups)
		}
		for i, id := range groups {
			if id == d.Id() {
				groups[i], groups = groups[len(groups)-1], groups[:len(groups)-1]
				m["self"] = true
			}
		}

		if len(groups) > 0 {
			raw, ok := m["security_groups"]
			if !ok {
				raw = make([]string, 0, len(groups))
			}
			list := raw.([]string)

			list = append(list, groups...)
			m["security_groups"] = list
		}
	}
	rules := make([]map[string]interface{}, 0, len(ruleMap))
	for _, m := range ruleMap {
		rules = append(rules, m)
	}

	return rules
}

func resourceAwsSecurityGroupUpdateRules(
	d *schema.ResourceData, ruleset string,
	meta interface{}, group ec2.SecurityGroup) error {
	if d.HasChange(ruleset) {
		o, n := d.GetChange(ruleset)
		if o == nil {
			o = new(schema.Set)
		}
		if n == nil {
			n = new(schema.Set)
		}

		os := o.(*schema.Set)
		ns := n.(*schema.Set)

		remove := expandIPPerms(d.Id(), os.Difference(ns).List())
		add := expandIPPerms(d.Id(), ns.Difference(os).List())

		// TODO: We need to handle partial state better in the in-between
		// in this update.

		// TODO: It'd be nicer to authorize before removing, but then we have
		// to deal with complicated unrolling to get individual CIDR blocks
		// to avoid authorizing already authorized sources. Removing before
		// adding is easier here, and Terraform should be fast enough to
		// not have service issues.

		if len(remove) > 0 || len(add) > 0 {
			ec2conn := meta.(*AWSClient).ec2conn

			if len(remove) > 0 {
				// Revoke the old rules
				revoke := ec2conn.RevokeSecurityGroup
				if ruleset == "egress" {
					revoke = ec2conn.RevokeSecurityGroupEgress
				}

				log.Printf("[DEBUG] Revoking security group %s %s rule: %#v",
					group, ruleset, remove)
				if _, err := revoke(group, remove); err != nil {
					return fmt.Errorf(
						"Error revoking security group %s rules: %s",
						ruleset, err)
				}
			}

			if len(add) > 0 {
				// Authorize the new rules
				authorize := ec2conn.AuthorizeSecurityGroup
				if ruleset == "egress" {
					authorize = ec2conn.AuthorizeSecurityGroupEgress
				}

				log.Printf("[DEBUG] Authorizing security group %s %s rule: %#v",
					group, ruleset, add)
				if _, err := authorize(group, add); err != nil {
					return fmt.Errorf(
						"Error authorizing security group %s rules: %s",
						ruleset, err)
				}
			}
		}
	}

	return nil
}

// SGStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
// a security group.
func SGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		sgs := []ec2.SecurityGroup{ec2.SecurityGroup{Id: id}}
		resp, err := conn.SecurityGroups(sgs, nil)
		if err != nil {
			if ec2err, ok := err.(*ec2.Error); ok {
				if ec2err.Code == "InvalidSecurityGroupID.NotFound" ||
					ec2err.Code == "InvalidGroup.NotFound" {
					resp = nil
					err = nil
				}
			}

			if err != nil {
				log.Printf("Error on SGStateRefresh: %s", err)
				return nil, "", err
			}
		}

		if resp == nil {
			return nil, "", nil
		}

		group := &resp.Groups[0]
		return group, "exists", nil
	}
}
<|endoftext|>


<commit_before>package fblib

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"time"

	"github.com/davecgh/go-spew/spew"

	"github.com/novatrixtech/go-fbmessenger/fbmodelsend"
)

var logLevelDebug = false

//ErrInvalidCallToFacebook is specific error when Facebook Messenger returns error after being called
var ErrInvalidCallToFacebook = errors.New("")

//MessageTypeResponse is in response to a received message.
const MessageTypeResponse = 1

//MessageTypeUpdate is being sent proactively and is not in response to a received message.
const MessageTypeUpdate = 2

//MessageTypeMessageTag is non-promotional and is being sent outside the 24-hour standard messaging window with a message tag.
const MessageTypeMessageTag = 3

//defineMessageType returns the Message Type description defined by Messenger
func defineMessageType(msgType int) (msgTypeDescription string) {
	switch msgType {
	case 2:
		msgTypeDescription = "UPDATE"
	case 3:
		msgTypeDescription = "MESSAGE_TAG"
	default:
		msgTypeDescription = "RESPONSE"
	}
	return
}

/*
SendTextMessage - Send text message to a recipient on Facebook Messenger
*/
func SendTextMessage(text string, recipient string, accessToken string, msgType int) (err error) {
	err = nil
	letter := new(fbmodelsend.Letter)
	letter.Message.Text = text
	letter.Recipient.ID = recipient
	letter.MessageType = defineMessageType(msgType)
	err = sendMessage(letter, recipient, accessToken)
	if err != nil {
		//fmt.Print("[fblib][sendTextMessage] Error during the call to Facebook to send the text message: " + err.Error())
		return
	}
	return
}

//SendPersonalFinanceUpdateMessage sends a Finance Update information to recipient
func SendPersonalFinanceUpdateMessage(text string, recipient string, accessToken string) (err error) {
	err = nil
	letter := new(fbmodelsend.Letter)
	letter.Message.Text = text
	letter.Tag = "PERSONAL_FINANCE_UPDATE"
	letter.Recipient.ID = recipient
	letter.MessageType = defineMessageType(3)
	err = sendMessage(letter, recipient, accessToken)
	if err != nil {
		//fmt.Print("[fblib][sendTextMessage] Error during the call to Facebook to send the text message: " + err.Error())
		return
	}
	return
}

/*
SendImageMessage - Sends image message to a recipient on Facebook Messenger
*/
func SendImageMessage(url string, recipient string, accessToken string, msgType int) (err error) {
	err = nil
	message := new(fbmodelsend.Letter)
	message.MessageType = defineMessageType(msgType)

	attch := new(fbmodelsend.Attachment)
	attch.AttachmentType = "image"
	attch.Payload.URL = url
	message.Message.Attachment = attch

	message.Recipient.ID = recipient
	err = sendMessage(message, recipient, accessToken)
	if err != nil {
		fmt.Print("[fblib][sendImageMessage] Error during the call to Facebook to send the image message: " + err.Error())
		return
	}
	return
}

/*
SendAudioMessage - Sends audio message to a recipient on Facebook Messenger
*/
func SendAudioMessage(url string, recipient string, accessToken string, msgType int) (err error) {
	err = nil
	message := new(fbmodelsend.Letter)
	message.MessageType = defineMessageType(msgType)
	attch := new(fbmodelsend.Attachment)
	attch.AttachmentType = "audio"
	attch.Payload.URL = url
	message.Message.Attachment = attch

	message.Recipient.ID = recipient
	err = sendMessage(message, recipient, accessToken)
	if err != nil {
		//fmt.Print("[fblib][sendImageMessage] Error during the call to Facebook to send the audio message: " + err.Error())
		return
	}
	return
}

/*
SendTypingMessage - Sends typing message to user
*/
func SendTypingMessage(onoff bool, recipient string, accessToken string, msgType int) (err error) {
	err = nil
	senderAction := new(fbmodelsend.SenderAction)
	senderAction.MessageType = defineMessageType(msgType)
	senderAction.Recipient.ID = recipient
	if onoff {
		senderAction.SenderActionState = "typing_on"
	} else {
		senderAction.SenderActionState = "typing_off"
	}
	err = sendMessage(senderAction, recipient, accessToken)
	if err != nil {
		//fmt.Print("[fblib][sendImageMessage] Error during the call to Facebook to send the typing message: " + err.Error())
		return
	}
	return
}

/*
SendGenericTemplateMessage - Sends a generic rich message to Facebook user.
It can include text, buttons, URLs Butttons, lists to reply
*/
func SendGenericTemplateMessage(template []*fbmodelsend.TemplateElement, recipient string, accessToken string, msgType int) (err error) {
	err = nil
	msg := new(fbmodelsend.Letter)
	msg.Recipient.ID = recipient
	msg.MessageType = defineMessageType(msgType)
	attch := new(fbmodelsend.Attachment)
	attch.AttachmentType = "template"
	attch.Payload.TemplateType = "generic"
	attch.Payload.Elements = template

	msg.Message.Attachment = attch

	err = sendMessage(msg, recipient, accessToken)
	if err != nil {
		//fmt.Print("[fblib][SendGenericTemplateMessage] Error during the call to Facebook to send the text message: " + err.Error())
		return
	}
	return
}

/*
SendButtonMessage - Sends a generic rich message to Facebook user.
It can include text, buttons, URLs Butttons, lists to reply
*/
func SendButtonMessage(template []*fbmodelsend.Button, text string, recipient string, accessToken string, msgType int) (err error) {
	err = nil
	msg := new(fbmodelsend.Letter)
	msg.Recipient.ID = recipient
	msg.MessageType = defineMessageType(msgType)

	attch := new(fbmodelsend.Attachment)
	attch.AttachmentType = "template"
	attch.Payload.TemplateType = "button"
	attch.Payload.Text = text
	attch.Payload.Buttons = template

	msg.Message.Attachment = attch

	err = sendMessage(msg, recipient, accessToken)
	if err != nil {
		//fmt.Print("[fblib][sendTextMessage] Error during the call to Facebook to send the text message: " + err.Error())
		return
	}
	return
}

/*
SendURLButtonMessage - Sends a message with a button that redirects the user to an external web page.
*/
func SendURLButtonMessage(text string, buttonTitle string, URL string, recipient string, accessToken string, msgType int) (err error) {
	err = nil
	msgElement := new(fbmodelsend.TemplateElement)
	msgElement.Title = text

	opt1 := new(fbmodelsend.Button)
	opt1.ButtonType = "web_url"
	opt1.Title = buttonTitle
	opt1.URL = URL

	buttons := []*fbmodelsend.Button{opt1}

	msgElement.Buttons = buttons
	elements := []*fbmodelsend.TemplateElement{msgElement}

	err = SendGenericTemplateMessage(elements, recipient, accessToken, msgType)
	if err != nil {
		//fmt.Print("[fblib][SendURLButtonMessage] Error during the call to Facebook to send the text message: " + err.Error())
		return
	}
	return
}

/*
SendShareMessage sends the message along with Share Button
*/
func SendShareMessage(title string, subtitle string, recipient string, accessToken string, msgType int) (err error) {
	err = nil
	msgElement := new(fbmodelsend.TemplateElement)
	msgElement.Title = title
	msgElement.Subtitle = subtitle

	opt1 := new(fbmodelsend.Button)
	opt1.ButtonType = "element_share"
	buttons := []*fbmodelsend.Button{opt1}

	msgElement.Buttons = buttons
	elements := []*fbmodelsend.TemplateElement{msgElement}

	err = SendGenericTemplateMessage(elements, recipient, accessToken, msgType)
	if err != nil {
		//fmt.Print("[fblib][SendShareMessage] Error during the call to Facebook to send the text message: " + err.Error())
		return
	}
	return

}

/*
SendShareContent share rich content media and url button
*/
func SendShareContent(titleToSender string, subtitleToSender string, imageURLToSender string, titleToRecipient string, subtitleToRecipient string, buttonTitleToRecipient string, imageURLToRecipient string, destinationURL string, recipient string, accessToken string, msgType int) (err error) {

	btnRecipient := new(fbmodelsend.Button)
	btnRecipient.ButtonType = "web_url"
	btnRecipient.Title = buttonTitleToRecipient
	btnRecipient.URL = destinationURL
	arrBtnRecipient := []*fbmodelsend.Button{btnRecipient}

	elementRecipient := new(fbmodelsend.TemplateElement)
	elementRecipient.Title = titleToRecipient
	elementRecipient.Subtitle = subtitleToRecipient
	elementRecipient.ImageURL = imageURLToRecipient
	elementRecipient.Buttons = arrBtnRecipient
	arrElementRecipient := []*fbmodelsend.TemplateElement{elementRecipient}

	btnSender := new(fbmodelsend.ButtonSharedContent)
	btnSender.ButtonType = "element_share"
	btnSender.ShareContents.Attachment.AttachmentType = "template"
	btnSender.ShareContents.Attachment.Payload.TemplateType = "generic"
	btnSender.ShareContents.Attachment.Payload.Elements = arrElementRecipient
	arrBtnSender := []*fbmodelsend.ButtonSharedContent{btnSender}

	elementSender := new(fbmodelsend.TemplateElementShareContent)
	elementSender.Title = titleToSender
	elementSender.Subtitle = subtitleToSender
	elementSender.ImageURL = imageURLToSender
	elementSender.Buttons = arrBtnSender
	arrElementSender := []*fbmodelsend.TemplateElementShareContent{elementSender}

	attch := new(fbmodelsend.SharedAttachment)
	attch.AttachmentType = "template"
	attch.Payload.TemplateType = "generic"
	attch.Payload.Elements = arrElementSender

	si := new(fbmodelsend.SharedInvite)
	si.MessageType = defineMessageType(msgType)
	si.Recipient.ID = recipient
	si.Message.Attachment = attch

	err = sendMessage(si, recipient, accessToken)
	if err != nil {
		//fmt.Print("[fblib][SendGenericTemplateMessage] Error during the call to Facebook to send the text message: " + err.Error())
		return
	}
	return
}

/*
SendQuickReply sends small messages in order to get small and quick answers from the users
*/
func SendQuickReply(text string, options []*fbmodelsend.QuickReply, recipient string, accessToken string, msgType int) (err error) {
	err = nil
	msg := new(fbmodelsend.Letter)
	msg.MessageType = defineMessageType(msgType)
	msg.Recipient.ID = recipient
	msg.Message.Text = text
	msg.Message.QuickReplies = options
	//log.Printf("[SendQuickReply] Enviado: [%s]\n", text)
	err = sendMessage(msg, recipient, accessToken)
	if err != nil {
		//log.Print("[fblib][SendQuickReply] Error during the call to Facebook to send the text message: " + err.Error())
		return
	}
	return
}

/*
SendAskUserLocation sends small message asking the users their location
*/
func SendAskUserLocation(text string, recipient string, accessToken string, msgType int) (err error) {
	err = nil
	qr := new(fbmodelsend.QuickReply)
	qr.ContentType = "location"

	arrayQr := []*fbmodelsend.QuickReply{qr}

	err = SendQuickReply(text, arrayQr, recipient, accessToken, msgType)
	if err != nil {
		//log.Print("[fblib][SendAskUserLocation] Error during the call to Facebook to send the text message: " + err.Error())
		return
	}
	return
}

/*
Send Message - Sends a generic message to Facebook Messenger
*/
func sendMessage(message interface{}, recipient string, accessToken string) error {

	if logLevelDebug {
		scs := spew.ConfigState{Indent: "\t"}
		scs.Dump(message)
		return nil
	}

	var url string
	if strings.Contains(accessToken, "http") {
		url = accessToken
	} else {
		url = "https://graph.facebook.com/v6.0/me/messages?access_token=" + accessToken
	}

	data, err := json.Marshal(message)
	if err != nil {
		//fmt.Print("[fblib][sendMessage] Error to convert message object: " + err.Error())
		return err
	}

	reqFb, _ := http.NewRequest("POST", url, bytes.NewBuffer(data))
	reqFb.Header.Set("Content-Type", "application/json")
	reqFb.Header.Set("Connection", "close")
	reqFb.Close = true

	client := &http.Client{
		Timeout: time.Second * 30,
	}

	//fmt.Println("[sendMessage] Replying at: " + url + " the message " + string(data))

	respFb, err := client.Do(reqFb)
	if err != nil {
		//fmt.Print("[fblib][sendMessage] Error during the call to Facebook to send the message: " + err.Error())
		return err
	}
	defer respFb.Body.Close()

	if respFb.StatusCode < 200 || respFb.StatusCode >= 300 {
		bodyFromFb, _ := ioutil.ReadAll(respFb.Body)
		status := string(bodyFromFb)
		fmt.Printf("[fblib][sendMessage] Response status code: [%d]\n", respFb.StatusCode)
		fmt.Println("[fblib][sendMessage] Response status: ", respFb.Status)
		fmt.Println("[fblib][sendMessage] Response Body from Facebook: ", status)
		fmt.Printf("[fblib][sendMessage] Facebook URL Called: [%s]\n", url)
		fmt.Printf("[fblib][sendMessage] Object sent to Facebook: [%s]\n", string(data))
		strErr := fmt.Sprintf("[fblib][sendMessage] Response status code: [%d]\nResponse status: [%s]\nResponse Body from Facebook: [%s]\nFacebook URL Called: [%s]\nObject sent to Facebook: [%s]\n",
			respFb.StatusCode,
			respFb.Status,
			status,
			url,
			string(data),
		)
		ErrInvalidCallToFacebook = errors.New(strErr)
		return ErrInvalidCallToFacebook
	}

	return nil
}
<commit_msg>remove outdated api<commit_after>package fblib

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"time"

	"github.com/davecgh/go-spew/spew"

	"github.com/novatrixtech/go-fbmessenger/fbmodelsend"
)

var logLevelDebug = false

//ErrInvalidCallToFacebook is specific error when Facebook Messenger returns error after being called
var ErrInvalidCallToFacebook = errors.New("")

//MessageTypeResponse is in response to a received message.
const MessageTypeResponse = 1

//MessageTypeUpdate is being sent proactively and is not in response to a received message.
const MessageTypeUpdate = 2

//MessageTypeMessageTag is non-promotional and is being sent outside the 24-hour standard messaging window with a message tag.
const MessageTypeMessageTag = 3

//defineMessageType returns the Message Type description defined by Messenger
func defineMessageType(msgType int) (msgTypeDescription string) {
	switch msgType {
	case 2:
		msgTypeDescription = "UPDATE"
	case 3:
		msgTypeDescription = "MESSAGE_TAG"
	default:
		msgTypeDescription = "RESPONSE"
	}
	return
}

/*
SendTextMessage - Send text message to a recipient on Facebook Messenger
*/
func SendTextMessage(text string, recipient string, accessToken string, msgType int) (err error) {
	err = nil
	letter := new(fbmodelsend.Letter)
	letter.Message.Text = text
	letter.Recipient.ID = recipient
	letter.MessageType = defineMessageType(msgType)
	err = sendMessage(letter, recipient, accessToken)
	if err != nil {
		//fmt.Print("[fblib][sendTextMessage] Error during the call to Facebook to send the text message: " + err.Error())
		return
	}
	return
}

//SendPersonalFinanceUpdateMessage sends a Finance Update information to recipient
func SendPersonalFinanceUpdateMessage(text string, recipient string, accessToken string) (err error) {
	err = nil
	letter := new(fbmodelsend.Letter)
	letter.Message.Text = text
	letter.Tag = "PERSONAL_FINANCE_UPDATE"
	letter.Recipient.ID = recipient
	letter.MessageType = defineMessageType(3)
	err = sendMessage(letter, recipient, accessToken)
	if err != nil {
		//fmt.Print("[fblib][sendTextMessage] Error during the call to Facebook to send the text message: " + err.Error())
		return
	}
	return
}

/*
SendImageMessage - Sends image message to a recipient on Facebook Messenger
*/
func SendImageMessage(url string, recipient string, accessToken string, msgType int) (err error) {
	err = nil
	message := new(fbmodelsend.Letter)
	message.MessageType = defineMessageType(msgType)

	attch := new(fbmodelsend.Attachment)
	attch.AttachmentType = "image"
	attch.Payload.URL = url
	message.Message.Attachment = attch

	message.Recipient.ID = recipient
	err = sendMessage(message, recipient, accessToken)
	if err != nil {
		fmt.Print("[fblib][sendImageMessage] Error during the call to Facebook to send the image message: " + err.Error())
		return
	}
	return
}

/*
SendAudioMessage - Sends audio message to a recipient on Facebook Messenger
*/
func SendAudioMessage(url string, recipient string, accessToken string, msgType int) (err error) {
	err = nil
	message := new(fbmodelsend.Letter)
	message.MessageType = defineMessageType(msgType)
	attch := new(fbmodelsend.Attachment)
	attch.AttachmentType = "audio"
	attch.Payload.URL = url
	message.Message.Attachment = attch

	message.Recipient.ID = recipient
	err = sendMessage(message, recipient, accessToken)
	if err != nil {
		//fmt.Print("[fblib][sendImageMessage] Error during the call to Facebook to send the audio message: " + err.Error())
		return
	}
	return
}

/*
SendTypingMessage - Sends typing message to user
*/
func SendTypingMessage(onoff bool, recipient string, accessToken string, msgType int) (err error) {
	err = nil
	senderAction := new(fbmodelsend.SenderAction)
	senderAction.MessageType = defineMessageType(msgType)
	senderAction.Recipient.ID = recipient
	if onoff {
		senderAction.SenderActionState = "typing_on"
	} else {
		senderAction.SenderActionState = "typing_off"
	}
	err = sendMessage(senderAction, recipient, accessToken)
	if err != nil {
		//fmt.Print("[fblib][sendImageMessage] Error during the call to Facebook to send the typing message: " + err.Error())
		return
	}
	return
}

/*
SendGenericTemplateMessage - Sends a generic rich message to Facebook user.
It can include text, buttons, URLs Butttons, lists to reply
*/
func SendGenericTemplateMessage(template []*fbmodelsend.TemplateElement, recipient string, accessToken string, msgType int) (err error) {
	err = nil
	msg := new(fbmodelsend.Letter)
	msg.Recipient.ID = recipient
	msg.MessageType = defineMessageType(msgType)
	attch := new(fbmodelsend.Attachment)
	attch.AttachmentType = "template"
	attch.Payload.TemplateType = "generic"
	attch.Payload.Elements = template

	msg.Message.Attachment = attch

	err = sendMessage(msg, recipient, accessToken)
	if err != nil {
		//fmt.Print("[fblib][SendGenericTemplateMessage] Error during the call to Facebook to send the text message: " + err.Error())
		return
	}
	return
}

/*
SendButtonMessage - Sends a generic rich message to Facebook user.
It can include text, buttons, URLs Butttons, lists to reply
*/
func SendButtonMessage(template []*fbmodelsend.Button, text string, recipient string, accessToken string, msgType int) (err error) {
	err = nil
	msg := new(fbmodelsend.Letter)
	msg.Recipient.ID = recipient
	msg.MessageType = defineMessageType(msgType)

	attch := new(fbmodelsend.Attachment)
	attch.AttachmentType = "template"
	attch.Payload.TemplateType = "button"
	attch.Payload.Text = text
	attch.Payload.Buttons = template

	msg.Message.Attachment = attch

	err = sendMessage(msg, recipient, accessToken)
	if err != nil {
		//fmt.Print("[fblib][sendTextMessage] Error during the call to Facebook to send the text message: " + err.Error())
		return
	}
	return
}

/*
SendURLButtonMessage - Sends a message with a button that redirects the user to an external web page.
*/
func SendURLButtonMessage(text string, buttonTitle string, URL string, recipient string, accessToken string, msgType int) (err error) {
	err = nil
	msgElement := new(fbmodelsend.TemplateElement)
	msgElement.Title = text

	opt1 := new(fbmodelsend.Button)
	opt1.ButtonType = "web_url"
	opt1.Title = buttonTitle
	opt1.URL = URL

	buttons := []*fbmodelsend.Button{opt1}

	msgElement.Buttons = buttons
	elements := []*fbmodelsend.TemplateElement{msgElement}

	err = SendGenericTemplateMessage(elements, recipient, accessToken, msgType)
	if err != nil {
		//fmt.Print("[fblib][SendURLButtonMessage] Error during the call to Facebook to send the text message: " + err.Error())
		return
	}
	return
}

/*
SendQuickReply sends small messages in order to get small and quick answers from the users
*/
func SendQuickReply(text string, options []*fbmodelsend.QuickReply, recipient string, accessToken string, msgType int) (err error) {
	err = nil
	msg := new(fbmodelsend.Letter)
	msg.MessageType = defineMessageType(msgType)
	msg.Recipient.ID = recipient
	msg.Message.Text = text
	msg.Message.QuickReplies = options
	//log.Printf("[SendQuickReply] Enviado: [%s]\n", text)
	err = sendMessage(msg, recipient, accessToken)
	if err != nil {
		//log.Print("[fblib][SendQuickReply] Error during the call to Facebook to send the text message: " + err.Error())
		return
	}
	return
}

/*
SendAskUserLocation sends small message asking the users their location
*/
func SendAskUserLocation(text string, recipient string, accessToken string, msgType int) (err error) {
	err = nil
	qr := new(fbmodelsend.QuickReply)
	qr.ContentType = "location"

	arrayQr := []*fbmodelsend.QuickReply{qr}

	err = SendQuickReply(text, arrayQr, recipient, accessToken, msgType)
	if err != nil {
		//log.Print("[fblib][SendAskUserLocation] Error during the call to Facebook to send the text message: " + err.Error())
		return
	}
	return
}

/*
Send Message - Sends a generic message to Facebook Messenger
*/
func sendMessage(message interface{}, recipient string, accessToken string) error {

	if logLevelDebug {
		scs := spew.ConfigState{Indent: "\t"}
		scs.Dump(message)
		return nil
	}

	var url string
	if strings.Contains(accessToken, "http") {
		url = accessToken
	} else {
		url = "https://graph.facebook.com/v6.0/me/messages?access_token=" + accessToken
	}

	data, err := json.Marshal(message)
	if err != nil {
		//fmt.Print("[fblib][sendMessage] Error to convert message object: " + err.Error())
		return err
	}

	reqFb, _ := http.NewRequest("POST", url, bytes.NewBuffer(data))
	reqFb.Header.Set("Content-Type", "application/json")
	reqFb.Header.Set("Connection", "close")
	reqFb.Close = true

	client := &http.Client{
		Timeout: time.Second * 30,
	}

	//fmt.Println("[sendMessage] Replying at: " + url + " the message " + string(data))

	respFb, err := client.Do(reqFb)
	if err != nil {
		//fmt.Print("[fblib][sendMessage] Error during the call to Facebook to send the message: " + err.Error())
		return err
	}
	defer respFb.Body.Close()

	if respFb.StatusCode < 200 || respFb.StatusCode >= 300 {
		bodyFromFb, _ := ioutil.ReadAll(respFb.Body)
		status := string(bodyFromFb)
		fmt.Printf("[fblib][sendMessage] Response status code: [%d]\n", respFb.StatusCode)
		fmt.Println("[fblib][sendMessage] Response status: ", respFb.Status)
		fmt.Println("[fblib][sendMessage] Response Body from Facebook: ", status)
		fmt.Printf("[fblib][sendMessage] Facebook URL Called: [%s]\n", url)
		fmt.Printf("[fblib][sendMessage] Object sent to Facebook: [%s]\n", string(data))
		strErr := fmt.Sprintf("[fblib][sendMessage] Response status code: [%d]\nResponse status: [%s]\nResponse Body from Facebook: [%s]\nFacebook URL Called: [%s]\nObject sent to Facebook: [%s]\n",
			respFb.StatusCode,
			respFb.Status,
			status,
			url,
			string(data),
		)
		ErrInvalidCallToFacebook = errors.New(strErr)
		return ErrInvalidCallToFacebook
	}

	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2017 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 aws

import (
	"fmt"
	"net/url"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ec2"
	"k8s.io/klog"

	"k8s.io/api/core/v1"
)

// awsInstanceRegMatch represents Regex Match for AWS instance.
var awsInstanceRegMatch = regexp.MustCompile("^i-[^/]*$")

// InstanceID represents the ID of the instance in the AWS API, e.g. i-12345678
// The "traditional" format is "i-12345678"
// A new longer format is also being introduced: "i-12345678abcdef01"
// We should not assume anything about the length or format, though it seems
// reasonable to assume that instances will continue to start with "i-".
type InstanceID string

func (i InstanceID) awsString() *string {
	return aws.String(string(i))
}

// KubernetesInstanceID represents the id for an instance in the kubernetes API;
// the following form
//  * aws:///<zone>/<awsInstanceId>
//  * aws:////<awsInstanceId>
//  * <awsInstanceId>
type KubernetesInstanceID string

// MapToAWSInstanceID extracts the InstanceID from the KubernetesInstanceID
func (name KubernetesInstanceID) MapToAWSInstanceID() (InstanceID, error) {
	s := string(name)

	if !strings.HasPrefix(s, "aws://") {
		// Assume a bare aws volume id (vol-1234...)
		// Build a URL with an empty host (AZ)
		s = "aws://" + "/" + "/" + s
	}
	url, err := url.Parse(s)
	if err != nil {
		return "", fmt.Errorf("Invalid instance name (%s): %v", name, err)
	}
	if url.Scheme != "aws" {
		return "", fmt.Errorf("Invalid scheme for AWS instance (%s)", name)
	}

	awsID := ""
	tokens := strings.Split(strings.Trim(url.Path, "/"), "/")
	if len(tokens) == 1 {
		// instanceId
		awsID = tokens[0]
	} else if len(tokens) == 2 {
		// az/instanceId
		awsID = tokens[1]
	}

	// We sanity check the resulting volume; the two known formats are
	// i-12345678 and i-12345678abcdef01
	if awsID == "" || !awsInstanceRegMatch.MatchString(awsID) {
		return "", fmt.Errorf("Invalid format for AWS instance (%s)", name)
	}

	return InstanceID(awsID), nil
}

// mapToAWSInstanceID extracts the InstanceIDs from the Nodes, returning an error if a Node cannot be mapped
func mapToAWSInstanceIDs(nodes []*v1.Node) ([]InstanceID, error) {
	var instanceIDs []InstanceID
	for _, node := range nodes {
		if node.Spec.ProviderID == "" {
			return nil, fmt.Errorf("node %q did not have ProviderID set", node.Name)
		}
		instanceID, err := KubernetesInstanceID(node.Spec.ProviderID).MapToAWSInstanceID()
		if err != nil {
			return nil, fmt.Errorf("unable to parse ProviderID %q for node %q", node.Spec.ProviderID, node.Name)
		}
		instanceIDs = append(instanceIDs, instanceID)
	}

	return instanceIDs, nil
}

// mapToAWSInstanceIDsTolerant extracts the InstanceIDs from the Nodes, skipping Nodes that cannot be mapped
func mapToAWSInstanceIDsTolerant(nodes []*v1.Node) []InstanceID {
	var instanceIDs []InstanceID
	for _, node := range nodes {
		if node.Spec.ProviderID == "" {
			klog.Warningf("node %q did not have ProviderID set", node.Name)
			continue
		}
		instanceID, err := KubernetesInstanceID(node.Spec.ProviderID).MapToAWSInstanceID()
		if err != nil {
			klog.Warningf("unable to parse ProviderID %q for node %q", node.Spec.ProviderID, node.Name)
			continue
		}
		instanceIDs = append(instanceIDs, instanceID)
	}

	return instanceIDs
}

// Gets the full information about this instance from the EC2 API
func describeInstance(ec2Client EC2, instanceID InstanceID) (*ec2.Instance, error) {
	request := &ec2.DescribeInstancesInput{
		InstanceIds: []*string{instanceID.awsString()},
	}

	instances, err := ec2Client.DescribeInstances(request)
	if err != nil {
		return nil, err
	}
	if len(instances) == 0 {
		return nil, fmt.Errorf("no instances found for instance: %s", instanceID)
	}
	if len(instances) > 1 {
		return nil, fmt.Errorf("multiple instances found for instance: %s", instanceID)
	}
	return instances[0], nil
}

// instanceCache manages the cache of DescribeInstances
type instanceCache struct {
	// TODO: Get rid of this field, send all calls through the instanceCache
	cloud *Cloud

	mutex    sync.Mutex
	snapshot *allInstancesSnapshot
}

// Gets the full information about these instance from the EC2 API
func (c *instanceCache) describeAllInstancesUncached() (*allInstancesSnapshot, error) {
	now := time.Now()

	klog.V(4).Infof("EC2 DescribeInstances - fetching all instances")

	filters := []*ec2.Filter{}
	instances, err := c.cloud.describeInstances(filters)
	if err != nil {
		return nil, err
	}

	m := make(map[InstanceID]*ec2.Instance)
	for _, i := range instances {
		id := InstanceID(aws.StringValue(i.InstanceId))
		m[id] = i
	}

	snapshot := &allInstancesSnapshot{now, m}

	c.mutex.Lock()
	defer c.mutex.Unlock()

	if c.snapshot != nil && snapshot.olderThan(c.snapshot) {
		// If this happens a lot, we could run this function in a mutex and only return one result
		klog.Infof("Not caching concurrent AWS DescribeInstances results")
	} else {
		c.snapshot = snapshot
	}

	return snapshot, nil
}

// cacheCriteria holds criteria that must hold to use a cached snapshot
type cacheCriteria struct {
	// MaxAge indicates the maximum age of a cached snapshot we can accept.
	// If set to 0 (i.e. unset), cached values will not time out because of age.
	MaxAge time.Duration

	// HasInstances is a list of InstanceIDs that must be in a cached snapshot for it to be considered valid.
	// If an instance is not found in the cached snapshot, the snapshot be ignored and we will re-fetch.
	HasInstances []InstanceID
}

// describeAllInstancesCached returns all instances, using cached results if applicable
func (c *instanceCache) describeAllInstancesCached(criteria cacheCriteria) (*allInstancesSnapshot, error) {
	var err error
	snapshot := c.getSnapshot()
	if snapshot != nil && !snapshot.MeetsCriteria(criteria) {
		snapshot = nil
	}

	if snapshot == nil {
		snapshot, err = c.describeAllInstancesUncached()
		if err != nil {
			return nil, err
		}
	} else {
		klog.V(6).Infof("EC2 DescribeInstances - using cached results")
	}

	return snapshot, nil
}

// getSnapshot returns a snapshot if one exists
func (c *instanceCache) getSnapshot() *allInstancesSnapshot {
	c.mutex.Lock()
	defer c.mutex.Unlock()

	return c.snapshot
}

// olderThan is a simple helper to encapsulate timestamp comparison
func (s *allInstancesSnapshot) olderThan(other *allInstancesSnapshot) bool {
	// After() is technically broken by time changes until we have monotonic time
	return other.timestamp.After(s.timestamp)
}

// MeetsCriteria returns true if the snapshot meets the criteria in cacheCriteria
func (s *allInstancesSnapshot) MeetsCriteria(criteria cacheCriteria) bool {
	if criteria.MaxAge > 0 {
		// Sub() is technically broken by time changes until we have monotonic time
		now := time.Now()
		if now.Sub(s.timestamp) > criteria.MaxAge {
			klog.V(6).Infof("instanceCache snapshot cannot be used as is older than MaxAge=%s", criteria.MaxAge)
			return false
		}
	}

	if len(criteria.HasInstances) != 0 {
		for _, id := range criteria.HasInstances {
			if nil == s.instances[id] {
				klog.V(6).Infof("instanceCache snapshot cannot be used as does not contain instance %s", id)
				return false
			}
		}
	}

	return true
}

// allInstancesSnapshot holds the results from querying for all instances,
// along with the timestamp for cache-invalidation purposes
type allInstancesSnapshot struct {
	timestamp time.Time
	instances map[InstanceID]*ec2.Instance
}

// FindInstances returns the instances corresponding to the specified ids.  If an id is not found, it is ignored.
func (s *allInstancesSnapshot) FindInstances(ids []InstanceID) map[InstanceID]*ec2.Instance {
	m := make(map[InstanceID]*ec2.Instance)
	for _, id := range ids {
		instance := s.instances[id]
		if instance != nil {
			m[id] = instance
		}
	}
	return m
}
<commit_msg>fix bug that awsSDKGO expect nil instead empty slice for ec2.DescribeInstances<commit_after>/*
Copyright 2017 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 aws

import (
	"fmt"
	"net/url"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ec2"
	"k8s.io/klog"

	"k8s.io/api/core/v1"
)

// awsInstanceRegMatch represents Regex Match for AWS instance.
var awsInstanceRegMatch = regexp.MustCompile("^i-[^/]*$")

// InstanceID represents the ID of the instance in the AWS API, e.g. i-12345678
// The "traditional" format is "i-12345678"
// A new longer format is also being introduced: "i-12345678abcdef01"
// We should not assume anything about the length or format, though it seems
// reasonable to assume that instances will continue to start with "i-".
type InstanceID string

func (i InstanceID) awsString() *string {
	return aws.String(string(i))
}

// KubernetesInstanceID represents the id for an instance in the kubernetes API;
// the following form
//  * aws:///<zone>/<awsInstanceId>
//  * aws:////<awsInstanceId>
//  * <awsInstanceId>
type KubernetesInstanceID string

// MapToAWSInstanceID extracts the InstanceID from the KubernetesInstanceID
func (name KubernetesInstanceID) MapToAWSInstanceID() (InstanceID, error) {
	s := string(name)

	if !strings.HasPrefix(s, "aws://") {
		// Assume a bare aws volume id (vol-1234...)
		// Build a URL with an empty host (AZ)
		s = "aws://" + "/" + "/" + s
	}
	url, err := url.Parse(s)
	if err != nil {
		return "", fmt.Errorf("Invalid instance name (%s): %v", name, err)
	}
	if url.Scheme != "aws" {
		return "", fmt.Errorf("Invalid scheme for AWS instance (%s)", name)
	}

	awsID := ""
	tokens := strings.Split(strings.Trim(url.Path, "/"), "/")
	if len(tokens) == 1 {
		// instanceId
		awsID = tokens[0]
	} else if len(tokens) == 2 {
		// az/instanceId
		awsID = tokens[1]
	}

	// We sanity check the resulting volume; the two known formats are
	// i-12345678 and i-12345678abcdef01
	if awsID == "" || !awsInstanceRegMatch.MatchString(awsID) {
		return "", fmt.Errorf("Invalid format for AWS instance (%s)", name)
	}

	return InstanceID(awsID), nil
}

// mapToAWSInstanceID extracts the InstanceIDs from the Nodes, returning an error if a Node cannot be mapped
func mapToAWSInstanceIDs(nodes []*v1.Node) ([]InstanceID, error) {
	var instanceIDs []InstanceID
	for _, node := range nodes {
		if node.Spec.ProviderID == "" {
			return nil, fmt.Errorf("node %q did not have ProviderID set", node.Name)
		}
		instanceID, err := KubernetesInstanceID(node.Spec.ProviderID).MapToAWSInstanceID()
		if err != nil {
			return nil, fmt.Errorf("unable to parse ProviderID %q for node %q", node.Spec.ProviderID, node.Name)
		}
		instanceIDs = append(instanceIDs, instanceID)
	}

	return instanceIDs, nil
}

// mapToAWSInstanceIDsTolerant extracts the InstanceIDs from the Nodes, skipping Nodes that cannot be mapped
func mapToAWSInstanceIDsTolerant(nodes []*v1.Node) []InstanceID {
	var instanceIDs []InstanceID
	for _, node := range nodes {
		if node.Spec.ProviderID == "" {
			klog.Warningf("node %q did not have ProviderID set", node.Name)
			continue
		}
		instanceID, err := KubernetesInstanceID(node.Spec.ProviderID).MapToAWSInstanceID()
		if err != nil {
			klog.Warningf("unable to parse ProviderID %q for node %q", node.Spec.ProviderID, node.Name)
			continue
		}
		instanceIDs = append(instanceIDs, instanceID)
	}

	return instanceIDs
}

// Gets the full information about this instance from the EC2 API
func describeInstance(ec2Client EC2, instanceID InstanceID) (*ec2.Instance, error) {
	request := &ec2.DescribeInstancesInput{
		InstanceIds: []*string{instanceID.awsString()},
	}

	instances, err := ec2Client.DescribeInstances(request)
	if err != nil {
		return nil, err
	}
	if len(instances) == 0 {
		return nil, fmt.Errorf("no instances found for instance: %s", instanceID)
	}
	if len(instances) > 1 {
		return nil, fmt.Errorf("multiple instances found for instance: %s", instanceID)
	}
	return instances[0], nil
}

// instanceCache manages the cache of DescribeInstances
type instanceCache struct {
	// TODO: Get rid of this field, send all calls through the instanceCache
	cloud *Cloud

	mutex    sync.Mutex
	snapshot *allInstancesSnapshot
}

// Gets the full information about these instance from the EC2 API
func (c *instanceCache) describeAllInstancesUncached() (*allInstancesSnapshot, error) {
	now := time.Now()

	klog.V(4).Infof("EC2 DescribeInstances - fetching all instances")

	var filters []*ec2.Filter
	instances, err := c.cloud.describeInstances(filters)
	if err != nil {
		return nil, err
	}

	m := make(map[InstanceID]*ec2.Instance)
	for _, i := range instances {
		id := InstanceID(aws.StringValue(i.InstanceId))
		m[id] = i
	}

	snapshot := &allInstancesSnapshot{now, m}

	c.mutex.Lock()
	defer c.mutex.Unlock()

	if c.snapshot != nil && snapshot.olderThan(c.snapshot) {
		// If this happens a lot, we could run this function in a mutex and only return one result
		klog.Infof("Not caching concurrent AWS DescribeInstances results")
	} else {
		c.snapshot = snapshot
	}

	return snapshot, nil
}

// cacheCriteria holds criteria that must hold to use a cached snapshot
type cacheCriteria struct {
	// MaxAge indicates the maximum age of a cached snapshot we can accept.
	// If set to 0 (i.e. unset), cached values will not time out because of age.
	MaxAge time.Duration

	// HasInstances is a list of InstanceIDs that must be in a cached snapshot for it to be considered valid.
	// If an instance is not found in the cached snapshot, the snapshot be ignored and we will re-fetch.
	HasInstances []InstanceID
}

// describeAllInstancesCached returns all instances, using cached results if applicable
func (c *instanceCache) describeAllInstancesCached(criteria cacheCriteria) (*allInstancesSnapshot, error) {
	var err error
	snapshot := c.getSnapshot()
	if snapshot != nil && !snapshot.MeetsCriteria(criteria) {
		snapshot = nil
	}

	if snapshot == nil {
		snapshot, err = c.describeAllInstancesUncached()
		if err != nil {
			return nil, err
		}
	} else {
		klog.V(6).Infof("EC2 DescribeInstances - using cached results")
	}

	return snapshot, nil
}

// getSnapshot returns a snapshot if one exists
func (c *instanceCache) getSnapshot() *allInstancesSnapshot {
	c.mutex.Lock()
	defer c.mutex.Unlock()

	return c.snapshot
}

// olderThan is a simple helper to encapsulate timestamp comparison
func (s *allInstancesSnapshot) olderThan(other *allInstancesSnapshot) bool {
	// After() is technically broken by time changes until we have monotonic time
	return other.timestamp.After(s.timestamp)
}

// MeetsCriteria returns true if the snapshot meets the criteria in cacheCriteria
func (s *allInstancesSnapshot) MeetsCriteria(criteria cacheCriteria) bool {
	if criteria.MaxAge > 0 {
		// Sub() is technically broken by time changes until we have monotonic time
		now := time.Now()
		if now.Sub(s.timestamp) > criteria.MaxAge {
			klog.V(6).Infof("instanceCache snapshot cannot be used as is older than MaxAge=%s", criteria.MaxAge)
			return false
		}
	}

	if len(criteria.HasInstances) != 0 {
		for _, id := range criteria.HasInstances {
			if nil == s.instances[id] {
				klog.V(6).Infof("instanceCache snapshot cannot be used as does not contain instance %s", id)
				return false
			}
		}
	}

	return true
}

// allInstancesSnapshot holds the results from querying for all instances,
// along with the timestamp for cache-invalidation purposes
type allInstancesSnapshot struct {
	timestamp time.Time
	instances map[InstanceID]*ec2.Instance
}

// FindInstances returns the instances corresponding to the specified ids.  If an id is not found, it is ignored.
func (s *allInstancesSnapshot) FindInstances(ids []InstanceID) map[InstanceID]*ec2.Instance {
	m := make(map[InstanceID]*ec2.Instance)
	for _, id := range ids {
		instance := s.instances[id]
		if instance != nil {
			m[id] = instance
		}
	}
	return m
}
<|endoftext|>


<commit_before>package gofeed

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"

	"github.com/mmcdole/gofeed/atom"
	"github.com/mmcdole/gofeed/feed"
	"github.com/mmcdole/gofeed/rss"
	"github.com/mmcdole/goxpp"
)

type FeedType int

const (
	FeedTypeUnknown FeedType = iota
	FeedTypeAtom
	FeedTypeRSS
)

type FeedParser struct {
	AtomTrans atom.Translator
	RSSTrans  rss.Translator
	rp        *rss.Parser
	ap        *atom.Parser
}

func NewFeedParser() *FeedParser {
	fp := FeedParser{
		rp: &rss.Parser{},
		ap: &atom.Parser{},
	}
	return &fp
}

func (f *FeedParser) ParseFeedURL(feedURL string) (*feed.Feed, error) {
	resp, err := http.Get(feedURL)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	return f.ParseFeed(string(body))
}

func (f *FeedParser) ParseFeed(feed string) (*feed.Feed, error) {
	fmt.Println(feed)
	ft := DetectFeedType(feed)
	switch ft {
	case FeedTypeAtom:
		return f.parseFeedFromAtom(feed)
	case FeedTypeRSS:
		return f.parseFeedFromRSS(feed)
	}
	return nil, errors.New("Failed to detect feed type")
}

func DetectFeedType(feed string) FeedType {
	p := xpp.NewXMLPullParser(strings.NewReader(feed))

	_, err := p.NextTag()
	if err != nil {
		fmt.Printf("Error %s: \n", err)
		return FeedTypeUnknown
	}

	name := strings.ToLower(p.Name)
	switch name {
	case "rdf":
		return FeedTypeRSS
	case "rss":
		return FeedTypeRSS
	case "feed":
		return FeedTypeAtom
	default:
		return FeedTypeUnknown
	}
}

func (f *FeedParser) parseFeedFromAtom(feed string) (*feed.Feed, error) {
	af, err := f.ap.ParseFeed(feed)
	if err != nil {
		return nil, err
	}
	result := f.atomTrans().Translate(af)
	return result, nil
}

func (f *FeedParser) parseFeedFromRSS(feed string) (*feed.Feed, error) {
	rf, err := f.rp.ParseFeed(feed)
	if err != nil {
		return nil, err
	}

	result := f.rssTrans().Translate(rf)
	return result, nil
}

func (f *FeedParser) atomTrans() atom.Translator {
	if f.AtomTrans != nil {
		return f.AtomTrans
	}
	f.AtomTrans = &atom.DefaultTranslator{}
	return f.AtomTrans
}

func (f *FeedParser) rssTrans() rss.Translator {
	if f.RSSTrans != nil {
		return f.RSSTrans
	}
	f.RSSTrans = &rss.DefaultTranslator{}
	return f.RSSTrans
}
<commit_msg>Move detect method into feedparser<commit_after>package gofeed

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"

	"github.com/mmcdole/gofeed/atom"
	"github.com/mmcdole/gofeed/feed"
	"github.com/mmcdole/gofeed/rss"
	"github.com/mmcdole/goxpp"
)

type FeedType int

const (
	FeedTypeUnknown FeedType = iota
	FeedTypeAtom
	FeedTypeRSS
)

type FeedParser struct {
	AtomTrans atom.Translator
	RSSTrans  rss.Translator
	rp        *rss.Parser
	ap        *atom.Parser
}

func NewFeedParser() *FeedParser {
	fp := FeedParser{
		rp: &rss.Parser{},
		ap: &atom.Parser{},
	}
	return &fp
}

func (f *FeedParser) ParseFeedURL(feedURL string) (*feed.Feed, error) {
	resp, err := http.Get(feedURL)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	return f.ParseFeed(string(body))
}

func (f *FeedParser) ParseFeed(feed string) (*feed.Feed, error) {
	fmt.Println(feed)
	ft := DetectFeedType(feed)
	switch ft {
	case FeedTypeAtom:
		return f.parseFeedFromAtom(feed)
	case FeedTypeRSS:
		return f.parseFeedFromRSS(feed)
	}
	return nil, errors.New("Failed to detect feed type")
}

func (f *FeedParser) DetectFeedType(feed string) FeedType {
	p := xpp.NewXMLPullParser(strings.NewReader(feed))

	_, err := p.NextTag()
	if err != nil {
		fmt.Printf("Error %s: \n", err)
		return FeedTypeUnknown
	}

	name := strings.ToLower(p.Name)
	switch name {
	case "rdf":
		return FeedTypeRSS
	case "rss":
		return FeedTypeRSS
	case "feed":
		return FeedTypeAtom
	default:
		return FeedTypeUnknown
	}
}

func (f *FeedParser) parseFeedFromAtom(feed string) (*feed.Feed, error) {
	af, err := f.ap.ParseFeed(feed)
	if err != nil {
		return nil, err
	}
	result := f.atomTrans().Translate(af)
	return result, nil
}

func (f *FeedParser) parseFeedFromRSS(feed string) (*feed.Feed, error) {
	rf, err := f.rp.ParseFeed(feed)
	if err != nil {
		return nil, err
	}

	result := f.rssTrans().Translate(rf)
	return result, nil
}

func (f *FeedParser) atomTrans() atom.Translator {
	if f.AtomTrans != nil {
		return f.AtomTrans
	}
	f.AtomTrans = &atom.DefaultTranslator{}
	return f.AtomTrans
}

func (f *FeedParser) rssTrans() rss.Translator {
	if f.RSSTrans != nil {
		return f.RSSTrans
	}
	f.RSSTrans = &rss.DefaultTranslator{}
	return f.RSSTrans
}
<|endoftext|>


<commit_before>// +build riak

package rkive

import (
	"bytes"
	"testing"
	"os"
	"fmt"
)

var testClient *Client

func init() {
        var err error
        testClient, err = DialOne("localhost:8087", "testClient")
        if err != nil {
                fmt.Printf("Couldn't connect to Riak: %s\n", err)
                os.Exit(1)
        }
}

type TestObject struct {
	Data []byte
	info *Info
}

func (t *TestObject) Unmarshal(b []byte) error {
	t.Data = b
	return nil
}

func (t *TestObject) Marshal() ([]byte, error) {
	return t.Data, nil
}

func (t *TestObject) Info() *Info { return t.info }

func (t *TestObject) NewEmpty() ObjectM { return &TestObject{nil, &Info{}} }

// naive merge
func (t *TestObject) Merge(o ObjectM) {
	tn := o.(*TestObject)
	if len(tn.Data) > len(t.Data) {
		t.Data = tn.Data
	}
}

func TestMultipleVclocks(t *testing.T) {
	oba := &TestObject{
		Data: []byte("Body 1"),
		info: &Info{},
	}

	obb := &TestObject{
		Data: []byte("Body 2..."),
		info: &Info{},
	}

	// manually create conflict - a user can't ordinarily do this
	oba.Info().bucket, oba.Info().key = []byte("testbucket"), []byte("conflict")
	obb.Info().bucket, obb.Info().key = []byte("testbucket"), []byte("conflict")

	cl := testClient

	// The store operations should not error,
	// because we are doing a fetch and merge
	// when we detect multiple responses on
	// Store()
	err := cl.Store(obb, nil)
	if err != nil {
		t.Fatal(err)
	}
	err = cl.Store(oba, nil)
	if err != nil {
		t.Fatal(err)
	}

	// Since our Merge() function takes the longer of the
	// two Data fields, the body should always be "Body 2..."
	err = cl.Fetch(oba, "testbucket", "conflict", nil)
	if err != nil {
		t.Fatal(err)
	}

	if !bytes.Equal(oba.Data, []byte("Body 2...")) {
		t.Errorf("Data should be %q; got %q", "Body 2...", oba.Data)
	}
}

func TestFetchNotFound(t *testing.T) {
	cl := testClient
	ob := &TestObject{}

	err := cl.Fetch(ob, "anybucket", "dne", nil)
	if err == nil {
		t.Error("'err' should not be nil")
	}
	if err != ErrNotFound {
		t.Errorf("err is not ErrNotFound: %q", err)
	}
}

func TestUpdate(t *testing.T) {
	cl := testClient

	test := cl.Bucket("testbucket")

	lb := &TestObject{
		Data: []byte("flibbertyibbitygibbit"),
		info: &Info{},
	}

	err := test.New(lb, nil)
	if err != nil {
		t.Fatal(err)
	}

	newlb := &TestObject{
		info: &Info{},
	}

	err = test.Fetch(newlb, lb.Info().Key())
	if err != nil {
		t.Fatal(err)
	}

	if !bytes.Equal(newlb.Data, lb.Data) {
		t.Logf("Object 1 data: %q", lb.Data)
		t.Logf("Object 2 data: %q", newlb.Data)
		t.Errorf("Objects don't have the same body")
	}

	// make a modification
	newlb.Data = []byte("new data.")
	err = test.Push(newlb)
	if err != nil {
		t.Fatal(err)
	}

	// this should return true
	upd, err := test.Update(lb)
	if err != nil {
		t.Fatal(err)
	}

	if !upd {
		t.Error("Object was not updated.")
	}

	if !bytes.Equal(lb.Data, newlb.Data) {
		t.Error("Objects are not equal after update.")
	}

	// this should return false
	upd, err = test.Update(newlb)
	if err != nil {
		t.Fatal(err)
	}

	if upd {
		t.Error("Object was spuriously updated...?")
	}
}
<commit_msg>FetchHead test<commit_after>// +build riak

package rkive

import (
	"bytes"
	"testing"
	"os"
	"fmt"
)

var testClient *Client

func init() {
        var err error
        testClient, err = DialOne("localhost:8087", "testClient")
        if err != nil {
                fmt.Printf("Couldn't connect to Riak: %s\n", err)
                os.Exit(1)
        }
}

type TestObject struct {
	Data []byte
	info *Info
}

func (t *TestObject) Unmarshal(b []byte) error {
	t.Data = b
	return nil
}

func (t *TestObject) Marshal() ([]byte, error) {
	return t.Data, nil
}

func (t *TestObject) Info() *Info { return t.info }

func (t *TestObject) NewEmpty() ObjectM { return &TestObject{nil, &Info{}} }

// naive merge
func (t *TestObject) Merge(o ObjectM) {
	tn := o.(*TestObject)
	if len(tn.Data) > len(t.Data) {
		t.Data = tn.Data
	}
}

func TestMultipleVclocks(t *testing.T) {
	oba := &TestObject{
		Data: []byte("Body 1"),
		info: &Info{},
	}

	obb := &TestObject{
		Data: []byte("Body 2..."),
		info: &Info{},
	}

	// manually create conflict - a user can't ordinarily do this
	oba.Info().bucket, oba.Info().key = []byte("testbucket"), []byte("conflict")
	obb.Info().bucket, obb.Info().key = []byte("testbucket"), []byte("conflict")

	cl := testClient

	// The store operations should not error,
	// because we are doing a fetch and merge
	// when we detect multiple responses on
	// Store()
	err := cl.Store(obb, nil)
	if err != nil {
		t.Fatal(err)
	}
	err = cl.Store(oba, nil)
	if err != nil {
		t.Fatal(err)
	}

	// Since our Merge() function takes the longer of the
	// two Data fields, the body should always be "Body 2..."
	err = cl.Fetch(oba, "testbucket", "conflict", nil)
	if err != nil {
		t.Fatal(err)
	}

	if !bytes.Equal(oba.Data, []byte("Body 2...")) {
		t.Errorf("Data should be %q; got %q", "Body 2...", oba.Data)
	}
}

func TestFetchNotFound(t *testing.T) {
	cl := testClient
	ob := &TestObject{}

	err := cl.Fetch(ob, "anybucket", "dne", nil)
	if err == nil {
		t.Error("'err' should not be nil")
	}
	if err != ErrNotFound {
		t.Errorf("err is not ErrNotFound: %q", err)
	}
}

func TestUpdate(t *testing.T) {
	cl := testClient

	test := cl.Bucket("testbucket")

	lb := &TestObject{
		Data: []byte("flibbertyibbitygibbit"),
		info: &Info{},
	}

	err := test.New(lb, nil)
	if err != nil {
		t.Fatal(err)
	}

	newlb := &TestObject{
		info: &Info{},
	}

	err = test.Fetch(newlb, lb.Info().Key())
	if err != nil {
		t.Fatal(err)
	}

	if !bytes.Equal(newlb.Data, lb.Data) {
		t.Logf("Object 1 data: %q", lb.Data)
		t.Logf("Object 2 data: %q", newlb.Data)
		t.Errorf("Objects don't have the same body")
	}

	// make a modification
	newlb.Data = []byte("new data.")
	err = test.Push(newlb)
	if err != nil {
		t.Fatal(err)
	}

	// this should return true
	upd, err := test.Update(lb)
	if err != nil {
		t.Fatal(err)
	}

	if !upd {
		t.Error("Object was not updated.")
	}

	if !bytes.Equal(lb.Data, newlb.Data) {
		t.Error("Objects are not equal after update.")
	}

	// this should return false
	upd, err = test.Update(newlb)
	if err != nil {
		t.Fatal(err)
	}

	if upd {
		t.Error("Object was spuriously updated...?")
	}
}

func TestHead(t *testing.T) {
        t.Parallel()
        cl := testClient

        tests := cl.Bucket("testbucket")

        ob := &TestObject{
                info: &Info{},
                Data: []byte("exists."),
        }

        err := tests.New(ob, nil)
        if err != nil {
                t.Fatal(err)
        }

        // fetch head exists
        var info *Info
        info, err = cl.FetchHead("testbucket", ob.Info().Key())
        if err != nil {
                t.Fatal(err)
        }

        if !bytes.Equal(info.vclock, ob.info.vclock) {
                t.Errorf("vclocks not equal: %q and %q", info.vclock, ob.info.vclock)
        }

        // fetch dne
        _, err = cl.FetchHead("testbucket", "dne")
        if err != ErrNotFound {
                t.Errorf("expected ErrNotFound, got: %q", err)
        }
}
<|endoftext|>


<commit_before>// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package platformvm

import (
	"fmt"
	"time"

	"github.com/ava-labs/avalanchego/vms/components/avax"

	safemath "github.com/ava-labs/avalanchego/utils/math"
)

var (
	_ UnsignedProposalTx = &UnsignedAdvanceTimeTx{}
)

// UnsignedAdvanceTimeTx is a transaction to increase the chain's timestamp.
// When the chain's timestamp is updated (a AdvanceTimeTx is accepted and
// followed by a commit block) the staker set is also updated accordingly.
// It must be that:
//   * proposed timestamp > [current chain time]
//   * proposed timestamp <= [time for next staker to be removed]
type UnsignedAdvanceTimeTx struct {
	avax.Metadata

	// Unix time this block proposes increasing the timestamp to
	Time uint64 `serialize:"true" json:"time"`
}

// Timestamp returns the time this block is proposing the chain should be set to
func (tx *UnsignedAdvanceTimeTx) Timestamp() time.Time {
	return time.Unix(int64(tx.Time), 0)
}

// SemanticVerify this transaction is valid.
func (tx *UnsignedAdvanceTimeTx) SemanticVerify(
	vm *VM,
	parentState MutableState,
	stx *Tx,
) (
	VersionedState,
	VersionedState,
	func() error,
	func() error,
	TxError,
) {
	switch {
	case tx == nil:
		return nil, nil, nil, nil, tempError{errNilTx}
	case len(stx.Creds) != 0:
		return nil, nil, nil, nil, permError{errWrongNumberOfCredentials}
	}

	timestamp := tx.Timestamp()
	localTimestamp := vm.clock.Time()
	if localTimestamp.Add(syncBound).Before(timestamp) {
		return nil, nil, nil, nil, tempError{
			fmt.Errorf(
				"proposed time (%s) is too far in the future relative to local time (%s)",
				timestamp,
				localTimestamp,
			),
		}
	}

	if currentTimestamp := parentState.GetTimestamp(); !timestamp.After(currentTimestamp) {
		return nil, nil, nil, nil, permError{
			fmt.Errorf(
				"proposed timestamp (%s), not after current timestamp (%s)",
				timestamp,
				currentTimestamp,
			),
		}
	}

	// Only allow timestamp to move forward as far as the time of next staker
	// set change time
	nextStakerChangeTime, err := vm.nextStakerChangeTime(parentState)
	if err != nil {
		return nil, nil, nil, nil, tempError{err}
	}

	if timestamp.After(nextStakerChangeTime) {
		return nil, nil, nil, nil, permError{
			fmt.Errorf(
				"proposed timestamp (%s) later than next staker change time (%s)",
				timestamp,
				nextStakerChangeTime,
			),
		}
	}

	currentSupply := parentState.GetCurrentSupply()

	pendingStakers := parentState.PendingStakerChainState()
	toAddValidatorsWithRewardToCurrent := []*validatorReward(nil)
	toAddDelegatorsWithRewardToCurrent := []*validatorReward(nil)
	toAddWithoutRewardToCurrent := []*Tx(nil)
	numToRemoveFromPending := 0

	// Add to the staker set any pending stakers whose start time is at or
	// before the new timestamp. [pendingStakers.Stakers()] is sorted in order
	// of increasing startTime
pendingStakerLoop:
	for _, tx := range pendingStakers.Stakers() {
		switch staker := tx.UnsignedTx.(type) {
		case *UnsignedAddDelegatorTx:
			if staker.StartTime().After(timestamp) {
				break pendingStakerLoop
			}

			r := reward(
				staker.Validator.Duration(),
				staker.Validator.Wght,
				currentSupply,
				vm.StakeMintingPeriod,
			)
			currentSupply, err = safemath.Add64(currentSupply, r)
			if err != nil {
				return nil, nil, nil, nil, permError{err}
			}

			toAddDelegatorsWithRewardToCurrent = append(toAddDelegatorsWithRewardToCurrent, &validatorReward{
				addStakerTx:     tx,
				potentialReward: r,
			})
			numToRemoveFromPending++
		case *UnsignedAddValidatorTx:
			if staker.StartTime().After(timestamp) {
				break pendingStakerLoop
			}

			r := reward(
				staker.Validator.Duration(),
				staker.Validator.Wght,
				currentSupply,
				vm.StakeMintingPeriod,
			)
			currentSupply, err = safemath.Add64(currentSupply, r)
			if err != nil {
				return nil, nil, nil, nil, permError{err}
			}

			toAddValidatorsWithRewardToCurrent = append(toAddValidatorsWithRewardToCurrent, &validatorReward{
				addStakerTx:     tx,
				potentialReward: r,
			})
			numToRemoveFromPending++
		case *UnsignedAddSubnetValidatorTx:
			if staker.StartTime().After(timestamp) {
				break pendingStakerLoop
			}

			// If this staker should already be removed, then we should just
			// never add them.
			if staker.EndTime().After(timestamp) {
				toAddWithoutRewardToCurrent = append(toAddWithoutRewardToCurrent, tx)
			}
			numToRemoveFromPending++
		default:
			return nil, nil, nil, nil, permError{
				fmt.Errorf("expected validator but got %T", tx.UnsignedTx),
			}
		}
	}
	newlyPendingStakers := pendingStakers.DeleteStakers(numToRemoveFromPending)

	currentStakers := parentState.CurrentStakerChainState()
	numToRemoveFromCurrent := 0

	// Remove from the staker set any subnet validators whose endTime is at or
	// before the new timestamp
currentStakerLoop:
	for _, tx := range currentStakers.Stakers() {
		switch staker := tx.UnsignedTx.(type) {
		case *UnsignedAddSubnetValidatorTx:
			if staker.EndTime().After(timestamp) {
				break currentStakerLoop
			}

			numToRemoveFromCurrent++
		case *UnsignedAddValidatorTx, *UnsignedAddDelegatorTx:
			// We shouldn't be removing any primary network validators here
			break currentStakerLoop
		default:
			return nil, nil, nil, nil, permError{errWrongTxType}
		}
	}
	newlyCurrentStakers, err := currentStakers.UpdateStakers(
		toAddValidatorsWithRewardToCurrent,
		toAddDelegatorsWithRewardToCurrent,
		toAddWithoutRewardToCurrent,
		numToRemoveFromCurrent,
	)
	if err != nil {
		return nil, nil, nil, nil, tempError{err}
	}

	onCommitState := newVersionedState(parentState, newlyCurrentStakers, newlyPendingStakers)
	onCommitState.SetTimestamp(timestamp)
	onCommitState.SetCurrentSupply(currentSupply)

	// State doesn't change if this proposal is aborted
	onAbortState := newVersionedState(parentState, currentStakers, pendingStakers)

	// If this block is committed, update the validator sets.
	// onCommitDB will be committed to vm.DB before this is called.
	onCommitFunc := func() error {
		// For each Subnet, update the node's validator manager to reflect
		// current Subnet membership
		return vm.updateValidators(false)
	}

	return onCommitState, onAbortState, onCommitFunc, nil, nil
}

// InitiallyPrefersCommit returns true if the proposed time is at
// or before the current time plus the synchrony bound
func (tx *UnsignedAdvanceTimeTx) InitiallyPrefersCommit(vm *VM) bool {
	return !tx.Timestamp().After(vm.clock.Time().Add(syncBound))
}

// newAdvanceTimeTx creates a new tx that, if it is accepted and followed by a
// Commit block, will set the chain's timestamp to [timestamp].
func (vm *VM) newAdvanceTimeTx(timestamp time.Time) (*Tx, error) {
	tx := &Tx{UnsignedTx: &UnsignedAdvanceTimeTx{
		Time: uint64(timestamp.Unix()),
	}}
	return tx, tx.Sign(vm.codec, nil)
}
<commit_msg>fix advance timestamp tx comment<commit_after>// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package platformvm

import (
	"fmt"
	"time"

	"github.com/ava-labs/avalanchego/vms/components/avax"

	safemath "github.com/ava-labs/avalanchego/utils/math"
)

var (
	_ UnsignedProposalTx = &UnsignedAdvanceTimeTx{}
)

// UnsignedAdvanceTimeTx is a transaction to increase the chain's timestamp.
// When the chain's timestamp is updated (a AdvanceTimeTx is accepted and
// followed by a commit block) the staker set is also updated accordingly.
// It must be that:
//   * proposed timestamp > [current chain time]
//   * proposed timestamp <= [time for next staker set change]
type UnsignedAdvanceTimeTx struct {
	avax.Metadata

	// Unix time this block proposes increasing the timestamp to
	Time uint64 `serialize:"true" json:"time"`
}

// Timestamp returns the time this block is proposing the chain should be set to
func (tx *UnsignedAdvanceTimeTx) Timestamp() time.Time {
	return time.Unix(int64(tx.Time), 0)
}

// SemanticVerify this transaction is valid.
func (tx *UnsignedAdvanceTimeTx) SemanticVerify(
	vm *VM,
	parentState MutableState,
	stx *Tx,
) (
	VersionedState,
	VersionedState,
	func() error,
	func() error,
	TxError,
) {
	switch {
	case tx == nil:
		return nil, nil, nil, nil, tempError{errNilTx}
	case len(stx.Creds) != 0:
		return nil, nil, nil, nil, permError{errWrongNumberOfCredentials}
	}

	timestamp := tx.Timestamp()
	localTimestamp := vm.clock.Time()
	if localTimestamp.Add(syncBound).Before(timestamp) {
		return nil, nil, nil, nil, tempError{
			fmt.Errorf(
				"proposed time (%s) is too far in the future relative to local time (%s)",
				timestamp,
				localTimestamp,
			),
		}
	}

	if currentTimestamp := parentState.GetTimestamp(); !timestamp.After(currentTimestamp) {
		return nil, nil, nil, nil, permError{
			fmt.Errorf(
				"proposed timestamp (%s), not after current timestamp (%s)",
				timestamp,
				currentTimestamp,
			),
		}
	}

	// Only allow timestamp to move forward as far as the time of next staker
	// set change time
	nextStakerChangeTime, err := vm.nextStakerChangeTime(parentState)
	if err != nil {
		return nil, nil, nil, nil, tempError{err}
	}

	if timestamp.After(nextStakerChangeTime) {
		return nil, nil, nil, nil, permError{
			fmt.Errorf(
				"proposed timestamp (%s) later than next staker change time (%s)",
				timestamp,
				nextStakerChangeTime,
			),
		}
	}

	currentSupply := parentState.GetCurrentSupply()

	pendingStakers := parentState.PendingStakerChainState()
	toAddValidatorsWithRewardToCurrent := []*validatorReward(nil)
	toAddDelegatorsWithRewardToCurrent := []*validatorReward(nil)
	toAddWithoutRewardToCurrent := []*Tx(nil)
	numToRemoveFromPending := 0

	// Add to the staker set any pending stakers whose start time is at or
	// before the new timestamp. [pendingStakers.Stakers()] is sorted in order
	// of increasing startTime
pendingStakerLoop:
	for _, tx := range pendingStakers.Stakers() {
		switch staker := tx.UnsignedTx.(type) {
		case *UnsignedAddDelegatorTx:
			if staker.StartTime().After(timestamp) {
				break pendingStakerLoop
			}

			r := reward(
				staker.Validator.Duration(),
				staker.Validator.Wght,
				currentSupply,
				vm.StakeMintingPeriod,
			)
			currentSupply, err = safemath.Add64(currentSupply, r)
			if err != nil {
				return nil, nil, nil, nil, permError{err}
			}

			toAddDelegatorsWithRewardToCurrent = append(toAddDelegatorsWithRewardToCurrent, &validatorReward{
				addStakerTx:     tx,
				potentialReward: r,
			})
			numToRemoveFromPending++
		case *UnsignedAddValidatorTx:
			if staker.StartTime().After(timestamp) {
				break pendingStakerLoop
			}

			r := reward(
				staker.Validator.Duration(),
				staker.Validator.Wght,
				currentSupply,
				vm.StakeMintingPeriod,
			)
			currentSupply, err = safemath.Add64(currentSupply, r)
			if err != nil {
				return nil, nil, nil, nil, permError{err}
			}

			toAddValidatorsWithRewardToCurrent = append(toAddValidatorsWithRewardToCurrent, &validatorReward{
				addStakerTx:     tx,
				potentialReward: r,
			})
			numToRemoveFromPending++
		case *UnsignedAddSubnetValidatorTx:
			if staker.StartTime().After(timestamp) {
				break pendingStakerLoop
			}

			// If this staker should already be removed, then we should just
			// never add them.
			if staker.EndTime().After(timestamp) {
				toAddWithoutRewardToCurrent = append(toAddWithoutRewardToCurrent, tx)
			}
			numToRemoveFromPending++
		default:
			return nil, nil, nil, nil, permError{
				fmt.Errorf("expected validator but got %T", tx.UnsignedTx),
			}
		}
	}
	newlyPendingStakers := pendingStakers.DeleteStakers(numToRemoveFromPending)

	currentStakers := parentState.CurrentStakerChainState()
	numToRemoveFromCurrent := 0

	// Remove from the staker set any subnet validators whose endTime is at or
	// before the new timestamp
currentStakerLoop:
	for _, tx := range currentStakers.Stakers() {
		switch staker := tx.UnsignedTx.(type) {
		case *UnsignedAddSubnetValidatorTx:
			if staker.EndTime().After(timestamp) {
				break currentStakerLoop
			}

			numToRemoveFromCurrent++
		case *UnsignedAddValidatorTx, *UnsignedAddDelegatorTx:
			// We shouldn't be removing any primary network validators here
			break currentStakerLoop
		default:
			return nil, nil, nil, nil, permError{errWrongTxType}
		}
	}
	newlyCurrentStakers, err := currentStakers.UpdateStakers(
		toAddValidatorsWithRewardToCurrent,
		toAddDelegatorsWithRewardToCurrent,
		toAddWithoutRewardToCurrent,
		numToRemoveFromCurrent,
	)
	if err != nil {
		return nil, nil, nil, nil, tempError{err}
	}

	onCommitState := newVersionedState(parentState, newlyCurrentStakers, newlyPendingStakers)
	onCommitState.SetTimestamp(timestamp)
	onCommitState.SetCurrentSupply(currentSupply)

	// State doesn't change if this proposal is aborted
	onAbortState := newVersionedState(parentState, currentStakers, pendingStakers)

	// If this block is committed, update the validator sets.
	// onCommitDB will be committed to vm.DB before this is called.
	onCommitFunc := func() error {
		// For each Subnet, update the node's validator manager to reflect
		// current Subnet membership
		return vm.updateValidators(false)
	}

	return onCommitState, onAbortState, onCommitFunc, nil, nil
}

// InitiallyPrefersCommit returns true if the proposed time is at
// or before the current time plus the synchrony bound
func (tx *UnsignedAdvanceTimeTx) InitiallyPrefersCommit(vm *VM) bool {
	return !tx.Timestamp().After(vm.clock.Time().Add(syncBound))
}

// newAdvanceTimeTx creates a new tx that, if it is accepted and followed by a
// Commit block, will set the chain's timestamp to [timestamp].
func (vm *VM) newAdvanceTimeTx(timestamp time.Time) (*Tx, error) {
	tx := &Tx{UnsignedTx: &UnsignedAdvanceTimeTx{
		Time: uint64(timestamp.Unix()),
	}}
	return tx, tx.Sign(vm.codec, nil)
}
<|endoftext|>