<commit_before>package resources

import (
	"github.com/hashicorp/terraform/helper/schema"
	"code.cloudfoundry.org/cli/cf/models"
	"github.com/orange-cloudfoundry/terraform-provider-cloudfoundry/cf_client"
	"log"
	"reflect"
	"fmt"
	"bytes"
	"github.com/hashicorp/terraform/helper/hashcode"
	"strconv"
	"github.com/orange-cloudfoundry/terraform-provider-cloudfoundry/resources/caching"
	"errors"
	"strings"
	"regexp"
)

var validProtocoles []string = []string{"icmp", "tcp", "udp", "all"}

type CfSecurityGroupResource struct {
	CfResource
}

func NewCfSecurityGroupResource() CfResource {
	return &CfSecurityGroupResource{}
}

func (c CfSecurityGroupResource) resourceObject(d *schema.ResourceData) models.SecurityGroupFields {
	rulesSchema := d.Get("rules").(*schema.Set)
	rules := make([]map[string]interface{}, 0)
	for _, rule := range rulesSchema.List() {
		rules = append(rules, c.sanitizeRule(rule.(map[string]interface{})))
	}
	return models.SecurityGroupFields{
		GUID: d.Id(),
		Name: d.Get("name").(string),
		Rules: rules,
	}
}
func (c CfSecurityGroupResource) unSanitizeRule(rule map[string]interface{}) map[string]interface{} {
	unSanitizedRule := make(map[string]interface{})
	if _, ok := rule["code"]; !ok {
		unSanitizedRule["code"] = 0
	}
	if _, ok := rule["type"]; !ok {
		unSanitizedRule["type"] = 0
	}
	for index, content := range rule {
		unSanitizedRule[index] = content
	}
	return unSanitizedRule
}
func (c CfSecurityGroupResource) sanitizeRule(rule map[string]interface{}) map[string]interface{} {
	sanitizedRule := make(map[string]interface{})

	for index, content := range rule {
		if index == "code" && content.(int) == 0 {
			continue
		}
		if index == "type" && content.(int) == 0 {
			continue
		}
		sanitizedRule[index] = content
	}
	return sanitizedRule
}
func (c CfSecurityGroupResource) Create(d *schema.ResourceData, meta interface{}) error {
	client := meta.(cf_client.Client)
	secGroup := c.resourceObject(d)
	var err error
	if ok, _ := c.Exists(d, meta); ok {
		log.Printf(
			"[INFO] skipping creation of security group %s/%s because it already exists on your Cloud Foundry",
			client.Config().ApiEndpoint,
			secGroup.Name,
		)
		err = client.SecurityGroups().Update(d.Id(), secGroup.Rules)
		if err != nil {
			return err
		}
	} else {
		err = client.SecurityGroups().Create(secGroup.Name, secGroup.Rules)
		if err != nil {
			return err
		}
		_, err = c.Exists(d, meta)
		if err != nil {
			return err
		}
	}
	if d.Get("on_staging").(bool) {
		err = client.SecurityGroupsStagingBinder().BindToStagingSet(d.Id())
		if err != nil {
			return err
		}
	}
	if d.Get("on_running").(bool) {
		err = client.SecurityGroupsRunningBinder().BindToRunningSet(d.Id())
		if err != nil {
			return err
		}
	}
	return nil
}

func (c CfSecurityGroupResource) Read(d *schema.ResourceData, meta interface{}) error {
	client := meta.(cf_client.Client)
	secGroupName := d.Get("name").(string)
	secGroup, err := c.getSecGroupFromCf(client, d.Id(), true)
	if err != nil {
		return err
	}
	if secGroup.GUID == "" {
		log.Printf(
			"[WARN] removing security group %s/%s from state because it no longer exists in your Cloud Foundry",
			client.Config().ApiEndpoint,
			secGroupName,
		)
		d.SetId("")
		return nil
	}
	d.Set("name", secGroup.Name)
	rules := make([]interface{}, 0)
	rulesSchema := schema.NewSet(d.Get("rules").(*schema.Set).F, rules)
	for _, rule := range secGroup.Rules {
		rulesSchema.Add(c.unSanitizeRule(rule))
	}
	d.Set("rules", rulesSchema)
	isOnStaging, err := c.isOnStaging(client, secGroup.GUID)
	if err != nil {
		return err
	}
	isOnRunning, err := c.isOnRunning(client, secGroup.GUID)
	if err != nil {
		return err
	}
	d.Set("on_staging", isOnStaging)
	d.Set("on_running", isOnRunning)
	return nil
}
func (c CfSecurityGroupResource) Update(d *schema.ResourceData, meta interface{}) error {
	client := meta.(cf_client.Client)
	secGroup := c.resourceObject(d)
	secGroupCf, err := c.getSecGroupFromCf(client, d.Id(), false)
	if err != nil {
		return err
	}
	if secGroupCf.GUID == "" {
		log.Printf(
			"[WARN] removing security group %s/%s from state because it no longer exists in your Cloud Foundry",
			client.Config().ApiEndpoint,
			secGroup.Name,
		)
		d.SetId("")
		return nil
	}
	if c.isRulesChange(secGroupCf.Rules, secGroup.Rules) {
		client.SecurityGroups().Update(d.Id(), secGroup.Rules)
	}
	isOnStaging, err := c.isOnStaging(client, d.Id())
	if err != nil {
		return err
	}
	if d.Get("on_staging").(bool) != isOnStaging {
		err = c.updateBindingStaging(client, d.Id(), d.Get("on_staging").(bool))
		if err != nil {
			return err
		}
	}
	if d.Get("on_running").(bool) != isOnStaging {
		err = c.updateBindingStaging(client, d.Id(), d.Get("on_running").(bool))
		if err != nil {
			return err
		}
	}
	return nil
}
func (c CfSecurityGroupResource) Delete(d *schema.ResourceData, meta interface{}) error {
	client := meta.(cf_client.Client)
	return client.SecurityGroups().Delete(d.Id())
}
func (c CfSecurityGroupResource) updateBindingStaging(client cf_client.Client, guid string, onStaging bool) error {
	if onStaging {
		return client.SecurityGroupsStagingBinder().BindToStagingSet(guid)
	}
	return client.SecurityGroupsStagingBinder().UnbindFromStagingSet(guid)
}
func (c CfSecurityGroupResource) updateBindingRunning(client cf_client.Client, guid string, onRunning bool) error {
	if onRunning {
		return client.SecurityGroupsRunningBinder().BindToRunningSet(guid)
	}
	return client.SecurityGroupsRunningBinder().UnbindFromRunningSet(guid)
}
func (c CfSecurityGroupResource) isRulesChange(rulesFrom, rulesTo []map[string]interface{}) bool {
	if rulesFrom == nil && rulesTo == nil {
		return false;
	}
	if rulesFrom == nil || rulesTo == nil {
		return true;
	}
	if len(rulesFrom) != len(rulesTo) {
		return true
	}
	for i := range rulesFrom {
		if !reflect.DeepEqual(rulesFrom[i], rulesTo[i]) {
			return true
		}
	}
	for i := range rulesTo {
		if !reflect.DeepEqual(rulesFrom[i], rulesTo[i]) {
			return true
		}
	}
	return false

}
func (c CfSecurityGroupResource) isOnStaging(client cf_client.Client, secGroupId string) (bool, error) {
	secGroups, err := client.SecurityGroupsStagingBinder().List()
	if err != nil {
		return false, err
	}
	return c.existsSecurityGroup(secGroups, secGroupId), nil
}
func (c CfSecurityGroupResource) isOnRunning(client cf_client.Client, secGroupId string) (bool, error) {
	secGroups, err := client.SecurityGroupsRunningBinder().List()
	if err != nil {
		return false, err
	}
	return c.existsSecurityGroup(secGroups, secGroupId), nil
}
func (c CfSecurityGroupResource) existsSecurityGroup(secGroups []models.SecurityGroupFields, secGroupId string) bool {
	for _, secGroup := range secGroups {
		if secGroup.GUID == secGroupId {
			return true
		}
	}
	return false
}
func (c CfSecurityGroupResource) getSecGroupFromCf(client cf_client.Client, secGroupId string, updateCache bool) (models.SecurityGroupFields, error) {
	secGroups, err := caching.GetSecGroupsFromCf(client, updateCache)
	if err != nil {
		return models.SecurityGroupFields{}, err
	}
	for _, secGroup := range secGroups {
		if secGroup.GUID == secGroupId {
			return secGroup.SecurityGroupFields, nil
		}
	}
	return models.SecurityGroupFields{}, nil
}
func (c CfSecurityGroupResource) Exists(d *schema.ResourceData, meta interface{}) (bool, error) {
	client := meta.(cf_client.Client)
	name := d.Get("name").(string)
	secGroups, err := caching.GetSecGroupsFromCf(client, true)
	if err != nil {
		return false, err
	}
	for _, secGroup := range secGroups {
		if secGroup.Name == name {
			d.SetId(secGroup.GUID)
			return true, nil
		}
	}

	return false, nil
}

func (c CfSecurityGroupResource) Schema() map[string]*schema.Schema {
	return map[string]*schema.Schema{
		"name": &schema.Schema{
			Type:     schema.TypeString,
			Required: true,
			ForceNew: true,
		},
		"rules": &schema.Schema{
			Type:     schema.TypeSet,
			Required: true,

			Elem:     &schema.Resource{
				Schema: map[string]*schema.Schema{
					"protocol": &schema.Schema{
						Type:     schema.TypeString,
						Required: true,
						ValidateFunc: func(elem interface{}, index string) ([]string, []error) {
							prot := elem.(string)
							found := false
							for _, validProt := range validProtocoles {
								if validProt == prot {
									found = true
									break
								}
							}
							if found {
								return make([]string, 0), make([]error, 0)
							}
							errMsg := fmt.Sprintf(
								"Protocol '%s' is not valid, it must be one of %s",
								prot,
								strings.Join(validProtocoles, ", "),
							)
							err := errors.New(errMsg)
							return make([]string, 0), []error{err}
						},
					},
					"destination": &schema.Schema{
						Type:     schema.TypeString,
						Optional: true,
					},
					"description": &schema.Schema{
						Type:     schema.TypeString,
						Optional: true,
					},
					"ports": &schema.Schema{
						Type:     schema.TypeString,
						Optional: true,
						ValidateFunc: func(elem interface{}, index string) ([]string, []error) {
							ports := elem.(string)
							match, _ := regexp.MatchString("^[0-9][0-9-,]*[0-9]?$", ports)
							if match {
								return make([]string, 0), make([]error, 0)
							}
							errMsg := fmt.Sprintf(
								"Ports '%s' is not valid. (valid examples: '443', '80,8080,8081', '8080-8081')",
								ports,
							)
							err := errors.New(errMsg)
							return make([]string, 0), []error{err}
						},
					},
					"code": &schema.Schema{
						Type:     schema.TypeInt,
						Optional: true,
					},
					"type": &schema.Schema{
						Type:     schema.TypeInt,
						Optional: true,
					},
					"log": &schema.Schema{
						Type:     schema.TypeBool,
						Default: true,
						Optional: true,
					},
				},
			},
			Set: func(v interface{}) int {
				var buf bytes.Buffer
				m := v.(map[string]interface{})
				buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string)))
				buf.WriteString(fmt.Sprintf("%s-", m["destination"].(string)))
				buf.WriteString(fmt.Sprintf("%s-", m["description"].(string)))
				buf.WriteString(fmt.Sprintf("%s-", m["ports"].(string)))
				buf.WriteString(fmt.Sprintf("%s-", strconv.Itoa(m["code"].(int))))
				buf.WriteString(fmt.Sprintf("%s-", strconv.Itoa(m["type"].(int))))
				buf.WriteString(fmt.Sprintf("%s-", strconv.FormatBool(m["log"].(bool))))
				return hashcode.String(buf.String())
			},
		},
		"on_staging": &schema.Schema{
			Type:     schema.TypeBool,
			Optional: true,
		},
		"on_running": &schema.Schema{
			Type:     schema.TypeBool,
			Optional: true,
		},
	}
}

<commit_msg>remove ports and destination if not exists to make cf happy<commit_after>package resources

import (
	"github.com/hashicorp/terraform/helper/schema"
	"code.cloudfoundry.org/cli/cf/models"
	"github.com/orange-cloudfoundry/terraform-provider-cloudfoundry/cf_client"
	"log"
	"reflect"
	"fmt"
	"bytes"
	"github.com/hashicorp/terraform/helper/hashcode"
	"strconv"
	"github.com/orange-cloudfoundry/terraform-provider-cloudfoundry/resources/caching"
	"errors"
	"strings"
	"regexp"
)

var validProtocoles []string = []string{"icmp", "tcp", "udp", "all"}

type CfSecurityGroupResource struct {
	CfResource
}

func NewCfSecurityGroupResource() CfResource {
	return &CfSecurityGroupResource{}
}

func (c CfSecurityGroupResource) resourceObject(d *schema.ResourceData) models.SecurityGroupFields {
	rulesSchema := d.Get("rules").(*schema.Set)
	rules := make([]map[string]interface{}, 0)
	for _, rule := range rulesSchema.List() {
		rules = append(rules, c.sanitizeRule(rule.(map[string]interface{})))
	}
	return models.SecurityGroupFields{
		GUID: d.Id(),
		Name: d.Get("name").(string),
		Rules: rules,
	}
}
func (c CfSecurityGroupResource) unSanitizeRule(rule map[string]interface{}) map[string]interface{} {
	unSanitizedRule := make(map[string]interface{})
	if _, ok := rule["code"]; !ok {
		unSanitizedRule["code"] = 0
	}
	if _, ok := rule["type"]; !ok {
		unSanitizedRule["type"] = 0
	}
	if _, ok := rule["ports"]; !ok {
		unSanitizedRule["ports"] = ""
	}
	if _, ok := rule["destination"]; !ok {
		unSanitizedRule["destination"] = ""
	}
	for index, content := range rule {
		unSanitizedRule[index] = content
	}
	return unSanitizedRule
}
func (c CfSecurityGroupResource) sanitizeRule(rule map[string]interface{}) map[string]interface{} {
	sanitizedRule := make(map[string]interface{})

	for index, content := range rule {
		if index == "code" && content.(int) == 0 {
			continue
		}
		if index == "type" && content.(int) == 0 {
			continue
		}
		if index == "ports" && content.(string) == "" {
			continue
		}
		if index == "destination" && content.(string) == "" {
			continue
		}
		sanitizedRule[index] = content
	}
	return sanitizedRule
}
func (c CfSecurityGroupResource) Create(d *schema.ResourceData, meta interface{}) error {
	client := meta.(cf_client.Client)
	secGroup := c.resourceObject(d)
	var err error
	if ok, _ := c.Exists(d, meta); ok {
		log.Printf(
			"[INFO] skipping creation of security group %s/%s because it already exists on your Cloud Foundry",
			client.Config().ApiEndpoint,
			secGroup.Name,
		)
		err = client.SecurityGroups().Update(d.Id(), secGroup.Rules)
		if err != nil {
			return err
		}
	} else {
		err = client.SecurityGroups().Create(secGroup.Name, secGroup.Rules)
		if err != nil {
			return err
		}
		_, err = c.Exists(d, meta)
		if err != nil {
			return err
		}
	}
	if d.Get("on_staging").(bool) {
		err = client.SecurityGroupsStagingBinder().BindToStagingSet(d.Id())
		if err != nil {
			return err
		}
	}
	if d.Get("on_running").(bool) {
		err = client.SecurityGroupsRunningBinder().BindToRunningSet(d.Id())
		if err != nil {
			return err
		}
	}
	return nil
}

func (c CfSecurityGroupResource) Read(d *schema.ResourceData, meta interface{}) error {
	client := meta.(cf_client.Client)
	secGroupName := d.Get("name").(string)
	secGroup, err := c.getSecGroupFromCf(client, d.Id(), true)
	if err != nil {
		return err
	}
	if secGroup.GUID == "" {
		log.Printf(
			"[WARN] removing security group %s/%s from state because it no longer exists in your Cloud Foundry",
			client.Config().ApiEndpoint,
			secGroupName,
		)
		d.SetId("")
		return nil
	}
	d.Set("name", secGroup.Name)
	rules := make([]interface{}, 0)
	rulesSchema := schema.NewSet(d.Get("rules").(*schema.Set).F, rules)
	for _, rule := range secGroup.Rules {
		rulesSchema.Add(c.unSanitizeRule(rule))
	}
	d.Set("rules", rulesSchema)
	isOnStaging, err := c.isOnStaging(client, secGroup.GUID)
	if err != nil {
		return err
	}
	isOnRunning, err := c.isOnRunning(client, secGroup.GUID)
	if err != nil {
		return err
	}
	d.Set("on_staging", isOnStaging)
	d.Set("on_running", isOnRunning)
	return nil
}
func (c CfSecurityGroupResource) Update(d *schema.ResourceData, meta interface{}) error {
	client := meta.(cf_client.Client)
	secGroup := c.resourceObject(d)
	secGroupCf, err := c.getSecGroupFromCf(client, d.Id(), false)
	if err != nil {
		return err
	}
	if secGroupCf.GUID == "" {
		log.Printf(
			"[WARN] removing security group %s/%s from state because it no longer exists in your Cloud Foundry",
			client.Config().ApiEndpoint,
			secGroup.Name,
		)
		d.SetId("")
		return nil
	}
	if c.isRulesChange(secGroupCf.Rules, secGroup.Rules) {
		client.SecurityGroups().Update(d.Id(), secGroup.Rules)
	}
	isOnStaging, err := c.isOnStaging(client, d.Id())
	if err != nil {
		return err
	}
	if d.Get("on_staging").(bool) != isOnStaging {
		err = c.updateBindingStaging(client, d.Id(), d.Get("on_staging").(bool))
		if err != nil {
			return err
		}
	}
	if d.Get("on_running").(bool) != isOnStaging {
		err = c.updateBindingStaging(client, d.Id(), d.Get("on_running").(bool))
		if err != nil {
			return err
		}
	}
	return nil
}
func (c CfSecurityGroupResource) Delete(d *schema.ResourceData, meta interface{}) error {
	client := meta.(cf_client.Client)
	return client.SecurityGroups().Delete(d.Id())
}
func (c CfSecurityGroupResource) updateBindingStaging(client cf_client.Client, guid string, onStaging bool) error {
	if onStaging {
		return client.SecurityGroupsStagingBinder().BindToStagingSet(guid)
	}
	return client.SecurityGroupsStagingBinder().UnbindFromStagingSet(guid)
}
func (c CfSecurityGroupResource) updateBindingRunning(client cf_client.Client, guid string, onRunning bool) error {
	if onRunning {
		return client.SecurityGroupsRunningBinder().BindToRunningSet(guid)
	}
	return client.SecurityGroupsRunningBinder().UnbindFromRunningSet(guid)
}
func (c CfSecurityGroupResource) isRulesChange(rulesFrom, rulesTo []map[string]interface{}) bool {
	if rulesFrom == nil && rulesTo == nil {
		return false;
	}
	if rulesFrom == nil || rulesTo == nil {
		return true;
	}
	if len(rulesFrom) != len(rulesTo) {
		return true
	}
	for i := range rulesFrom {
		if !reflect.DeepEqual(rulesFrom[i], rulesTo[i]) {
			return true
		}
	}
	for i := range rulesTo {
		if !reflect.DeepEqual(rulesFrom[i], rulesTo[i]) {
			return true
		}
	}
	return false

}
func (c CfSecurityGroupResource) isOnStaging(client cf_client.Client, secGroupId string) (bool, error) {
	secGroups, err := client.SecurityGroupsStagingBinder().List()
	if err != nil {
		return false, err
	}
	return c.existsSecurityGroup(secGroups, secGroupId), nil
}
func (c CfSecurityGroupResource) isOnRunning(client cf_client.Client, secGroupId string) (bool, error) {
	secGroups, err := client.SecurityGroupsRunningBinder().List()
	if err != nil {
		return false, err
	}
	return c.existsSecurityGroup(secGroups, secGroupId), nil
}
func (c CfSecurityGroupResource) existsSecurityGroup(secGroups []models.SecurityGroupFields, secGroupId string) bool {
	for _, secGroup := range secGroups {
		if secGroup.GUID == secGroupId {
			return true
		}
	}
	return false
}
func (c CfSecurityGroupResource) getSecGroupFromCf(client cf_client.Client, secGroupId string, updateCache bool) (models.SecurityGroupFields, error) {
	secGroups, err := caching.GetSecGroupsFromCf(client, updateCache)
	if err != nil {
		return models.SecurityGroupFields{}, err
	}
	for _, secGroup := range secGroups {
		if secGroup.GUID == secGroupId {
			return secGroup.SecurityGroupFields, nil
		}
	}
	return models.SecurityGroupFields{}, nil
}
func (c CfSecurityGroupResource) Exists(d *schema.ResourceData, meta interface{}) (bool, error) {
	client := meta.(cf_client.Client)
	name := d.Get("name").(string)
	secGroups, err := caching.GetSecGroupsFromCf(client, true)
	if err != nil {
		return false, err
	}
	for _, secGroup := range secGroups {
		if secGroup.Name == name {
			d.SetId(secGroup.GUID)
			return true, nil
		}
	}

	return false, nil
}

func (c CfSecurityGroupResource) Schema() map[string]*schema.Schema {
	return map[string]*schema.Schema{
		"name": &schema.Schema{
			Type:     schema.TypeString,
			Required: true,
			ForceNew: true,
		},
		"rules": &schema.Schema{
			Type:     schema.TypeSet,
			Required: true,

			Elem:     &schema.Resource{
				Schema: map[string]*schema.Schema{
					"protocol": &schema.Schema{
						Type:     schema.TypeString,
						Required: true,
						ValidateFunc: func(elem interface{}, index string) ([]string, []error) {
							prot := elem.(string)
							found := false
							for _, validProt := range validProtocoles {
								if validProt == prot {
									found = true
									break
								}
							}
							if found {
								return make([]string, 0), make([]error, 0)
							}
							errMsg := fmt.Sprintf(
								"Protocol '%s' is not valid, it must be one of %s",
								prot,
								strings.Join(validProtocoles, ", "),
							)
							err := errors.New(errMsg)
							return make([]string, 0), []error{err}
						},
					},
					"destination": &schema.Schema{
						Type:     schema.TypeString,
						Optional: true,
					},
					"description": &schema.Schema{
						Type:     schema.TypeString,
						Optional: true,
					},
					"ports": &schema.Schema{
						Type:     schema.TypeString,
						Optional: true,
						ValidateFunc: func(elem interface{}, index string) ([]string, []error) {
							ports := elem.(string)
							match, _ := regexp.MatchString("^[0-9][0-9-,]*[0-9]?$", ports)
							if match {
								return make([]string, 0), make([]error, 0)
							}
							errMsg := fmt.Sprintf(
								"Ports '%s' is not valid. (valid examples: '443', '80,8080,8081', '8080-8081')",
								ports,
							)
							err := errors.New(errMsg)
							return make([]string, 0), []error{err}
						},
					},
					"code": &schema.Schema{
						Type:     schema.TypeInt,
						Optional: true,
					},
					"type": &schema.Schema{
						Type:     schema.TypeInt,
						Optional: true,
					},
					"log": &schema.Schema{
						Type:     schema.TypeBool,
						Default: true,
						Optional: true,
					},
				},
			},
			Set: func(v interface{}) int {
				var buf bytes.Buffer
				m := v.(map[string]interface{})
				buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string)))
				buf.WriteString(fmt.Sprintf("%s-", m["destination"].(string)))
				buf.WriteString(fmt.Sprintf("%s-", m["description"].(string)))
				buf.WriteString(fmt.Sprintf("%s-", m["ports"].(string)))
				buf.WriteString(fmt.Sprintf("%s-", strconv.Itoa(m["code"].(int))))
				buf.WriteString(fmt.Sprintf("%s-", strconv.Itoa(m["type"].(int))))
				buf.WriteString(fmt.Sprintf("%s-", strconv.FormatBool(m["log"].(bool))))
				return hashcode.String(buf.String())
			},
		},
		"on_staging": &schema.Schema{
			Type:     schema.TypeBool,
			Optional: true,
		},
		"on_running": &schema.Schema{
			Type:     schema.TypeBool,
			Optional: true,
		},
	}
}

<|endoftext|>


<commit_before>package responsebody // import "go.delic.rs/cliware-middlewares/responsebody"

import (
	"encoding/json"
	"io/ioutil"
	"net/http"

	"io"

	c "go.delic.rs/cliware"
)

// JSON decodes response body from JSON format into provided interface.
func JSON(data interface{}) c.Middleware {
	return c.ResponseProcessor(func(resp *http.Response, err error) error {
		// TODO: Should we check for Content-Type header here?
		if err != nil {
			return err
		}
		defer resp.Body.Close()
		rawData, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		return json.Unmarshal(rawData, data)
	})
}

// String reads response body, converts it to string and writes it to provided
// string pointer.
func String(data *string) c.Middleware {
	return c.ResponseProcessor(func(resp *http.Response, err error) error {
		if err != nil {
			return err
		}
		defer resp.Body.Close()
		rawData, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		*data = string(rawData)
		return nil
	})
}

// Writer reads response body and writes it to provided writer.
func Writer(w io.Writer) c.Middleware {
	return c.ResponseProcessor(func(resp *http.Response, err error) error {
		if err != nil {
			return err
		}
		defer resp.Body.Close()
		if _, err = io.Copy(w, resp.Body); err != nil {
			return err
		}
		return nil
	})
}
<commit_msg>Short docstring for responsebody package.<commit_after>// Package responsebody contains middlewares for reading body of HTTP response in different ways.
package responsebody // import "go.delic.rs/cliware-middlewares/responsebody"

import (
	"encoding/json"
	"io/ioutil"
	"net/http"

	"io"

	c "go.delic.rs/cliware"
)

// JSON decodes response body from JSON format into provided interface.
func JSON(data interface{}) c.Middleware {
	return c.ResponseProcessor(func(resp *http.Response, err error) error {
		// TODO: Should we check for Content-Type header here?
		if err != nil {
			return err
		}
		defer resp.Body.Close()
		rawData, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		return json.Unmarshal(rawData, data)
	})
}

// String reads response body, converts it to string and writes it to provided
// string pointer.
func String(data *string) c.Middleware {
	return c.ResponseProcessor(func(resp *http.Response, err error) error {
		if err != nil {
			return err
		}
		defer resp.Body.Close()
		rawData, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		*data = string(rawData)
		return nil
	})
}

// Writer reads response body and writes it to provided writer.
func Writer(w io.Writer) c.Middleware {
	return c.ResponseProcessor(func(resp *http.Response, err error) error {
		if err != nil {
			return err
		}
		defer resp.Body.Close()
		if _, err = io.Copy(w, resp.Body); err != nil {
			return err
		}
		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 framework

import (
	"context"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"k8s.io/klog"

	"k8s.io/kubernetes/pkg/util/env"
)

var etcdURL = ""

const installEtcd = `
Cannot find etcd, cannot run integration tests
Please see https://git.k8s.io/community/contributors/devel/sig-testing/testing.md#install-etcd-dependency for instructions.

You can use 'hack/install-etcd.sh' to install a copy in third_party/.

`

// getEtcdPath returns a path to an etcd executable.
func getEtcdPath() (string, error) {
	bazelPath := filepath.Join(os.Getenv("RUNFILES_DIR"), "com_coreos_etcd/etcd")
	p, err := exec.LookPath(bazelPath)
	if err == nil {
		return p, nil
	}
	return exec.LookPath("etcd")
}

// getAvailablePort returns a TCP port that is available for binding.
func getAvailablePort() (int, error) {
	l, err := net.Listen("tcp", ":0")
	if err != nil {
		return 0, fmt.Errorf("could not bind to a port: %v", err)
	}
	// It is possible but unlikely that someone else will bind this port before we
	// get a chance to use it.
	defer l.Close()
	return l.Addr().(*net.TCPAddr).Port, nil
}

// startEtcd executes an etcd instance. The returned function will signal the
// etcd process and wait for it to exit.
func startEtcd() (func(), error) {
	etcdURL = env.GetEnvAsStringOrFallback("KUBE_INTEGRATION_ETCD_URL", "http://127.0.0.1:2379")
	conn, err := net.Dial("tcp", strings.TrimPrefix(etcdURL, "http://"))
	if err == nil {
		klog.Infof("etcd already running at %s", etcdURL)
		conn.Close()
		return func() {}, nil
	}
	klog.V(1).Infof("could not connect to etcd: %v", err)

	// TODO: Check for valid etcd version.
	etcdPath, err := getEtcdPath()
	if err != nil {
		fmt.Fprintf(os.Stderr, installEtcd)
		return nil, fmt.Errorf("could not find etcd in PATH: %v", err)
	}
	etcdPort, err := getAvailablePort()
	if err != nil {
		return nil, fmt.Errorf("could not get a port: %v", err)
	}
	etcdURL = fmt.Sprintf("http://127.0.0.1:%d", etcdPort)
	klog.Infof("starting etcd on %s", etcdURL)

	etcdDataDir, err := ioutil.TempDir(os.TempDir(), "integration_test_etcd_data")
	if err != nil {
		return nil, fmt.Errorf("unable to make temp etcd data dir: %v", err)
	}
	klog.Infof("storing etcd data in: %v", etcdDataDir)

	ctx, cancel := context.WithCancel(context.Background())
	cmd := exec.CommandContext(
		ctx,
		etcdPath,
		"--data-dir",
		etcdDataDir,
		"--listen-client-urls",
		GetEtcdURL(),
		"--advertise-client-urls",
		GetEtcdURL(),
		"--listen-peer-urls",
		"http://127.0.0.1:0",
		"--log-package-levels",
		"*=DEBUG",
	)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	stop := func() {
		cancel()
		err := cmd.Wait()
		klog.Infof("etcd exit status: %v", err)
		err = os.RemoveAll(etcdDataDir)
		if err != nil {
			klog.Warningf("error during etcd cleanup: %v", err)
		}
	}

	if err := cmd.Start(); err != nil {
		return nil, fmt.Errorf("failed to run etcd: %v", err)
	}
	return stop, nil
}

// EtcdMain starts an etcd instance before running tests.
func EtcdMain(tests func() int) {
	stop, err := startEtcd()
	if err != nil {
		klog.Fatalf("cannot run integration tests: unable to start etcd: %v", err)
	}
	result := tests()
	stop() // Don't defer this. See os.Exit documentation.
	os.Exit(result)
}

// GetEtcdURL returns the URL of the etcd instance started by EtcdMain.
func GetEtcdURL() string {
	return etcdURL
}
<commit_msg>Add an environment variable for bazel-test-integration on Arm<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 framework

import (
	"context"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"k8s.io/klog"

	"k8s.io/kubernetes/pkg/util/env"
)

var etcdURL = ""

const installEtcd = `
Cannot find etcd, cannot run integration tests
Please see https://git.k8s.io/community/contributors/devel/sig-testing/testing.md#install-etcd-dependency for instructions.

You can use 'hack/install-etcd.sh' to install a copy in third_party/.

`

// getEtcdPath returns a path to an etcd executable.
func getEtcdPath() (string, error) {
	bazelPath := filepath.Join(os.Getenv("RUNFILES_DIR"), "com_coreos_etcd/etcd")
	p, err := exec.LookPath(bazelPath)
	if err == nil {
		return p, nil
	}
	return exec.LookPath("etcd")
}

// getAvailablePort returns a TCP port that is available for binding.
func getAvailablePort() (int, error) {
	l, err := net.Listen("tcp", ":0")
	if err != nil {
		return 0, fmt.Errorf("could not bind to a port: %v", err)
	}
	// It is possible but unlikely that someone else will bind this port before we
	// get a chance to use it.
	defer l.Close()
	return l.Addr().(*net.TCPAddr).Port, nil
}

// startEtcd executes an etcd instance. The returned function will signal the
// etcd process and wait for it to exit.
func startEtcd() (func(), error) {
	if runtime.GOARCH == "arm64" {
		os.Setenv("ETCD_UNSUPPORTED_ARCH", "arm64")
	}

	etcdURL = env.GetEnvAsStringOrFallback("KUBE_INTEGRATION_ETCD_URL", "http://127.0.0.1:2379")
	conn, err := net.Dial("tcp", strings.TrimPrefix(etcdURL, "http://"))
	if err == nil {
		klog.Infof("etcd already running at %s", etcdURL)
		conn.Close()
		return func() {}, nil
	}
	klog.V(1).Infof("could not connect to etcd: %v", err)

	// TODO: Check for valid etcd version.
	etcdPath, err := getEtcdPath()
	if err != nil {
		fmt.Fprintf(os.Stderr, installEtcd)
		return nil, fmt.Errorf("could not find etcd in PATH: %v", err)
	}
	etcdPort, err := getAvailablePort()
	if err != nil {
		return nil, fmt.Errorf("could not get a port: %v", err)
	}
	etcdURL = fmt.Sprintf("http://127.0.0.1:%d", etcdPort)
	klog.Infof("starting etcd on %s", etcdURL)

	etcdDataDir, err := ioutil.TempDir(os.TempDir(), "integration_test_etcd_data")
	if err != nil {
		return nil, fmt.Errorf("unable to make temp etcd data dir: %v", err)
	}
	klog.Infof("storing etcd data in: %v", etcdDataDir)

	ctx, cancel := context.WithCancel(context.Background())
	cmd := exec.CommandContext(
		ctx,
		etcdPath,
		"--data-dir",
		etcdDataDir,
		"--listen-client-urls",
		GetEtcdURL(),
		"--advertise-client-urls",
		GetEtcdURL(),
		"--listen-peer-urls",
		"http://127.0.0.1:0",
		"--log-package-levels",
		"*=DEBUG",
	)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	stop := func() {
		cancel()
		err := cmd.Wait()
		klog.Infof("etcd exit status: %v", err)
		err = os.RemoveAll(etcdDataDir)
		if err != nil {
			klog.Warningf("error during etcd cleanup: %v", err)
		}
	}

	if err := cmd.Start(); err != nil {
		return nil, fmt.Errorf("failed to run etcd: %v", err)
	}
	return stop, nil
}

// EtcdMain starts an etcd instance before running tests.
func EtcdMain(tests func() int) {
	stop, err := startEtcd()
	if err != nil {
		klog.Fatalf("cannot run integration tests: unable to start etcd: %v", err)
	}
	result := tests()
	stop() // Don't defer this. See os.Exit documentation.
	os.Exit(result)
}

// GetEtcdURL returns the URL of the etcd instance started by EtcdMain.
func GetEtcdURL() string {
	return etcdURL
}
<|endoftext|>


<commit_before>// Package reverseproxy is a reverse proxy implementation based on the built-in
// httuptil.Reverseproxy. Extensions include better logging and support for
// injection.
package reverseproxy

import (
	"fmt"
	"io"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"sync"
	"time"

	"golang.org/x/net/context"

	"github.com/cortesi/devd/inject"
	"github.com/cortesi/termlog"
	"github.com/dustin/go-humanize"
)

// onExitFlushLoop is a callback set by tests to detect the state of the
// flushLoop() goroutine.
var onExitFlushLoop func()

// ReverseProxy is an HTTP Handler that takes an incoming request and
// sends it to another server, proxying the response back to the
// client.
type ReverseProxy struct {
	// Director must be a function which modifies
	// the request into a new request to be sent
	// using Transport. Its response is then copied
	// back to the original client unmodified.
	Director func(*http.Request)

	// The transport used to perform proxy requests.
	// If nil, http.DefaultTransport is used.
	Transport http.RoundTripper

	// FlushInterval specifies the flush interval
	// to flush to the client while copying the
	// response body.
	// If zero, no periodic flushing is done.
	FlushInterval time.Duration

	Inject inject.CopyInject
}

func singleJoiningSlash(a, b string) string {
	if b == "" {
		return a
	}

	aslash := strings.HasSuffix(a, "/")
	bslash := strings.HasPrefix(b, "/")
	switch {
	case aslash && bslash:
		return a + b[1:]
	case !aslash && !bslash:
		return a + "/" + b
	}
	return a + b
}

// NewSingleHostReverseProxy returns a new ReverseProxy that rewrites
// URLs to the scheme, host, and base path provided in target. If the
// target's path is "/base" and the incoming request was for "/dir",
// the target request will be for /base/dir.
func NewSingleHostReverseProxy(target *url.URL, ci inject.CopyInject) *ReverseProxy {
	targetQuery := target.RawQuery
	director := func(req *http.Request) {
		req.URL.Scheme = target.Scheme
		req.URL.Host = target.Host
		req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
		if req.Header.Get("X-Forwarded-Host") == "" {
			req.Header.Set("X-Forwarded-Host", req.Host)
		}
		if req.Header.Get("X-Forwarded-Proto") == "" {
			req.Header.Set("X-Forwarded-Prot", req.URL.Scheme)
		}

		// Set "identity"-only content encoding, in order for injector to
		// work on text response
		req.Header.Set("Accept-Encoding", "identity")

		req.Host = req.URL.Host
		if targetQuery == "" || req.URL.RawQuery == "" {
			req.URL.RawQuery = targetQuery + req.URL.RawQuery
		} else {
			req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
		}
	}
	return &ReverseProxy{Director: director, Inject: ci}
}

func copyHeader(dst, src http.Header) {
	for k, vv := range src {
		for _, v := range vv {
			dst.Add(k, v)
		}
	}
}

// Hop-by-hop headers. These are removed when sent to the backend.
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
var hopHeaders = []string{
	"Connection",
	"Keep-Alive",
	"Proxy-Authenticate",
	"Proxy-Authorization",
	"Te", // canonicalized version of "TE"
	"Trailers",
	"Transfer-Encoding",
	"Upgrade",
}

// ServeHTTPContext serves HTTP with a context
func (p *ReverseProxy) ServeHTTPContext(
	ctx context.Context, rw http.ResponseWriter, req *http.Request,
) {
	log := termlog.FromContext(ctx)
	transport := p.Transport
	if transport == nil {
		transport = http.DefaultTransport
	}

	outreq := new(http.Request)
	*outreq = *req // includes shallow copies of maps, but okay

	p.Director(outreq)
	outreq.Proto = "HTTP/1.1"
	outreq.ProtoMajor = 1
	outreq.ProtoMinor = 1
	outreq.Close = false

	// Remove hop-by-hop headers to the backend.  Especially
	// important is "Connection" because we want a persistent
	// connection, regardless of what the client sent to us.  This
	// is modifying the same underlying map from req (shallow
	// copied above) so we only copy it if necessary.
	copiedHeaders := false
	for _, h := range hopHeaders {
		if outreq.Header.Get(h) != "" {
			if !copiedHeaders {
				outreq.Header = make(http.Header)
				copyHeader(outreq.Header, req.Header)
				copiedHeaders = true
			}
			outreq.Header.Del(h)
		}
	}

	if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
		// If we aren't the first proxy retain prior
		// X-Forwarded-For information as a comma+space
		// separated list and fold multiple headers into one.
		if prior, ok := outreq.Header["X-Forwarded-For"]; ok {
			clientIP = strings.Join(prior, ", ") + ", " + clientIP
		}
		outreq.Header.Set("X-Forwarded-For", clientIP)
	}

	res, err := transport.RoundTrip(outreq)
	if err != nil {
		log.Shout("reverse proxy error: %v", err)
		rw.WriteHeader(http.StatusInternalServerError)
		return
	}
	defer res.Body.Close()
	if req.ContentLength > 0 {
		log.Say(fmt.Sprintf("%s uploaded", humanize.Bytes(uint64(req.ContentLength))))
	}

	inject, err := p.Inject.Sniff(res.Body)
	if err != nil {
		log.Shout("reverse proxy error: %v", err)
		rw.WriteHeader(http.StatusInternalServerError)
		return
	}

	if inject.Found {
		cl, err := strconv.ParseInt(res.Header.Get("Content-Length"), 10, 32)
		if err == nil {
			cl = cl + int64(inject.Extra())
			res.Header.Set("Content-Length", strconv.FormatInt(cl, 10))
		}
	}
	copyHeader(rw.Header(), res.Header)
	rw.WriteHeader(res.StatusCode)
	p.copyResponse(ctx, rw, inject)
}

func (p *ReverseProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	p.ServeHTTPContext(context.Background(), w, r)
}

func (p *ReverseProxy) copyResponse(ctx context.Context, dst io.Writer, inject *inject.Injector) {
	log := termlog.FromContext(ctx)
	if p.FlushInterval != 0 {
		if wf, ok := dst.(writeFlusher); ok {
			mlw := &maxLatencyWriter{
				dst:     wf,
				latency: p.FlushInterval,
				done:    make(chan bool),
			}
			go mlw.flushLoop()
			defer mlw.stop()
			dst = mlw
		}
	}
	_, err := inject.Copy(dst)
	if err != nil {
		log.Shout("Error forwarding data: %s", err)
	}
}

type writeFlusher interface {
	io.Writer
	http.Flusher
}

type maxLatencyWriter struct {
	sync.Mutex // protects Write + Flush

	dst     writeFlusher
	latency time.Duration

	done chan bool
}

func (m *maxLatencyWriter) Write(p []byte) (int, error) {
	m.Lock()
	defer m.Unlock()
	return m.dst.Write(p)
}

func (m *maxLatencyWriter) flushLoop() {
	t := time.NewTicker(m.latency)
	defer t.Stop()
	for {
		select {
		case <-m.done:
			if onExitFlushLoop != nil {
				onExitFlushLoop()
			}
			return
		case <-t.C:
			m.Lock()
			m.dst.Flush()
			m.Unlock()
		}
	}
}

func (m *maxLatencyWriter) stop() { m.done <- true }
<commit_msg>Fix typo in 2nd "X-Forwarded-Proto" mention.<commit_after>// Package reverseproxy is a reverse proxy implementation based on the built-in
// httuptil.Reverseproxy. Extensions include better logging and support for
// injection.
package reverseproxy

import (
	"fmt"
	"io"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"sync"
	"time"

	"golang.org/x/net/context"

	"github.com/cortesi/devd/inject"
	"github.com/cortesi/termlog"
	"github.com/dustin/go-humanize"
)

// onExitFlushLoop is a callback set by tests to detect the state of the
// flushLoop() goroutine.
var onExitFlushLoop func()

// ReverseProxy is an HTTP Handler that takes an incoming request and
// sends it to another server, proxying the response back to the
// client.
type ReverseProxy struct {
	// Director must be a function which modifies
	// the request into a new request to be sent
	// using Transport. Its response is then copied
	// back to the original client unmodified.
	Director func(*http.Request)

	// The transport used to perform proxy requests.
	// If nil, http.DefaultTransport is used.
	Transport http.RoundTripper

	// FlushInterval specifies the flush interval
	// to flush to the client while copying the
	// response body.
	// If zero, no periodic flushing is done.
	FlushInterval time.Duration

	Inject inject.CopyInject
}

func singleJoiningSlash(a, b string) string {
	if b == "" {
		return a
	}

	aslash := strings.HasSuffix(a, "/")
	bslash := strings.HasPrefix(b, "/")
	switch {
	case aslash && bslash:
		return a + b[1:]
	case !aslash && !bslash:
		return a + "/" + b
	}
	return a + b
}

// NewSingleHostReverseProxy returns a new ReverseProxy that rewrites
// URLs to the scheme, host, and base path provided in target. If the
// target's path is "/base" and the incoming request was for "/dir",
// the target request will be for /base/dir.
func NewSingleHostReverseProxy(target *url.URL, ci inject.CopyInject) *ReverseProxy {
	targetQuery := target.RawQuery
	director := func(req *http.Request) {
		req.URL.Scheme = target.Scheme
		req.URL.Host = target.Host
		req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
		if req.Header.Get("X-Forwarded-Host") == "" {
			req.Header.Set("X-Forwarded-Host", req.Host)
		}
		if req.Header.Get("X-Forwarded-Proto") == "" {
			req.Header.Set("X-Forwarded-Proto", req.URL.Scheme)
		}

		// Set "identity"-only content encoding, in order for injector to
		// work on text response
		req.Header.Set("Accept-Encoding", "identity")

		req.Host = req.URL.Host
		if targetQuery == "" || req.URL.RawQuery == "" {
			req.URL.RawQuery = targetQuery + req.URL.RawQuery
		} else {
			req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
		}
	}
	return &ReverseProxy{Director: director, Inject: ci}
}

func copyHeader(dst, src http.Header) {
	for k, vv := range src {
		for _, v := range vv {
			dst.Add(k, v)
		}
	}
}

// Hop-by-hop headers. These are removed when sent to the backend.
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
var hopHeaders = []string{
	"Connection",
	"Keep-Alive",
	"Proxy-Authenticate",
	"Proxy-Authorization",
	"Te", // canonicalized version of "TE"
	"Trailers",
	"Transfer-Encoding",
	"Upgrade",
}

// ServeHTTPContext serves HTTP with a context
func (p *ReverseProxy) ServeHTTPContext(
	ctx context.Context, rw http.ResponseWriter, req *http.Request,
) {
	log := termlog.FromContext(ctx)
	transport := p.Transport
	if transport == nil {
		transport = http.DefaultTransport
	}

	outreq := new(http.Request)
	*outreq = *req // includes shallow copies of maps, but okay

	p.Director(outreq)
	outreq.Proto = "HTTP/1.1"
	outreq.ProtoMajor = 1
	outreq.ProtoMinor = 1
	outreq.Close = false

	// Remove hop-by-hop headers to the backend.  Especially
	// important is "Connection" because we want a persistent
	// connection, regardless of what the client sent to us.  This
	// is modifying the same underlying map from req (shallow
	// copied above) so we only copy it if necessary.
	copiedHeaders := false
	for _, h := range hopHeaders {
		if outreq.Header.Get(h) != "" {
			if !copiedHeaders {
				outreq.Header = make(http.Header)
				copyHeader(outreq.Header, req.Header)
				copiedHeaders = true
			}
			outreq.Header.Del(h)
		}
	}

	if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
		// If we aren't the first proxy retain prior
		// X-Forwarded-For information as a comma+space
		// separated list and fold multiple headers into one.
		if prior, ok := outreq.Header["X-Forwarded-For"]; ok {
			clientIP = strings.Join(prior, ", ") + ", " + clientIP
		}
		outreq.Header.Set("X-Forwarded-For", clientIP)
	}

	res, err := transport.RoundTrip(outreq)
	if err != nil {
		log.Shout("reverse proxy error: %v", err)
		rw.WriteHeader(http.StatusInternalServerError)
		return
	}
	defer res.Body.Close()
	if req.ContentLength > 0 {
		log.Say(fmt.Sprintf("%s uploaded", humanize.Bytes(uint64(req.ContentLength))))
	}

	inject, err := p.Inject.Sniff(res.Body)
	if err != nil {
		log.Shout("reverse proxy error: %v", err)
		rw.WriteHeader(http.StatusInternalServerError)
		return
	}

	if inject.Found {
		cl, err := strconv.ParseInt(res.Header.Get("Content-Length"), 10, 32)
		if err == nil {
			cl = cl + int64(inject.Extra())
			res.Header.Set("Content-Length", strconv.FormatInt(cl, 10))
		}
	}
	copyHeader(rw.Header(), res.Header)
	rw.WriteHeader(res.StatusCode)
	p.copyResponse(ctx, rw, inject)
}

func (p *ReverseProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	p.ServeHTTPContext(context.Background(), w, r)
}

func (p *ReverseProxy) copyResponse(ctx context.Context, dst io.Writer, inject *inject.Injector) {
	log := termlog.FromContext(ctx)
	if p.FlushInterval != 0 {
		if wf, ok := dst.(writeFlusher); ok {
			mlw := &maxLatencyWriter{
				dst:     wf,
				latency: p.FlushInterval,
				done:    make(chan bool),
			}
			go mlw.flushLoop()
			defer mlw.stop()
			dst = mlw
		}
	}
	_, err := inject.Copy(dst)
	if err != nil {
		log.Shout("Error forwarding data: %s", err)
	}
}

type writeFlusher interface {
	io.Writer
	http.Flusher
}

type maxLatencyWriter struct {
	sync.Mutex // protects Write + Flush

	dst     writeFlusher
	latency time.Duration

	done chan bool
}

func (m *maxLatencyWriter) Write(p []byte) (int, error) {
	m.Lock()
	defer m.Unlock()
	return m.dst.Write(p)
}

func (m *maxLatencyWriter) flushLoop() {
	t := time.NewTicker(m.latency)
	defer t.Stop()
	for {
		select {
		case <-m.done:
			if onExitFlushLoop != nil {
				onExitFlushLoop()
			}
			return
		case <-t.C:
			m.Lock()
			m.dst.Flush()
			m.Unlock()
		}
	}
}

func (m *maxLatencyWriter) stop() { m.done <- true }
<|endoftext|>


<commit_before>package endpoint

import (
	"strconv"

	log "github.com/Sirupsen/logrus"
	"github.com/weaveworks/scope/probe/endpoint/procspy"
	"github.com/weaveworks/scope/probe/process"
	"github.com/weaveworks/scope/report"
)

// connectionTrackerConfig are the config options for the endpoint tracker.
type connectionTrackerConfig struct {
	HostID       string
	HostName     string
	SpyProcs     bool
	UseConntrack bool
	WalkProc     bool
	UseEbpfConn  bool
	ProcRoot     string
	BufferSize   int
	ProcessCache *process.CachingWalker
	Scanner      procspy.ConnectionScanner
	DNSSnooper   *DNSSnooper
}

type connectionTracker struct {
	conf            connectionTrackerConfig
	flowWalker      flowWalker // Interface
	ebpfTracker     eventTracker
	reverseResolver *reverseResolver
}

func newConnectionTracker(conf connectionTrackerConfig) connectionTracker {
	ct := connectionTracker{
		conf:            conf,
		reverseResolver: newReverseResolver(),
	}
	if conf.UseEbpfConn {
		et, err := newEbpfTracker()
		if err == nil {
			ct.ebpfTracker = et
			go ct.getInitialState()
			return ct
		}
		log.Warnf("Error setting up the eBPF tracker, falling back to proc scanning: %v", err)
	}
	ct.useProcfs()
	return ct
}

func flowToTuple(f flow) (ft fourTuple) {
	ft = fourTuple{
		f.Original.Layer3.SrcIP,
		f.Original.Layer3.DstIP,
		uint16(f.Original.Layer4.SrcPort),
		uint16(f.Original.Layer4.DstPort),
	}
	// Handle DNAT-ed connections in the initial state
	if f.Original.Layer3.DstIP != f.Reply.Layer3.SrcIP {
		ft = fourTuple{
			f.Reply.Layer3.DstIP,
			f.Reply.Layer3.SrcIP,
			uint16(f.Reply.Layer4.DstPort),
			uint16(f.Reply.Layer4.SrcPort),
		}
	}
	return ft
}

func (t *connectionTracker) useProcfs() {
	t.ebpfTracker = nil
	if t.conf.WalkProc && t.conf.Scanner == nil {
		t.conf.Scanner = procspy.NewConnectionScanner(t.conf.ProcessCache, t.conf.SpyProcs)
	}
	if t.flowWalker == nil {
		t.flowWalker = newConntrackFlowWalker(t.conf.UseConntrack, t.conf.ProcRoot, t.conf.BufferSize)
	}
}

// ReportConnections calls trackers according to the configuration.
func (t *connectionTracker) ReportConnections(rpt *report.Report) {
	hostNodeID := report.MakeHostNodeID(t.conf.HostID)

	if t.ebpfTracker != nil {
		if !t.ebpfTracker.isDead() {
			t.performEbpfTrack(rpt, hostNodeID)
			return
		}
		log.Warnf("ebpf tracker died, gently falling back to proc scanning")
		t.useProcfs()
	}

	// seenTuples contains information about connections seen by conntrack and it will be passed to the /proc parser
	seenTuples := map[string]fourTuple{}
	t.performFlowWalk(rpt, seenTuples)
	if t.conf.WalkProc && t.conf.Scanner != nil {
		t.performWalkProc(rpt, hostNodeID, seenTuples)
	}
}

func (t *connectionTracker) performFlowWalk(rpt *report.Report, seenTuples map[string]fourTuple) {
	// Consult the flowWalker for short-lived connections
	extraNodeInfo := map[string]string{
		Conntracked: "true",
	}
	t.flowWalker.walkFlows(func(f flow, alive bool) {
		tuple := flowToTuple(f)
		seenTuples[tuple.key()] = tuple
		t.addConnection(rpt, tuple, "", extraNodeInfo, extraNodeInfo)
	})
}

func (t *connectionTracker) performWalkProc(rpt *report.Report, hostNodeID string, seenTuples map[string]fourTuple) error {
	conns, err := t.conf.Scanner.Connections()
	if err != nil {
		return err
	}
	for conn := conns.Next(); conn != nil; conn = conns.Next() {
		tuple, namespaceID, incoming := connectionTuple(conn, seenTuples)
		var (
			toNodeInfo   = map[string]string{Procspied: "true"}
			fromNodeInfo = map[string]string{Procspied: "true"}
		)
		if conn.Proc.PID > 0 {
			fromNodeInfo[process.PID] = strconv.FormatUint(uint64(conn.Proc.PID), 10)
			fromNodeInfo[report.HostNodeID] = hostNodeID
		}
		if incoming {
			tuple.reverse()
			toNodeInfo, fromNodeInfo = fromNodeInfo, toNodeInfo
		}
		t.addConnection(rpt, tuple, namespaceID, fromNodeInfo, toNodeInfo)
	}
	return nil
}

// getInitialState runs conntrack and proc parsing synchronously only
// once to initialize ebpfTracker
func (t *connectionTracker) getInitialState() {
	var processCache *process.CachingWalker
	walker := process.NewWalker(t.conf.ProcRoot, true)
	processCache = process.NewCachingWalker(walker)
	processCache.Tick()

	scanner := procspy.NewSyncConnectionScanner(processCache, t.conf.SpyProcs)
	seenTuples := map[string]fourTuple{}
	// Consult the flowWalker to get the initial state
	if err := IsConntrackSupported(t.conf.ProcRoot); t.conf.UseConntrack && err != nil {
		log.Warnf("Not using conntrack: not supported by the kernel: %s", err)
	} else if existingFlows, err := existingConnections([]string{"--any-nat"}); err != nil {
		log.Errorf("conntrack existingConnections error: %v", err)
	} else {
		for _, f := range existingFlows {
			tuple := flowToTuple(f)
			seenTuples[tuple.key()] = tuple
		}
	}

	conns, err := scanner.Connections()
	if err != nil {
		log.Errorf("Error initializing ebpfTracker while scanning /proc, continuing without initial connections: %s", err)
	}
	scanner.Stop()

	processesWaitingInAccept := []int{}
	processCache.Walk(func(p, prev process.Process) {
		if p.IsWaitingInAccept {
			processesWaitingInAccept = append(processesWaitingInAccept, p.PID)
		}
	})

	t.ebpfTracker.feedInitialConnections(conns, seenTuples, processesWaitingInAccept, report.MakeHostNodeID(t.conf.HostID))
}

func (t *connectionTracker) performEbpfTrack(rpt *report.Report, hostNodeID string) error {
	t.ebpfTracker.walkConnections(func(e ebpfConnection) {
		fromNodeInfo := map[string]string{
			EBPF: "true",
		}
		toNodeInfo := map[string]string{
			EBPF: "true",
		}
		if e.pid > 0 {
			fromNodeInfo[process.PID] = strconv.Itoa(e.pid)
			fromNodeInfo[report.HostNodeID] = hostNodeID
		}

		if e.incoming {
			t.addConnection(rpt, reverse(e.tuple), e.networkNamespace, toNodeInfo, fromNodeInfo)
		} else {
			t.addConnection(rpt, e.tuple, e.networkNamespace, fromNodeInfo, toNodeInfo)
		}

	})
	return nil
}

func (t *connectionTracker) addConnection(rpt *report.Report, ft fourTuple, namespaceID string, extraFromNode, extraToNode map[string]string) {
	var (
		fromNode = t.makeEndpointNode(namespaceID, ft.fromAddr, ft.fromPort, extraFromNode)
		toNode   = t.makeEndpointNode(namespaceID, ft.toAddr, ft.toPort, extraToNode)
	)
	rpt.Endpoint = rpt.Endpoint.AddNode(fromNode.WithEdge(toNode.ID, report.EdgeMetadata{}))
	rpt.Endpoint = rpt.Endpoint.AddNode(toNode)
}

func (t *connectionTracker) makeEndpointNode(namespaceID string, addr string, port uint16, extra map[string]string) report.Node {
	portStr := strconv.Itoa(int(port))
	node := report.MakeNodeWith(report.MakeEndpointNodeID(t.conf.HostID, namespaceID, addr, portStr), nil)
	if names := t.conf.DNSSnooper.CachedNamesForIP(addr); len(names) > 0 {
		node = node.WithSet(SnoopedDNSNames, report.MakeStringSet(names...))
	}
	if names, err := t.reverseResolver.get(addr); err == nil && len(names) > 0 {
		node = node.WithSet(ReverseDNSNames, report.MakeStringSet(names...))
	}
	if extra != nil {
		node = node.WithLatests(extra)
	}
	return node
}

func (t *connectionTracker) Stop() error {
	if t.ebpfTracker != nil {
		t.ebpfTracker.stop()
	}
	if t.flowWalker != nil {
		t.flowWalker.stop()
	}
	t.reverseResolver.stop()
	return nil
}

func connectionTuple(conn *procspy.Connection, seenTuples map[string]fourTuple) (fourTuple, string, bool) {
	namespaceID := ""
	tuple := fourTuple{
		conn.LocalAddress.String(),
		conn.RemoteAddress.String(),
		conn.LocalPort,
		conn.RemotePort,
	}
	if conn.Proc.NetNamespaceID > 0 {
		namespaceID = strconv.FormatUint(conn.Proc.NetNamespaceID, 10)
	}

	// If we've already seen this connection, we should know the direction
	// (or have already figured it out), so we normalize and use the
	// canonical direction. Otherwise, we can use a port-heuristic to guess
	// the direction.
	canonical, ok := seenTuples[tuple.key()]
	return tuple, namespaceID, (ok && canonical != tuple) || (!ok && tuple.fromPort < tuple.toPort)
}
<commit_msg>refactor: make performFlowWalk data flow more obvious<commit_after>package endpoint

import (
	"strconv"

	log "github.com/Sirupsen/logrus"
	"github.com/weaveworks/scope/probe/endpoint/procspy"
	"github.com/weaveworks/scope/probe/process"
	"github.com/weaveworks/scope/report"
)

// connectionTrackerConfig are the config options for the endpoint tracker.
type connectionTrackerConfig struct {
	HostID       string
	HostName     string
	SpyProcs     bool
	UseConntrack bool
	WalkProc     bool
	UseEbpfConn  bool
	ProcRoot     string
	BufferSize   int
	ProcessCache *process.CachingWalker
	Scanner      procspy.ConnectionScanner
	DNSSnooper   *DNSSnooper
}

type connectionTracker struct {
	conf            connectionTrackerConfig
	flowWalker      flowWalker // Interface
	ebpfTracker     eventTracker
	reverseResolver *reverseResolver
}

func newConnectionTracker(conf connectionTrackerConfig) connectionTracker {
	ct := connectionTracker{
		conf:            conf,
		reverseResolver: newReverseResolver(),
	}
	if conf.UseEbpfConn {
		et, err := newEbpfTracker()
		if err == nil {
			ct.ebpfTracker = et
			go ct.getInitialState()
			return ct
		}
		log.Warnf("Error setting up the eBPF tracker, falling back to proc scanning: %v", err)
	}
	ct.useProcfs()
	return ct
}

func flowToTuple(f flow) (ft fourTuple) {
	ft = fourTuple{
		f.Original.Layer3.SrcIP,
		f.Original.Layer3.DstIP,
		uint16(f.Original.Layer4.SrcPort),
		uint16(f.Original.Layer4.DstPort),
	}
	// Handle DNAT-ed connections in the initial state
	if f.Original.Layer3.DstIP != f.Reply.Layer3.SrcIP {
		ft = fourTuple{
			f.Reply.Layer3.DstIP,
			f.Reply.Layer3.SrcIP,
			uint16(f.Reply.Layer4.DstPort),
			uint16(f.Reply.Layer4.SrcPort),
		}
	}
	return ft
}

func (t *connectionTracker) useProcfs() {
	t.ebpfTracker = nil
	if t.conf.WalkProc && t.conf.Scanner == nil {
		t.conf.Scanner = procspy.NewConnectionScanner(t.conf.ProcessCache, t.conf.SpyProcs)
	}
	if t.flowWalker == nil {
		t.flowWalker = newConntrackFlowWalker(t.conf.UseConntrack, t.conf.ProcRoot, t.conf.BufferSize)
	}
}

// ReportConnections calls trackers according to the configuration.
func (t *connectionTracker) ReportConnections(rpt *report.Report) {
	hostNodeID := report.MakeHostNodeID(t.conf.HostID)

	if t.ebpfTracker != nil {
		if !t.ebpfTracker.isDead() {
			t.performEbpfTrack(rpt, hostNodeID)
			return
		}
		log.Warnf("ebpf tracker died, gently falling back to proc scanning")
		t.useProcfs()
	}

	// seenTuples contains information about connections seen by
	// conntrack
	seenTuples := t.performFlowWalk(rpt)

	if t.conf.WalkProc && t.conf.Scanner != nil {
		t.performWalkProc(rpt, hostNodeID, seenTuples)
	}
}

// performFlowWalk consults the flowWalker for short-lived connections
func (t *connectionTracker) performFlowWalk(rpt *report.Report) map[string]fourTuple {
	seenTuples := map[string]fourTuple{}
	extraNodeInfo := map[string]string{
		Conntracked: "true",
	}
	t.flowWalker.walkFlows(func(f flow, alive bool) {
		tuple := flowToTuple(f)
		seenTuples[tuple.key()] = tuple
		t.addConnection(rpt, tuple, "", extraNodeInfo, extraNodeInfo)
	})
	return seenTuples
}

func (t *connectionTracker) performWalkProc(rpt *report.Report, hostNodeID string, seenTuples map[string]fourTuple) error {
	conns, err := t.conf.Scanner.Connections()
	if err != nil {
		return err
	}
	for conn := conns.Next(); conn != nil; conn = conns.Next() {
		tuple, namespaceID, incoming := connectionTuple(conn, seenTuples)
		var (
			toNodeInfo   = map[string]string{Procspied: "true"}
			fromNodeInfo = map[string]string{Procspied: "true"}
		)
		if conn.Proc.PID > 0 {
			fromNodeInfo[process.PID] = strconv.FormatUint(uint64(conn.Proc.PID), 10)
			fromNodeInfo[report.HostNodeID] = hostNodeID
		}
		if incoming {
			tuple.reverse()
			toNodeInfo, fromNodeInfo = fromNodeInfo, toNodeInfo
		}
		t.addConnection(rpt, tuple, namespaceID, fromNodeInfo, toNodeInfo)
	}
	return nil
}

// getInitialState runs conntrack and proc parsing synchronously only
// once to initialize ebpfTracker
func (t *connectionTracker) getInitialState() {
	var processCache *process.CachingWalker
	walker := process.NewWalker(t.conf.ProcRoot, true)
	processCache = process.NewCachingWalker(walker)
	processCache.Tick()

	scanner := procspy.NewSyncConnectionScanner(processCache, t.conf.SpyProcs)
	seenTuples := map[string]fourTuple{}
	// Consult the flowWalker to get the initial state
	if err := IsConntrackSupported(t.conf.ProcRoot); t.conf.UseConntrack && err != nil {
		log.Warnf("Not using conntrack: not supported by the kernel: %s", err)
	} else if existingFlows, err := existingConnections([]string{"--any-nat"}); err != nil {
		log.Errorf("conntrack existingConnections error: %v", err)
	} else {
		for _, f := range existingFlows {
			tuple := flowToTuple(f)
			seenTuples[tuple.key()] = tuple
		}
	}

	conns, err := scanner.Connections()
	if err != nil {
		log.Errorf("Error initializing ebpfTracker while scanning /proc, continuing without initial connections: %s", err)
	}
	scanner.Stop()

	processesWaitingInAccept := []int{}
	processCache.Walk(func(p, prev process.Process) {
		if p.IsWaitingInAccept {
			processesWaitingInAccept = append(processesWaitingInAccept, p.PID)
		}
	})

	t.ebpfTracker.feedInitialConnections(conns, seenTuples, processesWaitingInAccept, report.MakeHostNodeID(t.conf.HostID))
}

func (t *connectionTracker) performEbpfTrack(rpt *report.Report, hostNodeID string) error {
	t.ebpfTracker.walkConnections(func(e ebpfConnection) {
		fromNodeInfo := map[string]string{
			EBPF: "true",
		}
		toNodeInfo := map[string]string{
			EBPF: "true",
		}
		if e.pid > 0 {
			fromNodeInfo[process.PID] = strconv.Itoa(e.pid)
			fromNodeInfo[report.HostNodeID] = hostNodeID
		}

		if e.incoming {
			t.addConnection(rpt, reverse(e.tuple), e.networkNamespace, toNodeInfo, fromNodeInfo)
		} else {
			t.addConnection(rpt, e.tuple, e.networkNamespace, fromNodeInfo, toNodeInfo)
		}

	})
	return nil
}

func (t *connectionTracker) addConnection(rpt *report.Report, ft fourTuple, namespaceID string, extraFromNode, extraToNode map[string]string) {
	var (
		fromNode = t.makeEndpointNode(namespaceID, ft.fromAddr, ft.fromPort, extraFromNode)
		toNode   = t.makeEndpointNode(namespaceID, ft.toAddr, ft.toPort, extraToNode)
	)
	rpt.Endpoint = rpt.Endpoint.AddNode(fromNode.WithEdge(toNode.ID, report.EdgeMetadata{}))
	rpt.Endpoint = rpt.Endpoint.AddNode(toNode)
}

func (t *connectionTracker) makeEndpointNode(namespaceID string, addr string, port uint16, extra map[string]string) report.Node {
	portStr := strconv.Itoa(int(port))
	node := report.MakeNodeWith(report.MakeEndpointNodeID(t.conf.HostID, namespaceID, addr, portStr), nil)
	if names := t.conf.DNSSnooper.CachedNamesForIP(addr); len(names) > 0 {
		node = node.WithSet(SnoopedDNSNames, report.MakeStringSet(names...))
	}
	if names, err := t.reverseResolver.get(addr); err == nil && len(names) > 0 {
		node = node.WithSet(ReverseDNSNames, report.MakeStringSet(names...))
	}
	if extra != nil {
		node = node.WithLatests(extra)
	}
	return node
}

func (t *connectionTracker) Stop() error {
	if t.ebpfTracker != nil {
		t.ebpfTracker.stop()
	}
	if t.flowWalker != nil {
		t.flowWalker.stop()
	}
	t.reverseResolver.stop()
	return nil
}

func connectionTuple(conn *procspy.Connection, seenTuples map[string]fourTuple) (fourTuple, string, bool) {
	namespaceID := ""
	tuple := fourTuple{
		conn.LocalAddress.String(),
		conn.RemoteAddress.String(),
		conn.LocalPort,
		conn.RemotePort,
	}
	if conn.Proc.NetNamespaceID > 0 {
		namespaceID = strconv.FormatUint(conn.Proc.NetNamespaceID, 10)
	}

	// If we've already seen this connection, we should know the direction
	// (or have already figured it out), so we normalize and use the
	// canonical direction. Otherwise, we can use a port-heuristic to guess
	// the direction.
	canonical, ok := seenTuples[tuple.key()]
	return tuple, namespaceID, (ok && canonical != tuple) || (!ok && tuple.fromPort < tuple.toPort)
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"flag"
	"fmt"
	"log"
	"strconv"
	"unsafe"

	"github.com/stampzilla/stampzilla-go/nodes/basenode"
	"github.com/stampzilla/stampzilla-go/nodes/stampzilla-telldus-events/sensormonitor"
	"github.com/stampzilla/stampzilla-go/pkg/notifier"
	"github.com/stampzilla/stampzilla-go/protocol"
	"github.com/stampzilla/stampzilla-go/protocol/devices"
)

/*
#cgo LDFLAGS: -ltelldus-core

#include <telldus-core.h>

extern void registerCallbacks();
extern void unregisterCallbacks();
extern int updateDevices();

*/
import "C"

var VERSION string = "dev"
var BUILD_DATE string = ""

var node *protocol.Node
var state *State = &State{make(map[string]*Device), make(map[string]*Sensor, 0)}
var serverConnection basenode.Connection
var sensorMonitor *sensormonitor.Monitor

type Config struct {
	MonitorSensors []sensormonitor.SensorConfig
}

func main() {
	flag.Parse()

	//Get a config with the correct parameters
	config := basenode.NewConfig()
	basenode.SetConfig(config)
	nc := &Config{}
	err := config.NodeSpecific(&nc)
	if err != nil {
		log.Println(err)
		return
	}

	log.Println("Starting TELLDUS-events node")

	C.registerCallbacks()
	defer C.unregisterCallbacks()

	// Create new node description
	node = protocol.NewNode("telldus-events")
	node.Version = VERSION
	node.BuildDate = BUILD_DATE
	node.SetState(state)

	// Add devices
	cnt := C.updateDevices()
	log.Println("Updated devices (", cnt, " in total)")

	for _, dev := range state.Devices {
		node.AddElement(&protocol.Element{
			Type: protocol.ElementTypeToggle,
			Name: dev.Name,
			Command: &protocol.Command{
				Cmd:  "toggle",
				Args: []string{dev.Id},
			},
			Feedback: `Devices[` + dev.Id + `].State.On`,
		})

		node.Devices().Add(&devices.Device{
			Type:   "lamp",
			Name:   dev.Name,
			Id:     dev.Id,
			Online: true,
			StateMap: map[string]string{
				"on": "Devices[" + dev.Id + "]" + ".State.On",
			},
		})
	}

	for _, dev := range nc.MonitorSensors {
		id := strconv.Itoa(dev.Id)
		node.Devices_["s"+id] = &devices.Device{
			Type:   "sensor",
			Name:   dev.Name,
			Id:     "s" + id,
			Online: true,
			StateMap: map[string]string{
				"temp":     "Sensors[" + id + "]" + ".Temp",
				"humidity": "Sensors[" + id + "]" + ".Humidity",
			},
		}
	}

	// Start the connection
	//go connection(host, port, node)

	serverConnection = basenode.Connect()
	notify := notifier.New(serverConnection)
	notify.SetSource(node)

	sensorMonitor = sensormonitor.New(notify)
	sensorMonitor.MonitorSensors = nc.MonitorSensors
	sensorMonitor.Start()
	log.Println("Monitoring Sensors: ", nc.MonitorSensors)

	go monitorState(serverConnection)

	// This worker recives all incomming commands
	go serverRecv(serverConnection)

	select {}
}

// WORKER that monitors the current connection state
func monitorState(connection basenode.Connection) {
	for s := range connection.State() {
		switch s {
		case basenode.ConnectionStateConnected:
			connection.Send(node.Node())
		case basenode.ConnectionStateDisconnected:
		}
	}
}

// WORKER that recives all incomming commands
func serverRecv(connection basenode.Connection) {
	send := processCommandWorker()
	for d := range connection.Receive() {
		send <- d
	}
}

func processCommandWorker() chan protocol.Command {
	var send = make(chan protocol.Command, 100)

	go func() {
		for c := range send {
			if err := processCommand(c); err != nil {
				log.Println(err)
			}
		}
	}()

	return send
}

func processCommand(cmd protocol.Command) error {
	log.Println("Processing command", cmd)
	var result C.int = C.TELLSTICK_ERROR_UNKNOWN
	var id C.int = 0

	i, err := strconv.Atoi(cmd.Args[0])
	if err != nil {
		return fmt.Errorf("Failed to decode arg[0] to int %s %s", err, cmd.Args[0])
	}

	id = C.int(i)

	switch cmd.Cmd {
	case "on", "stampzilla-device-on":
		result = C.tdTurnOn(id)
	case "off", "stampzilla-device-off":
		result = C.tdTurnOff(id)
	case "toggle":
		s := C.tdLastSentCommand(id, C.TELLSTICK_TURNON|C.TELLSTICK_TURNOFF|C.TELLSTICK_DIM)
		switch {
		case s&C.TELLSTICK_DIM != 0:
			var state *C.char = C.tdLastSentValue(id)
			log.Println("DIM: ", C.GoString(state))
			if C.GoString(state) == "0" {
				result = C.tdTurnOn(id)
			} else {
				result = C.tdTurnOff(id)
			}
			C.tdReleaseString(state)
		case s&C.TELLSTICK_TURNON != 0:
			result = C.tdTurnOff(id)
		case s&C.TELLSTICK_TURNOFF != 0:
			result = C.tdTurnOn(id)
		}
	default:
		log.Println("Unknown command")
	}

	if result != C.TELLSTICK_SUCCESS {
		var errorString *C.char = C.tdGetErrorString(result)
		C.tdReleaseString(errorString)
		err := errors.New(C.GoString(errorString))
		log.Println("Command failed", err)
		return err
	}

	return nil
}

//export newDevice
func newDevice(id int, name *C.char, methods, s int, value *C.char) {
	//log.Println(id, C.GoString(name))

	features := []string{}
	if methods&C.TELLSTICK_TURNON != 0 {
		features = append(features, "on")
	}
	if methods&C.TELLSTICK_TURNOFF != 0 {
		features = append(features, "off")
	}
	if methods&C.TELLSTICK_BELL != 0 {
		features = append(features, "bell")
	}
	if methods&C.TELLSTICK_TOGGLE != 0 {
		features = append(features, "toggle")
	}
	if methods&C.TELLSTICK_DIM != 0 {
		features = append(features, "dim")
	}
	if methods&C.TELLSTICK_EXECUTE != 0 {
		features = append(features, "execute")
	}
	if methods&C.TELLSTICK_UP != 0 {
		features = append(features, "up")
	}
	if methods&C.TELLSTICK_DOWN != 0 {
		features = append(features, "down")
	}
	if methods&C.TELLSTICK_STOP != 0 {
		features = append(features, "stop")
	}

	if s&C.TELLSTICK_TURNON != 0 {
		state.AddDevice(strconv.Itoa(id), C.GoString(name), features, DeviceState{On: true, Dim: 100})
	}
	if s&C.TELLSTICK_TURNOFF != 0 {
		state.AddDevice(strconv.Itoa(id), C.GoString(name), features, DeviceState{On: false})
	}
	if s&C.TELLSTICK_DIM != 0 {
		var currentState = C.GoString(value)
		level, _ := strconv.ParseUint(currentState, 10, 16)
		state.AddDevice(strconv.Itoa(id), C.GoString(name), features, DeviceState{On: level > 0, Dim: int(level)})
	}

}

//export sensorEvent
func sensorEvent(protocol, model *C.char, sensorId, dataType int, value *C.char) {
	//log.Println("SensorEVENT: ", C.GoString(protocol), C.GoString(model), sensorId)

	var s *Sensor
	if s = state.GetSensor(sensorId); s == nil {
		s = state.AddSensor(sensorId)
	}
	sensorMonitor.Alive(s.Id)

	if dataType == C.TELLSTICK_TEMPERATURE {
		t, _ := strconv.ParseFloat(C.GoString(value), 64)
		log.Printf("Temperature %d : %f\n", s.Id, t)
		if s.Temp != t {
			//log.Println("Difference, sending to server")
			s.Temp = t
			serverConnection.Send(node.Node())
		}
	} else if dataType == C.TELLSTICK_HUMIDITY {
		h, _ := strconv.ParseFloat(C.GoString(value), 64)
		log.Printf("Humidity %d : %f\n", s.Id, h)
		if s.Humidity != h {
			//log.Println("Difference, sending to server")
			s.Humidity = h
			serverConnection.Send(node.Node())
		}
	}
}

//export deviceEvent
func deviceEvent(deviceId, method int, data *C.char, callbackId int, context unsafe.Pointer) {
	//log.Println("DeviceEVENT: ", deviceId, method, C.GoString(data))
	device := state.GetDevice(strconv.Itoa(deviceId))
	if method&C.TELLSTICK_TURNON != 0 {
		device.State.On = true
		serverConnection.Send(node.Node())
	}
	if method&C.TELLSTICK_TURNOFF != 0 {
		device.State.On = false
		serverConnection.Send(node.Node())
	}
	if method&C.TELLSTICK_DIM != 0 {
		level, err := strconv.ParseUint(C.GoString(data), 10, 16)
		if err != nil {
			log.Println(err)
			return
		}
		if level == 0 {
			device.State.On = false
		}
		if level > 0 {
			device.State.On = true
		}
		device.State.Dim = int(level)
		serverConnection.Send(node.Node())
	}
}

//export deviceChangeEvent
func deviceChangeEvent(deviceId, changeEvent, changeType, callbackId int, context unsafe.Pointer) {
	//log.Println("DeviceChangeEVENT: ", deviceId, changeEvent, changeType)
}

//export rawDeviceEvent
func rawDeviceEvent(data *C.char, controllerId, callbackId int, context unsafe.Pointer) {
	//log.Println("rawDeviceEVENT: ", controllerId, C.GoString(data))
}
<commit_msg>Fix regression from #88<commit_after>package main

import (
	"errors"
	"flag"
	"fmt"
	"log"
	"strconv"
	"unsafe"

	"github.com/stampzilla/stampzilla-go/nodes/basenode"
	"github.com/stampzilla/stampzilla-go/nodes/stampzilla-telldus-events/sensormonitor"
	"github.com/stampzilla/stampzilla-go/pkg/notifier"
	"github.com/stampzilla/stampzilla-go/protocol"
	"github.com/stampzilla/stampzilla-go/protocol/devices"
)

/*
#cgo LDFLAGS: -ltelldus-core

#include <telldus-core.h>

extern void registerCallbacks();
extern void unregisterCallbacks();
extern int updateDevices();

*/
import "C"

var VERSION string = "dev"
var BUILD_DATE string = ""

var node *protocol.Node
var state *State = &State{make(map[string]*Device), make(map[string]*Sensor, 0)}
var serverConnection basenode.Connection
var sensorMonitor *sensormonitor.Monitor

type Config struct {
	MonitorSensors []sensormonitor.SensorConfig
}

func main() {
	flag.Parse()

	//Get a config with the correct parameters
	config := basenode.NewConfig()
	basenode.SetConfig(config)
	nc := &Config{}
	err := config.NodeSpecific(&nc)
	if err != nil {
		log.Println(err)
		return
	}

	log.Println("Starting TELLDUS-events node")

	C.registerCallbacks()
	defer C.unregisterCallbacks()

	// Create new node description
	node = protocol.NewNode("telldus-events")
	node.Version = VERSION
	node.BuildDate = BUILD_DATE
	node.SetState(state)

	// Add devices
	cnt := C.updateDevices()
	log.Println("Updated devices (", cnt, " in total)")

	for _, dev := range state.Devices {
		node.AddElement(&protocol.Element{
			Type: protocol.ElementTypeToggle,
			Name: dev.Name,
			Command: &protocol.Command{
				Cmd:  "toggle",
				Args: []string{dev.Id},
			},
			Feedback: `Devices[` + dev.Id + `].State.On`,
		})

		node.Devices().Add(&devices.Device{
			Type:   "lamp",
			Name:   dev.Name,
			Id:     dev.Id,
			Online: true,
			StateMap: map[string]string{
				"on": "Devices[" + dev.Id + "]" + ".State.On",
			},
		})
	}

	for _, dev := range nc.MonitorSensors {
		id := strconv.Itoa(dev.Id)
		node.Devices.Add(&devices.Device{
			Type:   "sensor",
			Name:   dev.Name,
			Id:     "s" + id,
			Online: true,
			StateMap: map[string]string{
				"temp":     "Sensors[" + id + "]" + ".Temp",
				"humidity": "Sensors[" + id + "]" + ".Humidity",
			},
		})
	}

	// Start the connection
	//go connection(host, port, node)

	serverConnection = basenode.Connect()
	notify := notifier.New(serverConnection)
	notify.SetSource(node)

	sensorMonitor = sensormonitor.New(notify)
	sensorMonitor.MonitorSensors = nc.MonitorSensors
	sensorMonitor.Start()
	log.Println("Monitoring Sensors: ", nc.MonitorSensors)

	go monitorState(serverConnection)

	// This worker recives all incomming commands
	go serverRecv(serverConnection)

	select {}
}

// WORKER that monitors the current connection state
func monitorState(connection basenode.Connection) {
	for s := range connection.State() {
		switch s {
		case basenode.ConnectionStateConnected:
			connection.Send(node.Node())
		case basenode.ConnectionStateDisconnected:
		}
	}
}

// WORKER that recives all incomming commands
func serverRecv(connection basenode.Connection) {
	send := processCommandWorker()
	for d := range connection.Receive() {
		send <- d
	}
}

func processCommandWorker() chan protocol.Command {
	var send = make(chan protocol.Command, 100)

	go func() {
		for c := range send {
			if err := processCommand(c); err != nil {
				log.Println(err)
			}
		}
	}()

	return send
}

func processCommand(cmd protocol.Command) error {
	log.Println("Processing command", cmd)
	var result C.int = C.TELLSTICK_ERROR_UNKNOWN
	var id C.int = 0

	i, err := strconv.Atoi(cmd.Args[0])
	if err != nil {
		return fmt.Errorf("Failed to decode arg[0] to int %s %s", err, cmd.Args[0])
	}

	id = C.int(i)

	switch cmd.Cmd {
	case "on", "stampzilla-device-on":
		result = C.tdTurnOn(id)
	case "off", "stampzilla-device-off":
		result = C.tdTurnOff(id)
	case "toggle":
		s := C.tdLastSentCommand(id, C.TELLSTICK_TURNON|C.TELLSTICK_TURNOFF|C.TELLSTICK_DIM)
		switch {
		case s&C.TELLSTICK_DIM != 0:
			var state *C.char = C.tdLastSentValue(id)
			log.Println("DIM: ", C.GoString(state))
			if C.GoString(state) == "0" {
				result = C.tdTurnOn(id)
			} else {
				result = C.tdTurnOff(id)
			}
			C.tdReleaseString(state)
		case s&C.TELLSTICK_TURNON != 0:
			result = C.tdTurnOff(id)
		case s&C.TELLSTICK_TURNOFF != 0:
			result = C.tdTurnOn(id)
		}
	default:
		log.Println("Unknown command")
	}

	if result != C.TELLSTICK_SUCCESS {
		var errorString *C.char = C.tdGetErrorString(result)
		C.tdReleaseString(errorString)
		err := errors.New(C.GoString(errorString))
		log.Println("Command failed", err)
		return err
	}

	return nil
}

//export newDevice
func newDevice(id int, name *C.char, methods, s int, value *C.char) {
	//log.Println(id, C.GoString(name))

	features := []string{}
	if methods&C.TELLSTICK_TURNON != 0 {
		features = append(features, "on")
	}
	if methods&C.TELLSTICK_TURNOFF != 0 {
		features = append(features, "off")
	}
	if methods&C.TELLSTICK_BELL != 0 {
		features = append(features, "bell")
	}
	if methods&C.TELLSTICK_TOGGLE != 0 {
		features = append(features, "toggle")
	}
	if methods&C.TELLSTICK_DIM != 0 {
		features = append(features, "dim")
	}
	if methods&C.TELLSTICK_EXECUTE != 0 {
		features = append(features, "execute")
	}
	if methods&C.TELLSTICK_UP != 0 {
		features = append(features, "up")
	}
	if methods&C.TELLSTICK_DOWN != 0 {
		features = append(features, "down")
	}
	if methods&C.TELLSTICK_STOP != 0 {
		features = append(features, "stop")
	}

	if s&C.TELLSTICK_TURNON != 0 {
		state.AddDevice(strconv.Itoa(id), C.GoString(name), features, DeviceState{On: true, Dim: 100})
	}
	if s&C.TELLSTICK_TURNOFF != 0 {
		state.AddDevice(strconv.Itoa(id), C.GoString(name), features, DeviceState{On: false})
	}
	if s&C.TELLSTICK_DIM != 0 {
		var currentState = C.GoString(value)
		level, _ := strconv.ParseUint(currentState, 10, 16)
		state.AddDevice(strconv.Itoa(id), C.GoString(name), features, DeviceState{On: level > 0, Dim: int(level)})
	}

}

//export sensorEvent
func sensorEvent(protocol, model *C.char, sensorId, dataType int, value *C.char) {
	//log.Println("SensorEVENT: ", C.GoString(protocol), C.GoString(model), sensorId)

	var s *Sensor
	if s = state.GetSensor(sensorId); s == nil {
		s = state.AddSensor(sensorId)
	}
	sensorMonitor.Alive(s.Id)

	if dataType == C.TELLSTICK_TEMPERATURE {
		t, _ := strconv.ParseFloat(C.GoString(value), 64)
		log.Printf("Temperature %d : %f\n", s.Id, t)
		if s.Temp != t {
			//log.Println("Difference, sending to server")
			s.Temp = t
			serverConnection.Send(node.Node())
		}
	} else if dataType == C.TELLSTICK_HUMIDITY {
		h, _ := strconv.ParseFloat(C.GoString(value), 64)
		log.Printf("Humidity %d : %f\n", s.Id, h)
		if s.Humidity != h {
			//log.Println("Difference, sending to server")
			s.Humidity = h
			serverConnection.Send(node.Node())
		}
	}
}

//export deviceEvent
func deviceEvent(deviceId, method int, data *C.char, callbackId int, context unsafe.Pointer) {
	//log.Println("DeviceEVENT: ", deviceId, method, C.GoString(data))
	device := state.GetDevice(strconv.Itoa(deviceId))
	if method&C.TELLSTICK_TURNON != 0 {
		device.State.On = true
		serverConnection.Send(node.Node())
	}
	if method&C.TELLSTICK_TURNOFF != 0 {
		device.State.On = false
		serverConnection.Send(node.Node())
	}
	if method&C.TELLSTICK_DIM != 0 {
		level, err := strconv.ParseUint(C.GoString(data), 10, 16)
		if err != nil {
			log.Println(err)
			return
		}
		if level == 0 {
			device.State.On = false
		}
		if level > 0 {
			device.State.On = true
		}
		device.State.Dim = int(level)
		serverConnection.Send(node.Node())
	}
}

//export deviceChangeEvent
func deviceChangeEvent(deviceId, changeEvent, changeType, callbackId int, context unsafe.Pointer) {
	//log.Println("DeviceChangeEVENT: ", deviceId, changeEvent, changeType)
}

//export rawDeviceEvent
func rawDeviceEvent(data *C.char, controllerId, callbackId int, context unsafe.Pointer) {
	//log.Println("rawDeviceEVENT: ", controllerId, C.GoString(data))
}
<|endoftext|>