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

// Network interface identification for BSD variants

package net

import (
	"os"
	"syscall"
)

// IsUp returns true if ifi is up.
func (ifi *Interface) IsUp() bool {
	if ifi == nil {
		return false
	}
	return ifi.rawFlags&syscall.IFF_UP != 0
}

// IsLoopback returns true if ifi is a loopback interface.
func (ifi *Interface) IsLoopback() bool {
	if ifi == nil {
		return false
	}
	return ifi.rawFlags&syscall.IFF_LOOPBACK != 0
}

// CanBroadcast returns true if ifi supports a broadcast access
// capability.
func (ifi *Interface) CanBroadcast() bool {
	if ifi == nil {
		return false
	}
	return ifi.rawFlags&syscall.IFF_BROADCAST != 0
}

// IsPointToPoint returns true if ifi belongs to a point-to-point
// link.
func (ifi *Interface) IsPointToPoint() bool {
	if ifi == nil {
		return false
	}
	return ifi.rawFlags&syscall.IFF_POINTOPOINT != 0
}

// CanMulticast returns true if ifi supports a multicast access
// capability.
func (ifi *Interface) CanMulticast() bool {
	if ifi == nil {
		return false
	}
	return ifi.rawFlags&syscall.IFF_MULTICAST != 0
}

// If the ifindex is zero, interfaceTable returns mappings of all
// network interfaces.  Otheriwse it returns a mapping of a specific
// interface.
func interfaceTable(ifindex int) ([]Interface, os.Error) {
	var (
		tab  []byte
		e    int
		msgs []syscall.RoutingMessage
		ift  []Interface
	)

	tab, e = syscall.RouteRIB(syscall.NET_RT_IFLIST, ifindex)
	if e != 0 {
		return nil, os.NewSyscallError("route rib", e)
	}

	msgs, e = syscall.ParseRoutingMessage(tab)
	if e != 0 {
		return nil, os.NewSyscallError("route message", e)
	}

	for _, m := range msgs {
		switch v := m.(type) {
		case *syscall.InterfaceMessage:
			if ifindex == 0 || ifindex == int(v.Header.Index) {
				ifi, err := newLink(v)
				if err != nil {
					return nil, err
				}
				ift = append(ift, ifi...)
			}
		}
	}

	return ift, nil
}

func newLink(m *syscall.InterfaceMessage) ([]Interface, os.Error) {
	var ift []Interface

	sas, e := syscall.ParseRoutingSockaddr(m)
	if e != 0 {
		return nil, os.NewSyscallError("route sockaddr", e)
	}

	for _, s := range sas {
		switch v := s.(type) {
		case *syscall.SockaddrDatalink:
			ifi := Interface{Index: int(m.Header.Index), rawFlags: int(m.Header.Flags)}
			var name [syscall.IFNAMSIZ]byte
			for i := 0; i < int(v.Nlen); i++ {
				name[i] = byte(v.Data[i])
			}
			ifi.Name = string(name[:v.Nlen])
			ifi.MTU = int(m.Header.Data.Mtu)
			addr := make([]byte, v.Alen)
			for i := 0; i < int(v.Alen); i++ {
				addr[i] = byte(v.Data[int(v.Nlen)+i])
			}
			ifi.HardwareAddr = addr[:v.Alen]
			ift = append(ift, ifi)
		}
	}

	return ift, nil
}

// If the ifindex is zero, interfaceAddrTable returns addresses
// for all network interfaces.  Otherwise it returns addresses
// for a specific interface.
func interfaceAddrTable(ifindex int) ([]Addr, os.Error) {
	var (
		tab  []byte
		e    int
		msgs []syscall.RoutingMessage
		ifat []Addr
	)

	tab, e = syscall.RouteRIB(syscall.NET_RT_IFLIST, ifindex)
	if e != 0 {
		return nil, os.NewSyscallError("route rib", e)
	}

	msgs, e = syscall.ParseRoutingMessage(tab)
	if e != 0 {
		return nil, os.NewSyscallError("route message", e)
	}

	for _, m := range msgs {
		switch v := m.(type) {
		case *syscall.InterfaceAddrMessage:
			if ifindex == 0 || ifindex == int(v.Header.Index) {
				ifa, err := newAddr(v)
				if err != nil {
					return nil, err
				}
				ifat = append(ifat, ifa...)
			}
		}
	}

	return ifat, nil
}

func newAddr(m *syscall.InterfaceAddrMessage) ([]Addr, os.Error) {
	var ifat []Addr

	sas, e := syscall.ParseRoutingSockaddr(m)
	if e != 0 {
		return nil, os.NewSyscallError("route sockaddr", e)
	}

	for _, s := range sas {
		var ifa IPAddr
		switch v := s.(type) {
		case *syscall.SockaddrInet4:
			ifa.IP = IPv4(v.Addr[0], v.Addr[1], v.Addr[2], v.Addr[3])
		case *syscall.SockaddrInet6:
			ifa.IP = make(IP, IPv6len)
			copy(ifa.IP, v.Addr[:])
			// NOTE: KAME based IPv6 protcol stack usually embeds
			// the interface index in the interface-local or link-
			// local address as the kernel-internal form.
			if ifa.IP.IsLinkLocalUnicast() ||
				ifa.IP.IsInterfaceLocalMulticast() ||
				ifa.IP.IsLinkLocalMulticast() {
				// remove embedded scope zone ID
				ifa.IP[2], ifa.IP[3] = 0, 0
			}
		}
		ifat = append(ifat, ifa.toAddr())
	}

	return ifat, nil
}
<commit_msg>net: fix bug in net.Interfaces: handle elastic sdl_data size correctly<commit_after>// Copyright 2011 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Network interface identification for BSD variants

package net

import (
	"os"
	"syscall"
	"unsafe"
)

// IsUp returns true if ifi is up.
func (ifi *Interface) IsUp() bool {
	if ifi == nil {
		return false
	}
	return ifi.rawFlags&syscall.IFF_UP != 0
}

// IsLoopback returns true if ifi is a loopback interface.
func (ifi *Interface) IsLoopback() bool {
	if ifi == nil {
		return false
	}
	return ifi.rawFlags&syscall.IFF_LOOPBACK != 0
}

// CanBroadcast returns true if ifi supports a broadcast access
// capability.
func (ifi *Interface) CanBroadcast() bool {
	if ifi == nil {
		return false
	}
	return ifi.rawFlags&syscall.IFF_BROADCAST != 0
}

// IsPointToPoint returns true if ifi belongs to a point-to-point
// link.
func (ifi *Interface) IsPointToPoint() bool {
	if ifi == nil {
		return false
	}
	return ifi.rawFlags&syscall.IFF_POINTOPOINT != 0
}

// CanMulticast returns true if ifi supports a multicast access
// capability.
func (ifi *Interface) CanMulticast() bool {
	if ifi == nil {
		return false
	}
	return ifi.rawFlags&syscall.IFF_MULTICAST != 0
}

// If the ifindex is zero, interfaceTable returns mappings of all
// network interfaces.  Otheriwse it returns a mapping of a specific
// interface.
func interfaceTable(ifindex int) ([]Interface, os.Error) {
	var (
		tab  []byte
		e    int
		msgs []syscall.RoutingMessage
		ift  []Interface
	)

	tab, e = syscall.RouteRIB(syscall.NET_RT_IFLIST, ifindex)
	if e != 0 {
		return nil, os.NewSyscallError("route rib", e)
	}

	msgs, e = syscall.ParseRoutingMessage(tab)
	if e != 0 {
		return nil, os.NewSyscallError("route message", e)
	}

	for _, m := range msgs {
		switch v := m.(type) {
		case *syscall.InterfaceMessage:
			if ifindex == 0 || ifindex == int(v.Header.Index) {
				ifi, err := newLink(v)
				if err != nil {
					return nil, err
				}
				ift = append(ift, ifi...)
			}
		}
	}

	return ift, nil
}

func newLink(m *syscall.InterfaceMessage) ([]Interface, os.Error) {
	var ift []Interface

	sas, e := syscall.ParseRoutingSockaddr(m)
	if e != 0 {
		return nil, os.NewSyscallError("route sockaddr", e)
	}

	for _, s := range sas {
		switch v := s.(type) {
		case *syscall.SockaddrDatalink:
			// NOTE: SockaddrDatalink.Data is minimum work area,
			// can be larger.
			m.Data = m.Data[unsafe.Offsetof(v.Data):]
			ifi := Interface{Index: int(m.Header.Index), rawFlags: int(m.Header.Flags)}
			var name [syscall.IFNAMSIZ]byte
			for i := 0; i < int(v.Nlen); i++ {
				name[i] = byte(m.Data[i])
			}
			ifi.Name = string(name[:v.Nlen])
			ifi.MTU = int(m.Header.Data.Mtu)
			addr := make([]byte, v.Alen)
			for i := 0; i < int(v.Alen); i++ {
				addr[i] = byte(m.Data[int(v.Nlen)+i])
			}
			ifi.HardwareAddr = addr[:v.Alen]
			ift = append(ift, ifi)
		}
	}

	return ift, nil
}

// If the ifindex is zero, interfaceAddrTable returns addresses
// for all network interfaces.  Otherwise it returns addresses
// for a specific interface.
func interfaceAddrTable(ifindex int) ([]Addr, os.Error) {
	var (
		tab  []byte
		e    int
		msgs []syscall.RoutingMessage
		ifat []Addr
	)

	tab, e = syscall.RouteRIB(syscall.NET_RT_IFLIST, ifindex)
	if e != 0 {
		return nil, os.NewSyscallError("route rib", e)
	}

	msgs, e = syscall.ParseRoutingMessage(tab)
	if e != 0 {
		return nil, os.NewSyscallError("route message", e)
	}

	for _, m := range msgs {
		switch v := m.(type) {
		case *syscall.InterfaceAddrMessage:
			if ifindex == 0 || ifindex == int(v.Header.Index) {
				ifa, err := newAddr(v)
				if err != nil {
					return nil, err
				}
				ifat = append(ifat, ifa...)
			}
		}
	}

	return ifat, nil
}

func newAddr(m *syscall.InterfaceAddrMessage) ([]Addr, os.Error) {
	var ifat []Addr

	sas, e := syscall.ParseRoutingSockaddr(m)
	if e != 0 {
		return nil, os.NewSyscallError("route sockaddr", e)
	}

	for _, s := range sas {
		var ifa IPAddr
		switch v := s.(type) {
		case *syscall.SockaddrInet4:
			ifa.IP = IPv4(v.Addr[0], v.Addr[1], v.Addr[2], v.Addr[3])
		case *syscall.SockaddrInet6:
			ifa.IP = make(IP, IPv6len)
			copy(ifa.IP, v.Addr[:])
			// NOTE: KAME based IPv6 protcol stack usually embeds
			// the interface index in the interface-local or link-
			// local address as the kernel-internal form.
			if ifa.IP.IsLinkLocalUnicast() ||
				ifa.IP.IsInterfaceLocalMulticast() ||
				ifa.IP.IsLinkLocalMulticast() {
				// remove embedded scope zone ID
				ifa.IP[2], ifa.IP[3] = 0, 0
			}
		}
		ifat = append(ifat, ifa.toAddr())
	}

	return ifat, nil
}
<|endoftext|>


<commit_before>package aws

import (
	"encoding/json"
	"fmt"
	"log"
	"regexp"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/arn"
	"github.com/aws/aws-sdk-go/aws/awsutil"
	events "github.com/aws/aws-sdk-go/service/cloudwatchevents"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/helper/validation"
)

func resourceAwsCloudWatchEventPermission() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsCloudWatchEventPermissionCreate,
		Read:   resourceAwsCloudWatchEventPermissionRead,
		Update: resourceAwsCloudWatchEventPermissionUpdate,
		Delete: resourceAwsCloudWatchEventPermissionDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"action": {
				Type:         schema.TypeString,
				Optional:     true,
				Default:      "events:PutEvents",
				ValidateFunc: validateCloudWatchEventPermissionAction,
			},
			"condition": {
				Type:     schema.TypeList,
				Optional: true,
				MaxItems: 1,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"key": {
							Type:         schema.TypeString,
							Required:     true,
							ValidateFunc: validation.StringInSlice([]string{"aws:PrincipalOrgID"}, false),
						},
						"type": {
							Type:         schema.TypeString,
							Required:     true,
							ValidateFunc: validation.StringInSlice([]string{"StringEquals"}, false),
						},
						"value": {
							Type:         schema.TypeString,
							Required:     true,
							ValidateFunc: validation.NoZeroValues,
						},
					},
				},
			},
			"principal": {
				Type:         schema.TypeString,
				Required:     true,
				ValidateFunc: validateCloudWatchEventPermissionPrincipal,
			},
			"statement_id": {
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validateCloudWatchEventPermissionStatementID,
			},
		},
	}
}

func resourceAwsCloudWatchEventPermissionCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).cloudwatcheventsconn

	statementID := d.Get("statement_id").(string)

	input := events.PutPermissionInput{
		Action:      aws.String(d.Get("action").(string)),
		Condition:   expandCloudWatchEventsCondition(d.Get("condition").([]interface{})),
		Principal:   aws.String(d.Get("principal").(string)),
		StatementId: aws.String(statementID),
	}

	log.Printf("[DEBUG] Creating CloudWatch Events permission: %s", input)
	_, err := conn.PutPermission(&input)
	if err != nil {
		return fmt.Errorf("Creating CloudWatch Events permission failed: %s", err.Error())
	}

	d.SetId(statementID)

	return resourceAwsCloudWatchEventPermissionRead(d, meta)
}

// See also: https://docs.aws.amazon.com/AmazonCloudWatchEvents/latest/APIReference/API_DescribeEventBus.html
func resourceAwsCloudWatchEventPermissionRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).cloudwatcheventsconn
	input := events.DescribeEventBusInput{}
	var output *events.DescribeEventBusOutput
	var policyStatement *CloudWatchEventPermissionPolicyStatement

	// Especially with concurrent PutPermission calls there can be a slight delay
	var err error
	err = resource.Retry(1*time.Minute, func() *resource.RetryError {
		log.Printf("[DEBUG] Reading CloudWatch Events bus: %s", input)
		output, err := conn.DescribeEventBus(&input)
		if err != nil {
			return resource.NonRetryableError(fmt.Errorf("Reading CloudWatch Events permission '%s' failed: %s", d.Id(), err.Error()))
		}

		policyStatement, err = getPolicyStatement(output, d.Id())
		return resource.RetryableError(err)
	})

	if isResourceTimeoutError(err) {
		output, err = conn.DescribeEventBus(&input)
		if output != nil {
			policyStatement, err = getPolicyStatement(output, d.Id())
		}
	}

	if isResourceNotFoundError(err) {
		log.Printf("[WARN] %s", err)
		d.SetId("")
		return nil
	}
	if err != nil {
		// Missing statement inside valid policy
		return err
	}

	d.Set("action", policyStatement.Action)

	if err := d.Set("condition", flattenCloudWatchEventPermissionPolicyStatementCondition(policyStatement.Condition)); err != nil {
		return fmt.Errorf("error setting condition: %s", err)
	}

	principalString, ok := policyStatement.Principal.(string)
	if ok && (principalString == "*") {
		d.Set("principal", "*")
	} else {
		principalMap := policyStatement.Principal.(map[string]interface{})
		policyARN, err := arn.Parse(principalMap["AWS"].(string))
		if err != nil {
			return fmt.Errorf("Reading CloudWatch Events permission '%s' failed: %s", d.Id(), err)
		}
		d.Set("principal", policyARN.AccountID)
	}
	d.Set("statement_id", policyStatement.Sid)

	return nil
}

func getPolicyStatement(output *events.DescribeEventBusOutput, statementID string) (*CloudWatchEventPermissionPolicyStatement, error) {
	var policyDoc CloudWatchEventPermissionPolicyDoc

	if output == nil || output.Policy == nil {
		return nil, &resource.NotFoundError{
			Message: fmt.Sprintf("CloudWatch Events permission %q not found"+
				"in given results from DescribeEventBus", statementID),
			LastResponse: output,
		}
	}

	err := json.Unmarshal([]byte(*output.Policy), &policyDoc)
	if err != nil {
		return nil, fmt.Errorf("Reading CloudWatch Events permission '%s' failed: %s", statementID, err)
	}

	return findCloudWatchEventPermissionPolicyStatementByID(&policyDoc, statementID)
}

func resourceAwsCloudWatchEventPermissionUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).cloudwatcheventsconn

	input := events.PutPermissionInput{
		Action:      aws.String(d.Get("action").(string)),
		Condition:   expandCloudWatchEventsCondition(d.Get("condition").([]interface{})),
		Principal:   aws.String(d.Get("principal").(string)),
		StatementId: aws.String(d.Get("statement_id").(string)),
	}

	log.Printf("[DEBUG] Update CloudWatch Events permission: %s", input)
	_, err := conn.PutPermission(&input)
	if isAWSErr(err, events.ErrCodeResourceNotFoundException, "") {
		log.Printf("[WARN] CloudWatch Events permission %q not found, removing from state", d.Id())
		d.SetId("")
		return nil
	}
	if err != nil {
		return fmt.Errorf("Updating CloudWatch Events permission '%s' failed: %s", d.Id(), err.Error())
	}

	return resourceAwsCloudWatchEventPermissionRead(d, meta)
}

func resourceAwsCloudWatchEventPermissionDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).cloudwatcheventsconn
	input := events.RemovePermissionInput{
		StatementId: aws.String(d.Id()),
	}

	log.Printf("[DEBUG] Delete CloudWatch Events permission: %s", input)
	_, err := conn.RemovePermission(&input)
	if isAWSErr(err, events.ErrCodeResourceNotFoundException, "") {
		return nil
	}
	if err != nil {
		return fmt.Errorf("Deleting CloudWatch Events permission '%s' failed: %s", d.Id(), err.Error())
	}
	return nil
}

// https://docs.aws.amazon.com/AmazonCloudWatchEvents/latest/APIReference/API_PutPermission.html#API_PutPermission_RequestParameters
func validateCloudWatchEventPermissionAction(v interface{}, k string) (ws []string, es []error) {
	value := v.(string)
	if (len(value) < 1) || (len(value) > 64) {
		es = append(es, fmt.Errorf("%q must be between 1 and 64 characters", k))
	}

	if !regexp.MustCompile(`^events:[a-zA-Z]+$`).MatchString(value) {
		es = append(es, fmt.Errorf("%q must be: events: followed by one or more alphabetic characters", k))
	}
	return
}

// https://docs.aws.amazon.com/AmazonCloudWatchEvents/latest/APIReference/API_PutPermission.html#API_PutPermission_RequestParameters
func validateCloudWatchEventPermissionPrincipal(v interface{}, k string) (ws []string, es []error) {
	value := v.(string)
	if !regexp.MustCompile(`^(\d{12}|\*)$`).MatchString(value) {
		es = append(es, fmt.Errorf("%q must be * or a 12 digit AWS account ID", k))
	}
	return
}

// https://docs.aws.amazon.com/AmazonCloudWatchEvents/latest/APIReference/API_PutPermission.html#API_PutPermission_RequestParameters
func validateCloudWatchEventPermissionStatementID(v interface{}, k string) (ws []string, es []error) {
	value := v.(string)
	if (len(value) < 1) || (len(value) > 64) {
		es = append(es, fmt.Errorf("%q must be between 1 and 64 characters", k))
	}

	if !regexp.MustCompile(`^[a-zA-Z0-9-_]+$`).MatchString(value) {
		es = append(es, fmt.Errorf("%q must be one or more alphanumeric, hyphen, or underscore characters", k))
	}
	return
}

// CloudWatchEventPermissionPolicyDoc represents the Policy attribute of DescribeEventBus
// See also: https://docs.aws.amazon.com/AmazonCloudWatchEvents/latest/APIReference/API_DescribeEventBus.html
type CloudWatchEventPermissionPolicyDoc struct {
	Version    string
	ID         string                                     `json:"Id,omitempty"`
	Statements []CloudWatchEventPermissionPolicyStatement `json:"Statement"`
}

// String returns the string representation
func (d CloudWatchEventPermissionPolicyDoc) String() string {
	return awsutil.Prettify(d)
}

// GoString returns the string representation
func (d CloudWatchEventPermissionPolicyDoc) GoString() string {
	return d.String()
}

// CloudWatchEventPermissionPolicyStatement represents the Statement attribute of CloudWatchEventPermissionPolicyDoc
// See also: https://docs.aws.amazon.com/AmazonCloudWatchEvents/latest/APIReference/API_DescribeEventBus.html
type CloudWatchEventPermissionPolicyStatement struct {
	Sid       string
	Effect    string
	Action    string
	Condition *CloudWatchEventPermissionPolicyStatementCondition `json:"Condition,omitempty"`
	Principal interface{}                                        // "*" or {"AWS": "arn:aws:iam::111111111111:root"}
	Resource  string
}

// String returns the string representation
func (s CloudWatchEventPermissionPolicyStatement) String() string {
	return awsutil.Prettify(s)
}

// GoString returns the string representation
func (s CloudWatchEventPermissionPolicyStatement) GoString() string {
	return s.String()
}

// CloudWatchEventPermissionPolicyStatementCondition represents the Condition attribute of CloudWatchEventPermissionPolicyStatement
// See also: https://docs.aws.amazon.com/AmazonCloudWatchEvents/latest/APIReference/API_DescribeEventBus.html
type CloudWatchEventPermissionPolicyStatementCondition struct {
	Key   string
	Type  string
	Value string
}

// String returns the string representation
func (c CloudWatchEventPermissionPolicyStatementCondition) String() string {
	return awsutil.Prettify(c)
}

// GoString returns the string representation
func (c CloudWatchEventPermissionPolicyStatementCondition) GoString() string {
	return c.String()
}

func (c *CloudWatchEventPermissionPolicyStatementCondition) UnmarshalJSON(b []byte) error {
	var out CloudWatchEventPermissionPolicyStatementCondition

	// JSON representation: \"Condition\":{\"StringEquals\":{\"aws:PrincipalOrgID\":\"o-0123456789\"}}
	var data map[string]map[string]string
	if err := json.Unmarshal(b, &data); err != nil {
		return err
	}

	for typeKey, typeValue := range data {
		for conditionKey, conditionValue := range typeValue {
			out = CloudWatchEventPermissionPolicyStatementCondition{
				Key:   conditionKey,
				Type:  typeKey,
				Value: conditionValue,
			}
		}
	}

	*c = out
	return nil
}

func findCloudWatchEventPermissionPolicyStatementByID(policy *CloudWatchEventPermissionPolicyDoc, id string) (
	*CloudWatchEventPermissionPolicyStatement, error) {

	log.Printf("[DEBUG] Finding statement (%s) in CloudWatch Events permission policy: %s", id, policy)
	for _, statement := range policy.Statements {
		if statement.Sid == id {
			return &statement, nil
		}
	}

	return nil, &resource.NotFoundError{
		LastRequest:  id,
		LastResponse: policy,
		Message:      fmt.Sprintf("Failed to find statement (%s) in CloudWatch Events permission policy: %s", id, policy),
	}
}

func expandCloudWatchEventsCondition(l []interface{}) *events.Condition {
	if len(l) == 0 || l[0] == nil {
		return nil
	}

	m := l[0].(map[string]interface{})

	condition := &events.Condition{
		Key:   aws.String(m["key"].(string)),
		Type:  aws.String(m["type"].(string)),
		Value: aws.String(m["value"].(string)),
	}

	return condition
}

func flattenCloudWatchEventPermissionPolicyStatementCondition(c *CloudWatchEventPermissionPolicyStatementCondition) []interface{} {
	if c == nil {
		return []interface{}{}
	}

	m := map[string]interface{}{
		"key":   c.Key,
		"type":  c.Type,
		"value": c.Value,
	}

	return []interface{}{m}
}
<commit_msg>Appease the linting gods<commit_after>package aws

import (
	"encoding/json"
	"fmt"
	"log"
	"regexp"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/arn"
	"github.com/aws/aws-sdk-go/aws/awsutil"
	events "github.com/aws/aws-sdk-go/service/cloudwatchevents"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/helper/validation"
)

func resourceAwsCloudWatchEventPermission() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsCloudWatchEventPermissionCreate,
		Read:   resourceAwsCloudWatchEventPermissionRead,
		Update: resourceAwsCloudWatchEventPermissionUpdate,
		Delete: resourceAwsCloudWatchEventPermissionDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"action": {
				Type:         schema.TypeString,
				Optional:     true,
				Default:      "events:PutEvents",
				ValidateFunc: validateCloudWatchEventPermissionAction,
			},
			"condition": {
				Type:     schema.TypeList,
				Optional: true,
				MaxItems: 1,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"key": {
							Type:         schema.TypeString,
							Required:     true,
							ValidateFunc: validation.StringInSlice([]string{"aws:PrincipalOrgID"}, false),
						},
						"type": {
							Type:         schema.TypeString,
							Required:     true,
							ValidateFunc: validation.StringInSlice([]string{"StringEquals"}, false),
						},
						"value": {
							Type:         schema.TypeString,
							Required:     true,
							ValidateFunc: validation.NoZeroValues,
						},
					},
				},
			},
			"principal": {
				Type:         schema.TypeString,
				Required:     true,
				ValidateFunc: validateCloudWatchEventPermissionPrincipal,
			},
			"statement_id": {
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validateCloudWatchEventPermissionStatementID,
			},
		},
	}
}

func resourceAwsCloudWatchEventPermissionCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).cloudwatcheventsconn

	statementID := d.Get("statement_id").(string)

	input := events.PutPermissionInput{
		Action:      aws.String(d.Get("action").(string)),
		Condition:   expandCloudWatchEventsCondition(d.Get("condition").([]interface{})),
		Principal:   aws.String(d.Get("principal").(string)),
		StatementId: aws.String(statementID),
	}

	log.Printf("[DEBUG] Creating CloudWatch Events permission: %s", input)
	_, err := conn.PutPermission(&input)
	if err != nil {
		return fmt.Errorf("Creating CloudWatch Events permission failed: %s", err.Error())
	}

	d.SetId(statementID)

	return resourceAwsCloudWatchEventPermissionRead(d, meta)
}

// See also: https://docs.aws.amazon.com/AmazonCloudWatchEvents/latest/APIReference/API_DescribeEventBus.html
func resourceAwsCloudWatchEventPermissionRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).cloudwatcheventsconn
	input := events.DescribeEventBusInput{}
	var output *events.DescribeEventBusOutput
	var policyStatement *CloudWatchEventPermissionPolicyStatement

	// Especially with concurrent PutPermission calls there can be a slight delay
	err := resource.Retry(1*time.Minute, func() *resource.RetryError {
		log.Printf("[DEBUG] Reading CloudWatch Events bus: %s", input)
		output, err := conn.DescribeEventBus(&input)
		if err != nil {
			return resource.NonRetryableError(fmt.Errorf("Reading CloudWatch Events permission '%s' failed: %s", d.Id(), err.Error()))
		}

		policyStatement, err = getPolicyStatement(output, d.Id())
		return resource.RetryableError(err)
	})

	if isResourceTimeoutError(err) {
		output, err = conn.DescribeEventBus(&input)
		if output != nil {
			policyStatement, err = getPolicyStatement(output, d.Id())
		}
	}

	if isResourceNotFoundError(err) {
		log.Printf("[WARN] %s", err)
		d.SetId("")
		return nil
	}
	if err != nil {
		// Missing statement inside valid policy
		return err
	}

	d.Set("action", policyStatement.Action)

	if err := d.Set("condition", flattenCloudWatchEventPermissionPolicyStatementCondition(policyStatement.Condition)); err != nil {
		return fmt.Errorf("error setting condition: %s", err)
	}

	principalString, ok := policyStatement.Principal.(string)
	if ok && (principalString == "*") {
		d.Set("principal", "*")
	} else {
		principalMap := policyStatement.Principal.(map[string]interface{})
		policyARN, err := arn.Parse(principalMap["AWS"].(string))
		if err != nil {
			return fmt.Errorf("Reading CloudWatch Events permission '%s' failed: %s", d.Id(), err)
		}
		d.Set("principal", policyARN.AccountID)
	}
	d.Set("statement_id", policyStatement.Sid)

	return nil
}

func getPolicyStatement(output *events.DescribeEventBusOutput, statementID string) (*CloudWatchEventPermissionPolicyStatement, error) {
	var policyDoc CloudWatchEventPermissionPolicyDoc

	if output == nil || output.Policy == nil {
		return nil, &resource.NotFoundError{
			Message: fmt.Sprintf("CloudWatch Events permission %q not found"+
				"in given results from DescribeEventBus", statementID),
			LastResponse: output,
		}
	}

	err := json.Unmarshal([]byte(*output.Policy), &policyDoc)
	if err != nil {
		return nil, fmt.Errorf("Reading CloudWatch Events permission '%s' failed: %s", statementID, err)
	}

	return findCloudWatchEventPermissionPolicyStatementByID(&policyDoc, statementID)
}

func resourceAwsCloudWatchEventPermissionUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).cloudwatcheventsconn

	input := events.PutPermissionInput{
		Action:      aws.String(d.Get("action").(string)),
		Condition:   expandCloudWatchEventsCondition(d.Get("condition").([]interface{})),
		Principal:   aws.String(d.Get("principal").(string)),
		StatementId: aws.String(d.Get("statement_id").(string)),
	}

	log.Printf("[DEBUG] Update CloudWatch Events permission: %s", input)
	_, err := conn.PutPermission(&input)
	if isAWSErr(err, events.ErrCodeResourceNotFoundException, "") {
		log.Printf("[WARN] CloudWatch Events permission %q not found, removing from state", d.Id())
		d.SetId("")
		return nil
	}
	if err != nil {
		return fmt.Errorf("Updating CloudWatch Events permission '%s' failed: %s", d.Id(), err.Error())
	}

	return resourceAwsCloudWatchEventPermissionRead(d, meta)
}

func resourceAwsCloudWatchEventPermissionDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).cloudwatcheventsconn
	input := events.RemovePermissionInput{
		StatementId: aws.String(d.Id()),
	}

	log.Printf("[DEBUG] Delete CloudWatch Events permission: %s", input)
	_, err := conn.RemovePermission(&input)
	if isAWSErr(err, events.ErrCodeResourceNotFoundException, "") {
		return nil
	}
	if err != nil {
		return fmt.Errorf("Deleting CloudWatch Events permission '%s' failed: %s", d.Id(), err.Error())
	}
	return nil
}

// https://docs.aws.amazon.com/AmazonCloudWatchEvents/latest/APIReference/API_PutPermission.html#API_PutPermission_RequestParameters
func validateCloudWatchEventPermissionAction(v interface{}, k string) (ws []string, es []error) {
	value := v.(string)
	if (len(value) < 1) || (len(value) > 64) {
		es = append(es, fmt.Errorf("%q must be between 1 and 64 characters", k))
	}

	if !regexp.MustCompile(`^events:[a-zA-Z]+$`).MatchString(value) {
		es = append(es, fmt.Errorf("%q must be: events: followed by one or more alphabetic characters", k))
	}
	return
}

// https://docs.aws.amazon.com/AmazonCloudWatchEvents/latest/APIReference/API_PutPermission.html#API_PutPermission_RequestParameters
func validateCloudWatchEventPermissionPrincipal(v interface{}, k string) (ws []string, es []error) {
	value := v.(string)
	if !regexp.MustCompile(`^(\d{12}|\*)$`).MatchString(value) {
		es = append(es, fmt.Errorf("%q must be * or a 12 digit AWS account ID", k))
	}
	return
}

// https://docs.aws.amazon.com/AmazonCloudWatchEvents/latest/APIReference/API_PutPermission.html#API_PutPermission_RequestParameters
func validateCloudWatchEventPermissionStatementID(v interface{}, k string) (ws []string, es []error) {
	value := v.(string)
	if (len(value) < 1) || (len(value) > 64) {
		es = append(es, fmt.Errorf("%q must be between 1 and 64 characters", k))
	}

	if !regexp.MustCompile(`^[a-zA-Z0-9-_]+$`).MatchString(value) {
		es = append(es, fmt.Errorf("%q must be one or more alphanumeric, hyphen, or underscore characters", k))
	}
	return
}

// CloudWatchEventPermissionPolicyDoc represents the Policy attribute of DescribeEventBus
// See also: https://docs.aws.amazon.com/AmazonCloudWatchEvents/latest/APIReference/API_DescribeEventBus.html
type CloudWatchEventPermissionPolicyDoc struct {
	Version    string
	ID         string                                     `json:"Id,omitempty"`
	Statements []CloudWatchEventPermissionPolicyStatement `json:"Statement"`
}

// String returns the string representation
func (d CloudWatchEventPermissionPolicyDoc) String() string {
	return awsutil.Prettify(d)
}

// GoString returns the string representation
func (d CloudWatchEventPermissionPolicyDoc) GoString() string {
	return d.String()
}

// CloudWatchEventPermissionPolicyStatement represents the Statement attribute of CloudWatchEventPermissionPolicyDoc
// See also: https://docs.aws.amazon.com/AmazonCloudWatchEvents/latest/APIReference/API_DescribeEventBus.html
type CloudWatchEventPermissionPolicyStatement struct {
	Sid       string
	Effect    string
	Action    string
	Condition *CloudWatchEventPermissionPolicyStatementCondition `json:"Condition,omitempty"`
	Principal interface{}                                        // "*" or {"AWS": "arn:aws:iam::111111111111:root"}
	Resource  string
}

// String returns the string representation
func (s CloudWatchEventPermissionPolicyStatement) String() string {
	return awsutil.Prettify(s)
}

// GoString returns the string representation
func (s CloudWatchEventPermissionPolicyStatement) GoString() string {
	return s.String()
}

// CloudWatchEventPermissionPolicyStatementCondition represents the Condition attribute of CloudWatchEventPermissionPolicyStatement
// See also: https://docs.aws.amazon.com/AmazonCloudWatchEvents/latest/APIReference/API_DescribeEventBus.html
type CloudWatchEventPermissionPolicyStatementCondition struct {
	Key   string
	Type  string
	Value string
}

// String returns the string representation
func (c CloudWatchEventPermissionPolicyStatementCondition) String() string {
	return awsutil.Prettify(c)
}

// GoString returns the string representation
func (c CloudWatchEventPermissionPolicyStatementCondition) GoString() string {
	return c.String()
}

func (c *CloudWatchEventPermissionPolicyStatementCondition) UnmarshalJSON(b []byte) error {
	var out CloudWatchEventPermissionPolicyStatementCondition

	// JSON representation: \"Condition\":{\"StringEquals\":{\"aws:PrincipalOrgID\":\"o-0123456789\"}}
	var data map[string]map[string]string
	if err := json.Unmarshal(b, &data); err != nil {
		return err
	}

	for typeKey, typeValue := range data {
		for conditionKey, conditionValue := range typeValue {
			out = CloudWatchEventPermissionPolicyStatementCondition{
				Key:   conditionKey,
				Type:  typeKey,
				Value: conditionValue,
			}
		}
	}

	*c = out
	return nil
}

func findCloudWatchEventPermissionPolicyStatementByID(policy *CloudWatchEventPermissionPolicyDoc, id string) (
	*CloudWatchEventPermissionPolicyStatement, error) {

	log.Printf("[DEBUG] Finding statement (%s) in CloudWatch Events permission policy: %s", id, policy)
	for _, statement := range policy.Statements {
		if statement.Sid == id {
			return &statement, nil
		}
	}

	return nil, &resource.NotFoundError{
		LastRequest:  id,
		LastResponse: policy,
		Message:      fmt.Sprintf("Failed to find statement (%s) in CloudWatch Events permission policy: %s", id, policy),
	}
}

func expandCloudWatchEventsCondition(l []interface{}) *events.Condition {
	if len(l) == 0 || l[0] == nil {
		return nil
	}

	m := l[0].(map[string]interface{})

	condition := &events.Condition{
		Key:   aws.String(m["key"].(string)),
		Type:  aws.String(m["type"].(string)),
		Value: aws.String(m["value"].(string)),
	}

	return condition
}

func flattenCloudWatchEventPermissionPolicyStatementCondition(c *CloudWatchEventPermissionPolicyStatementCondition) []interface{} {
	if c == nil {
		return []interface{}{}
	}

	m := map[string]interface{}{
		"key":   c.Key,
		"type":  c.Type,
		"value": c.Value,
	}

	return []interface{}{m}
}
<|endoftext|>


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

package main

import (
	"flag"
	"fmt"
	"sort"
	"strings"
	"time"

	"github.com/luci/luci-go/common/api/buildbucket/buildbucket/v1"
	"github.com/luci/luci-go/common/auth"
	"github.com/luci/luci-go/common/cli"
	"github.com/maruel/subcommands"
	"golang.org/x/net/context"
)

func cmdInconsistency(authOptions auth.Options) *subcommands.Command {
	return &subcommands.Command{
		UsageLine: `inconsistency`,
		ShortDesc: "finds inconsistencies between buildbot and swarmbucket builders",
		LongDesc:  "Finds inconsistencies between buildbot and swarmbucket builders",
		Advanced:  true,
		CommandRun: func() subcommands.CommandRun {
			r := &inconsistencyRun{}
			r.SetDefaultFlags(authOptions)
			r.Flags.Int64Var(&r.since, "since", 0, "analyze builds since this timestamp. Defaults to 10 days ago.")
			r.Flags.StringVar(&r.bucket, "bucket", "", `buildbucket bucket name, e.g. "master.tryserver.infra"`)
			r.Flags.StringVar(&r.builders, "builder", "", `comma-separated list of builder names without swarming suffix, e.g. "Infra Presubmit"`)
			r.Flags.StringVar(&r.builderSuffix, "builder-suffix", " (Swarming)", "builder name suffix")
			return r
		},
	}
}

type inconsistencyRun struct {
	baseCommandRun
	since         int64
	bucket        string
	builders      string
	builderSuffix string
	client        *buildbucket.Service
}

func (r *inconsistencyRun) Run(a subcommands.Application, args []string, env subcommands.Env) int {
	ctx := cli.GetContext(a, r, env)
	if r.bucket == "" {
		return r.done(ctx, fmt.Errorf("bucket is not specified"))
	}
	if r.builders == "" {
		return r.done(ctx, fmt.Errorf("builders are not specified"))
	}
	if len(args) > 0 {
		return r.done(ctx, fmt.Errorf("unexpected arguments: %s", flag.Args()))
	}

	client, err := r.createClient(ctx)
	if err != nil {
		return r.done(ctx, err)
	}
	r.client, err = buildbucket.New(client.HTTP)
	if err != nil {
		return r.done(ctx, err)
	}
	r.client.BasePath = client.baseURL.String()

	var startingFrom time.Time
	var duration time.Duration
	if r.since == 0 {
		duration = 240 * time.Hour
		startingFrom = time.Now().Add(-duration)
	} else {
		startingFrom = time.Unix(r.since, 0)
		duration = time.Since(startingFrom)
	}

	for i, builder := range strings.Split(r.builders, ",") {
		builder = strings.TrimSpace(builder)
		if builder == "" {
			continue
		}

		if i > 0 {
			fmt.Println()
		}
		fmt.Printf("builder %q\n", builder)
		if err := r.compareBuilder(ctx, builder, startingFrom); err != nil {
			return r.done(ctx, err)
		}
	}
	return 0
}

func (r *inconsistencyRun) compareBuilder(ctx context.Context, builder string, startingFrom time.Time) error {
	fmt.Printf("searching for all builds since timestamp %d till %d...\n",
		startingFrom.Unix(), time.Now().Unix())
	// We will actually fetch builds after after time.Now too, but it is fine.
	swarmingBuilds, err := r.fetchBuilds(r.bucket, builder+r.builderSuffix, startingFrom)
	if err != nil {
		return fmt.Errorf("could not fetch builds: %s", err)
	}
	if len(swarmingBuilds) == 0 {
		fmt.Printf("no swarming builds for builder %q\n", builder)
		return nil
	}
	buildbotBuilds, err := r.fetchBuilds(r.bucket, builder, startingFrom)
	if err != nil {
		return fmt.Errorf("could not fetch builds: %s", err)
	}
	if len(buildbotBuilds) == 0 {
		fmt.Printf("no buildbot builds for builder %q\n", builder)
		return nil
	}

	swarmingBuildSets := groupBuilds(swarmingBuilds)
	buildbotBuildSets := groupBuilds(buildbotBuilds)

	consistentN := 0
	inconsistentN := 0
	for setName, swarmingSet := range swarmingBuildSets {
		buildbotSet := buildbotBuildSets[setName]
		if buildbotSet == nil {
			fmt.Printf("no buildbot builds for buildset %s\n", setName)
			continue
		}
		if buildbotSet.bestResult == swarmingSet.bestResult {
			consistentN++
			continue
		}
		inconsistentN++

		fmt.Printf("%s is inconsistent\n", setName)
		for _, b := range swarmingSet.builds {
			fmt.Printf("  %s %s\n", b.Result, b.Url)
		}
		for _, b := range buildbotSet.builds {
			fmt.Printf("  %s %s\n", b.Result, b.Url)
		}
	}

	fmt.Printf("%0.2f%% consistent build sets, %d buildbot builds, %d swarming builds\n",
		100*float64(consistentN)/float64(consistentN+inconsistentN), len(buildbotBuilds), len(swarmingBuilds))

	swarmingTime := medianTime(swarmingBuilds)
	buildbotTime := medianTime(buildbotBuilds)
	factor := float64(buildbotTime) / float64(swarmingTime)
	if factor >= 1 {
		fmt.Printf("swarming is %.1fx faster\n", factor)
	} else {
		fmt.Printf("swarming is %.1fx slower\n", 1/factor)
	}
	fmt.Printf("median times: buildbot %s, swarming %s\n", buildbotTime, swarmingTime)

	return nil
}

func (r *inconsistencyRun) fetchBuilds(bucket, builder string, startingFrom time.Time) ([]*buildbucket.ApiBuildMessage, error) {
	req := r.client.Search()
	req.Bucket(bucket)
	req.Tag("builder:" + builder)
	req.Status("COMPLETED")
	req.MaxBuilds(100)

	var result []*buildbucket.ApiBuildMessage
	for {
		res, err := req.Do()
		if err != nil {
			return result, err
		}
		if res.Error != nil {
			return result, fmt.Errorf(res.Error.Message)
		}

		for _, b := range res.Builds {
			if parseTimestamp(b.CreatedTs).Before(startingFrom) {
				return result, nil
			}
			result = append(result, b)
		}

		if len(res.Builds) == 0 || res.NextCursor == "" {
			break
		}
		req.StartCursor(res.NextCursor)
	}
	return result, nil
}

type buildSet struct {
	builds     []*buildbucket.ApiBuildMessage
	bestResult string
}

// groupBuilds groups builds by buildset tag.
func groupBuilds(builds []*buildbucket.ApiBuildMessage) map[string]*buildSet {
	results := map[string]*buildSet{}
	for _, b := range builds {
		tags := parseTags(b.Tags)
		buildSetName := tags["buildset"]
		if buildSetName == "" {
			fmt.Printf("skipped build %d: no buildset tag\n", b.Id)
			continue
		}
		set := results[buildSetName]
		if set == nil {
			set = &buildSet{}
			results[buildSetName] = set
		}

		set.builds = append(set.builds, b)
		if set.bestResult == "" || b.Result == "SUCCESS" {
			set.bestResult = b.Result
		}
	}
	return results
}

// medianTime returns median completed_time - created_time of successful builds.
func medianTime(builds []*buildbucket.ApiBuildMessage) time.Duration {
	if len(builds) == 0 {
		return 0
	}
	durations := make(durationSlice, 0, len(builds))
	for _, b := range builds {
		if b.Result != "SUCCESS" {
			continue
		}
		created := parseTimestamp(b.CreatedTs)
		completed := parseTimestamp(b.CompletedTs)
		durations = append(durations, completed.Sub(created))
	}
	sort.Sort(durations)
	return durations[len(durations)/2]
}

func parseTags(tags []string) map[string]string {
	result := make(map[string]string, len(tags))
	for _, t := range tags {
		parts := strings.SplitN(t, ":", 2)
		if len(parts) == 2 {
			result[parts[0]] = parts[1]
		}
	}
	return result
}

func parseTimestamp(ts int64) time.Time {
	if ts == 0 {
		return time.Time{}
	}
	return time.Unix(ts/1000000, 0)
}

type durationSlice []time.Duration

func (a durationSlice) Len() int           { return len(a) }
func (a durationSlice) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a durationSlice) Less(i, j int) bool { return a[i] < a[j] }
<commit_msg>buildbucket tool: simplify inconsistency subcommand<commit_after>// Copyright 2016 The LUCI Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

package main

import (
	"flag"
	"fmt"
	"sort"
	"strings"
	"time"

	"github.com/maruel/subcommands"
	"golang.org/x/net/context"

	"github.com/luci/luci-go/common/api/buildbucket/buildbucket/v1"
	"github.com/luci/luci-go/common/auth"
	"github.com/luci/luci-go/common/cli"
)

func cmdInconsistency(authOptions auth.Options) *subcommands.Command {
	return &subcommands.Command{
		UsageLine: `inconsistency`,
		ShortDesc: "finds inconsistencies between buildbot and swarmbucket builders",
		LongDesc:  "Finds inconsistencies between buildbot and swarmbucket builders",
		Advanced:  true,
		CommandRun: func() subcommands.CommandRun {
			r := &inconsistencyRun{}
			r.SetDefaultFlags(authOptions)
			r.Flags.Int64Var(&r.since, "since", 0, "analyze builds since this timestamp. Defaults to 10 days ago.")
			r.Flags.Var(&r.builder1, "builder1", `colon-separated bucket and builder, e.g. "master.tryserver.chromium.linux:linux_chromium_rel_ng"`)
			r.Flags.Var(&r.builder2, "builder2", `colon-separated bucket and builder of the alternative builder to compare to"`)
			return r
		},
	}
}

type inconsistencyRun struct {
	baseCommandRun
	since              int64
	builder1, builder2 builderID
	client             *buildbucket.Service
}

type builderID struct {
	Bucket  string
	Builder string
}

func (b *builderID) Set(v string) error {
	parts := strings.SplitN(v, ":", 2)
	if len(parts) != 2 {
		return fmt.Errorf("does not have ':'")
	}
	parsed := builderID{parts[0], parts[1]}
	if err := parsed.Validate(); err != nil {
		return err
	}
	*b = parsed
	return nil
}

func (b builderID) String() string {
	return b.Bucket + ":" + b.Builder
}

func (b *builderID) Validate() error {
	if b.Bucket == "" {
		return fmt.Errorf("bucket unspecified")
	}
	if b.Builder == "" {
		return fmt.Errorf("builder unspecified")
	}
	return nil
}

func (r *inconsistencyRun) Run(a subcommands.Application, args []string, env subcommands.Env) int {
	ctx := cli.GetContext(a, r, env)
	if len(args) > 0 {
		return r.done(ctx, fmt.Errorf("unexpected arguments: %s", flag.Args()))
	}

	if err := r.builder1.Validate(); err != nil {
		return r.done(ctx, fmt.Errorf("invalid -builder1: %s", err))
	}
	if err := r.builder2.Validate(); err != nil {
		return r.done(ctx, fmt.Errorf("invalid -builder2: %s", err))
	}

	client, err := r.createClient(ctx)
	if err != nil {
		return r.done(ctx, err)
	}
	r.client, err = buildbucket.New(client.HTTP)
	if err != nil {
		return r.done(ctx, err)
	}
	r.client.BasePath = client.baseURL.String()

	var startingFrom time.Time
	var duration time.Duration
	if r.since == 0 {
		duration = 240 * time.Hour
		startingFrom = time.Now().Add(-duration)
	} else {
		startingFrom = time.Unix(r.since, 0)
		duration = time.Since(startingFrom)
	}

	if err := r.compareBuilder(ctx, startingFrom); err != nil {
		return r.done(ctx, err)
	}
	return 0
}

func (r *inconsistencyRun) compareBuilder(ctx context.Context, startingFrom time.Time) error {
	fmt.Printf("searching for all builds since timestamp %d till %d...\n",
		startingFrom.Unix(), time.Now().Unix())
	// We will actually fetch builds after after time.Now too, but it is fine.
	builds1, err := r.fetchBuilds(r.builder1, startingFrom)
	if err != nil {
		return fmt.Errorf("could not fetch %s builds: %s", r.builder1, err)
	}
	if len(builds1) == 0 {
		fmt.Printf("no %s builds\n", r.builder1)
		return nil
	}

	builds2, err := r.fetchBuilds(r.builder2, startingFrom)
	if err != nil {
		return fmt.Errorf("could not fetch %s builds: %s", r.builder2, err)
	}
	if len(builds2) == 0 {
		fmt.Printf("no %s builds\n", r.builder2)
		return nil
	}

	buildSets1 := groupBuilds(builds1)
	buildSets2 := groupBuilds(builds2)

	consistentN := 0
	inconsistentN := 0
	for setName, set2 := range buildSets2 {
		set1 := buildSets1[setName]
		if set1 == nil {
			fmt.Printf("no %s builds for buildset %s\n", r.builder1, setName)
			continue
		}
		if set1.bestResult == set2.bestResult {
			consistentN++
			continue
		}
		inconsistentN++

		fmt.Printf("%s is inconsistent\n", setName)
		for _, b := range set2.builds {
			fmt.Printf("  %s %s\n", b.Result, b.Url)
		}
		for _, b := range set1.builds {
			fmt.Printf("  %s %s\n", b.Result, b.Url)
		}
	}

	fmt.Printf("%0.2f%% consistent build sets, %d %s builds, %d %s builds\n",
		100*float64(consistentN)/float64(consistentN+inconsistentN),
		len(builds1), r.builder1,
		len(builds2), r.builder2)

	time1 := medianTime(builds1)
	time2 := medianTime(builds2)
	factor := float64(time1) / float64(time2)
	if factor >= 1 {
		fmt.Printf("%s is %.1fx faster\n", r.builder2, factor)
	} else {
		fmt.Printf("%s is %.1fx slower\n", r.builder2, 1/factor)
	}
	fmt.Printf("%s median time: %s\n", r.builder1, time1)
	fmt.Printf("%s median time: %s\n", r.builder2, time2)
	return nil
}

func (r *inconsistencyRun) fetchBuilds(builder builderID, startingFrom time.Time) ([]*buildbucket.ApiBuildMessage, error) {
	req := r.client.Search()
	req.Bucket(builder.Bucket)
	req.Tag("builder:" + builder.Builder)
	req.Status("COMPLETED")
	req.MaxBuilds(100)

	var result []*buildbucket.ApiBuildMessage
	for {
		res, err := req.Do()
		if err != nil {
			return result, err
		}
		if res.Error != nil {
			return result, fmt.Errorf(res.Error.Message)
		}

		for _, b := range res.Builds {
			if parseTimestamp(b.CreatedTs).Before(startingFrom) {
				return result, nil
			}
			result = append(result, b)
		}

		if len(res.Builds) == 0 || res.NextCursor == "" {
			break
		}
		req.StartCursor(res.NextCursor)
	}
	return result, nil
}

type buildSet struct {
	builds     []*buildbucket.ApiBuildMessage
	bestResult string
}

// groupBuilds groups builds by buildset tag.
func groupBuilds(builds []*buildbucket.ApiBuildMessage) map[string]*buildSet {
	results := map[string]*buildSet{}
	for _, b := range builds {
		tags := parseTags(b.Tags)
		buildSetName := tags["buildset"]
		if buildSetName == "" {
			fmt.Printf("skipped build %d: no buildset tag\n", b.Id)
			continue
		}
		set := results[buildSetName]
		if set == nil {
			set = &buildSet{}
			results[buildSetName] = set
		}

		set.builds = append(set.builds, b)
		if set.bestResult == "" || b.Result == "SUCCESS" {
			set.bestResult = b.Result
		}
	}
	return results
}

// medianTime returns median completed_time - created_time of successful builds.
func medianTime(builds []*buildbucket.ApiBuildMessage) time.Duration {
	if len(builds) == 0 {
		return 0
	}
	durations := make(durationSlice, 0, len(builds))
	for _, b := range builds {
		if b.Result != "SUCCESS" {
			continue
		}
		created := parseTimestamp(b.CreatedTs)
		completed := parseTimestamp(b.CompletedTs)
		durations = append(durations, completed.Sub(created))
	}
	sort.Sort(durations)
	return durations[len(durations)/2]
}

func parseTags(tags []string) map[string]string {
	result := make(map[string]string, len(tags))
	for _, t := range tags {
		parts := strings.SplitN(t, ":", 2)
		if len(parts) == 2 {
			result[parts[0]] = parts[1]
		}
	}
	return result
}

func parseTimestamp(ts int64) time.Time {
	if ts == 0 {
		return time.Time{}
	}
	return time.Unix(ts/1000000, 0)
}

type durationSlice []time.Duration

func (a durationSlice) Len() int           { return len(a) }
func (a durationSlice) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a durationSlice) Less(i, j int) bool { return a[i] < a[j] }
<|endoftext|>


<commit_before>// Package dockerstats provides the ability to get currently running Docker container statistics,
// including memory and CPU usage.
//
// To get the statistics of running Docker containers, you can use the `Current()` function:
//
// 		stats, err := dockerstats.Current()
//		if err != nil {
//			panic(err)
//		}
//
//		for _, s := range stats {
//			fmt.Println(s.Container) // 9f2656020722
//			fmt.Println(s.Memory) // {Raw=221.7 MiB / 7.787 GiB, Percent=2.78%}
//			fmt.Println(s.CPU) // 99.79%
//		}
//
// Alternatively, you can use the `Monitor()` function to receive a constant stream of Docker container stats:
//
// 		c := dockerstats.Monitor()
//
// 		for {
// 			res := <-c
//			if res.Error != nil {
//				panic(err)
//			}
//
//			for _, con := range res.Stats {
//				fmt.Println(con.Container) // 9f2656020722
//			}
// 		}
package dockerstats

import (
	"encoding/json"
	"os/exec"
	"strings"
)

const (
	dockerPath        string = "/usr/local/bin/docker"
	dockerCommand     string = "stats"
	dockerNoStreamArg string = "--no-stream"
	dockerFormatArg   string = "--format"
	dockerFormat      string = `{"container":"{{ .Container }}","memory":{"raw":"{{ .MemUsage }}","percent":"{{ .MemPerc }}"},"cpu":"{{ .CPUPerc }}"}`
)

// Monitor repeatedly retrieves the current stats for each running Docker container,
// and sends them through the channel provided.
//
// Each `StatsResult` sent through the channel contains either an `error` or a
// `Stats` slice equal in length to the number of running Docker containers.
func Monitor() chan *StatsResult {
	c := make(chan *StatsResult)
	go func() {
		for {
			println("HERE")
			s, err := Current()
			c <- &StatsResult{
				Stats: s,
				Error: err,
			}
		}
	}()

	return c
}

// Current returns the current `Stats` of each running Docker container.
//
// Current will always return a `[]Stats` slice equal in length to the number of
// running Docker containers, or an `error`. No error is returned if there are no
// running Docker containers, simply an empty slice.
func Current() ([]Stats, error) {
	out, err := exec.Command(dockerPath, dockerCommand, dockerNoStreamArg, dockerFormatArg, dockerFormat).Output()
	if err != nil {
		return nil, err
	}

	containers := strings.Split(string(out), "\n")
	stats := make([]Stats, 0)
	for _, con := range containers {
		if len(con) == 0 {
			continue
		}

		var s Stats
		if err := json.Unmarshal([]byte(con), &s); err != nil {
			return nil, err
		}

		stats = append(stats, s)
	}

	return stats, nil
}
<commit_msg>Late night bug fixes<commit_after>// Package dockerstats provides the ability to get currently running Docker container statistics,
// including memory and CPU usage.
//
// To get the statistics of running Docker containers, you can use the `Current()` function:
//
// 		stats, err := dockerstats.Current()
//		if err != nil {
//			panic(err)
//		}
//
//		for _, s := range stats {
//			fmt.Println(s.Container) // 9f2656020722
//			fmt.Println(s.Memory) // {Raw=221.7 MiB / 7.787 GiB, Percent=2.78%}
//			fmt.Println(s.CPU) // 99.79%
//		}
//
// Alternatively, you can use the `Monitor()` function to receive a constant stream of Docker container stats:
//
// 		c := dockerstats.Monitor()
//
// 		for {
// 			res := <-c
//			if res.Error != nil {
//				panic(err)
//			}
//
//			for _, con := range res.Stats {
//				fmt.Println(con.Container) // 9f2656020722
//			}
// 		}
package dockerstats

import (
	"encoding/json"
	"os/exec"
	"strings"
)

const (
	dockerPath        string = "/usr/local/bin/docker"
	dockerCommand     string = "stats"
	dockerNoStreamArg string = "--no-stream"
	dockerFormatArg   string = "--format"
	dockerFormat      string = `{"container":"{{ .Container }}","memory":{"raw":"{{ .MemUsage }}","percent":"{{ .MemPerc }}"},"cpu":"{{ .CPUPerc }}"}`
)

// Monitor repeatedly retrieves the current stats for each running Docker container,
// and sends them through the channel provided.
//
// Each `StatsResult` sent through the channel contains either an `error` or a
// `Stats` slice equal in length to the number of running Docker containers.
func Monitor() chan *StatsResult {
	c := make(chan *StatsResult)
	go func() {
		for {
			s, err := Current()
			c <- &StatsResult{
				Stats: s,
				Error: err,
			}
		}
	}()

	return c
}

// Current returns the current `Stats` of each running Docker container.
//
// Current will always return a `[]Stats` slice equal in length to the number of
// running Docker containers, or an `error`. No error is returned if there are no
// running Docker containers, simply an empty slice.
func Current() ([]Stats, error) {
	out, err := exec.Command(dockerPath, dockerCommand, dockerNoStreamArg, dockerFormatArg, dockerFormat).Output()
	if err != nil {
		return nil, err
	}

	containers := strings.Split(string(out), "\n")
	stats := make([]Stats, 0)
	for _, con := range containers {
		if len(con) == 0 {
			continue
		}

		var s Stats
		if err := json.Unmarshal([]byte(con), &s); err != nil {
			return nil, err
		}

		stats = append(stats, s)
	}

	return stats, nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"net/http"
	"os"

	mp "github.com/mackerelio/go-mackerel-plugin"
	"github.com/mackerelio/mackerel-agent/logging"
)

var logger = logging.GetLogger("metrics.plugin.elasticsearch")

var graphdef = map[string](mp.Graphs){
	"elasticsearch.http": mp.Graphs{
		Label: "Elasticsearch HTTP",
		Unit:  "integer",
		Metrics: [](mp.Metrics){
			mp.Metrics{Name: "http_opened", Label: "Opened", Diff: true},
		},
	},
	"elasticsearch.indices": mp.Graphs{
		Label: "Elasticsearch Indices",
		Unit:  "integer",
		Metrics: [](mp.Metrics){
			mp.Metrics{Name: "total_indexing_index", Label: "Indexing-Index", Diff: true, Stacked: true},
			mp.Metrics{Name: "total_indexing_delete", Label: "Indexing-Delete", Diff: true, Stacked: true},
			mp.Metrics{Name: "total_get", Label: "Get", Diff: true, Stacked: true},
			mp.Metrics{Name: "total_search_query", Label: "Search-Query", Diff: true, Stacked: true},
			mp.Metrics{Name: "total_search_fetch", Label: "Search-fetch", Diff: true, Stacked: true},
			mp.Metrics{Name: "total_merges", Label: "Merges", Diff: true, Stacked: true},
			mp.Metrics{Name: "total_refresh", Label: "Refresh", Diff: true, Stacked: true},
			mp.Metrics{Name: "total_flush", Label: "Flush", Diff: true, Stacked: true},
			mp.Metrics{Name: "total_warmer", Label: "Warmer", Diff: true, Stacked: true},
			mp.Metrics{Name: "total_percolate", Label: "Percolate", Diff: true, Stacked: true},
			mp.Metrics{Name: "total_suggest", Label: "Suggest", Diff: true, Stacked: true},
		},
	},
	"elasticsearch.indices.docs": mp.Graphs{
		Label: "Elasticsearch Indices Docs",
		Unit:  "integer",
		Metrics: [](mp.Metrics){
			mp.Metrics{Name: "docs_count", Label: "Count", Stacked: true},
			mp.Metrics{Name: "docs_deleted", Label: "Deleted", Stacked: true},
		},
	},
	"elasticsearch.indices.memory_size": mp.Graphs{
		Label: "Elasticsearch Indices Memory Size",
		Unit:  "bytes",
		Metrics: [](mp.Metrics){
			mp.Metrics{Name: "fielddata_size", Label: "Fielddata", Stacked: true},
			mp.Metrics{Name: "filter_cache_size", Label: "Filter Cache", Stacked: true},
			mp.Metrics{Name: "segments_size", Label: "Lucene Segments", Stacked: true},
			mp.Metrics{Name: "segments_index_writer_size", Label: "Lucene Segments Index Writer", Stacked: true},
			mp.Metrics{Name: "segments_version_map_size", Label: "Lucene Segments Version Map", Stacked: true},
			mp.Metrics{Name: "segments_fixed_bit_set_size", Label: "Lucene Segments Fixed Bit Set", Stacked: true},
		},
	},
	"elasticsearch.indices.evictions": mp.Graphs{
		Label: "Elasticsearch Indices Evictions",
		Unit:  "integer",
		Metrics: [](mp.Metrics){
			mp.Metrics{Name: "evictions_fielddata", Label: "Fielddata", Diff: true},
			mp.Metrics{Name: "evictions_filter_cache", Label: "Filter Cache", Diff: true},
		},
	},
	"elasticsearch.jvm.heap": mp.Graphs{
		Label: "Elasticsearch JVM Heap Mem",
		Unit:  "bytes",
		Metrics: [](mp.Metrics){
			mp.Metrics{Name: "heap_used", Label: "Used"},
			mp.Metrics{Name: "heap_max", Label: "Max"},
		},
	},
	"elasticsearch.thread_pool.threads": mp.Graphs{
		Label: "Elasticsearch Thread-Pool Threads",
		Unit:  "integer",
		Metrics: [](mp.Metrics){
			mp.Metrics{Name: "threads_generic", Label: "Generic", Stacked: true},
			mp.Metrics{Name: "threads_index", Label: "Index", Stacked: true},
			mp.Metrics{Name: "threads_snapshot_data", Label: "Snapshot Data", Stacked: true},
			mp.Metrics{Name: "threads_get", Label: "Get", Stacked: true},
			mp.Metrics{Name: "threads_bench", Label: "Bench", Stacked: true},
			mp.Metrics{Name: "threads_snapshot", Label: "Snapshot", Stacked: true},
			mp.Metrics{Name: "threads_merge", Label: "Merge", Stacked: true},
			mp.Metrics{Name: "threads_suggest", Label: "Suggest", Stacked: true},
			mp.Metrics{Name: "threads_bulk", Label: "Bulk", Stacked: true},
			mp.Metrics{Name: "threads_optimize", Label: "Optimize", Stacked: true},
			mp.Metrics{Name: "threads_warmer", Label: "Warmer", Stacked: true},
			mp.Metrics{Name: "threads_flush", Label: "Flush", Stacked: true},
			mp.Metrics{Name: "threads_search", Label: "Search", Stacked: true},
			mp.Metrics{Name: "threads_percolate", Label: "Percolate", Stacked: true},
			mp.Metrics{Name: "threads_refresh", Label: "Refresh", Stacked: true},
			mp.Metrics{Name: "threads_management", Label: "Management", Stacked: true},
			mp.Metrics{Name: "threads_fetch_shard_started", Label: "Fetch Shard Started", Stacked: true},
			mp.Metrics{Name: "threads_fetch_shard_store", Label: "Fetch Shard Store", Stacked: true},
			mp.Metrics{Name: "threads_listener", Label: "Listener", Stacked: true},
		},
	},
	"elasticsearch.transport.count": mp.Graphs{
		Label: "Elasticsearch Transport Count",
		Unit:  "integer",
		Metrics: [](mp.Metrics){
			mp.Metrics{Name: "count_rx", Label: "TX", Diff: true},
			mp.Metrics{Name: "count_tx", Label: "RX", Diff: true},
		},
	},
}

var metricPlace = map[string][]string{
	"http_opened":                 []string{"http", "total_opened"},
	"total_indexing_index":        []string{"indices", "indexing", "index_total"},
	"total_indexing_delete":       []string{"indices", "indexing", "delete_total"},
	"total_get":                   []string{"indices", "get", "total"},
	"total_search_query":          []string{"indices", "search", "query_total"},
	"total_search_fetch":          []string{"indices", "search", "fetch_total"},
	"total_merges":                []string{"indices", "merges", "total"},
	"total_refresh":               []string{"indices", "refresh", "total"},
	"total_flush":                 []string{"indices", "flush", "total"},
	"total_warmer":                []string{"indices", "warmer", "total"},
	"total_percolate":             []string{"indices", "percolate", "total"},
	"total_suggest":               []string{"indices", "suggest", "total"},
	"docs_count":                  []string{"indices", "docs", "count"},
	"docs_deleted":                []string{"indices", "docs", "deleted"},
	"fielddata_size":              []string{"indices", "fielddata", "memory_size_in_bytes"},
	"filter_cache_size":           []string{"indices", "filter_cache", "memory_size_in_bytes"},
	"segments_size":               []string{"indices", "segments", "memory_in_bytes"},
	"segments_index_writer_size":  []string{"indices", "segments", "index_writer_memory_in_bytes"},
	"segments_version_map_size":   []string{"indices", "segments", "version_map_memory_in_bytes"},
	"segments_fixed_bit_set_size": []string{"indices", "segments", "fixed_bit_set_memory_in_bytes"},
	"evictions_fielddata":         []string{"indices", "fielddata", "evictions"},
	"evictions_filter_cache":      []string{"indices", "filter_cache", "evictions"},
	"heap_used":                   []string{"jvm", "mem", "heap_used_in_bytes"},
	"heap_max":                    []string{"jvm", "mem", "heap_max_in_bytes"},
	"threads_generic":             []string{"thread_pool", "generic", "threads"},
	"threads_index":               []string{"thread_pool", "index", "threads"},
	"threads_snapshot_data":       []string{"thread_pool", "snapshot_data", "threads"},
	"threads_get":                 []string{"thread_pool", "get", "threads"},
	"threads_bench":               []string{"thread_pool", "bench", "threads"},
	"threads_snapshot":            []string{"thread_pool", "snapshot", "threads"},
	"threads_merge":               []string{"thread_pool", "merge", "threads"},
	"threads_suggest":             []string{"thread_pool", "suggest", "threads"},
	"threads_bulk":                []string{"thread_pool", "bulk", "threads"},
	"threads_optimize":            []string{"thread_pool", "optimize", "threads"},
	"threads_warmer":              []string{"thread_pool", "warmer", "threads"},
	"threads_flush":               []string{"thread_pool", "flush", "threads"},
	"threads_search":              []string{"thread_pool", "search", "threads"},
	"threads_percolate":           []string{"thread_pool", "percolate", "threads"},
	"threads_refresh":             []string{"thread_pool", "refresh", "threads"},
	"threads_management":          []string{"thread_pool", "management", "threads"},
	"threads_fetch_shard_started": []string{"thread_pool", "fetch_shard_started", "threads"},
	"threads_fetch_shard_store":   []string{"thread_pool", "fetch_shard_store", "threads"},
	"threads_listener":            []string{"thread_pool", "listener", "threads"},
	"count_rx":                    []string{"transport", "rx_count"},
	"count_tx":                    []string{"transport", "tx_count"},
}

func getFloatValue(s map[string]interface{}, keys []string) (float64, error) {
	var val float64
	sm := s
	for i, k := range keys {
		if i+1 < len(keys) {
			switch sm[k].(type) {
			case map[string]interface{}:
				sm = sm[k].(map[string]interface{})
			default:
				return 0, errors.New("Cannot handle as a hash")
			}
		} else {
			switch sm[k].(type) {
			case float64:
				val = sm[k].(float64)
			default:
				return 0, errors.New("Not float64")
			}
		}
	}

	return val, nil
}

// ElasticsearchPlugin mackerel plugin for Elasticsearch
type ElasticsearchPlugin struct {
	URI string
}

// FetchMetrics interface for mackerelplugin
func (p ElasticsearchPlugin) FetchMetrics() (map[string]float64, error) {
	resp, err := http.Get(p.URI + "/_nodes/_local/stats")
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	stat := make(map[string]float64)
	decoder := json.NewDecoder(resp.Body)

	var s map[string]interface{}
	err = decoder.Decode(&s)
	if err != nil {
		return nil, err
	}

	nodes := s["nodes"].(map[string]interface{})
	n := ""
	for k := range nodes {
		if n != "" {
			return nil, errors.New("Multiple node found")
		}
		n = k
	}
	node := nodes[n].(map[string]interface{})

	for k, v := range metricPlace {
		val, err := getFloatValue(node, v)
		if err != nil {
			logger.Errorf("Failed to find '%s': %s", k, err)
			continue
		}

		stat[k] = val
	}

	return stat, nil
}

// GraphDefinition interface for mackerelplugin
func (p ElasticsearchPlugin) GraphDefinition() map[string](mp.Graphs) {
	return graphdef
}

func main() {
	optScheme := flag.String("scheme", "http", "Scheme")
	optHost := flag.String("host", "localhost", "Host")
	optPort := flag.String("port", "9200", "Port")
	optTempfile := flag.String("tempfile", "", "Temp file name")
	flag.Parse()

	var elasticsearch ElasticsearchPlugin
	elasticsearch.URI = fmt.Sprintf("%s://%s:%s", *optScheme, *optHost, *optPort)

	helper := mp.NewMackerelPlugin(elasticsearch)
	if *optTempfile != "" {
		helper.Tempfile = *optTempfile
	} else {
		helper.Tempfile = fmt.Sprintf("/tmp/mackerel-plugin-elasticsearch-%s-%s", *optHost, *optPort)
	}

	if os.Getenv("MACKEREL_AGENT_PLUGIN_META") != "" {
		helper.OutputDefinitions()
	} else {
		helper.OutputValues()
	}
}
<commit_msg>add metric key and label prefix option<commit_after>package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"net/http"
	"os"
	"strings"

	mp "github.com/mackerelio/go-mackerel-plugin"
	"github.com/mackerelio/mackerel-agent/logging"
)

var logger = logging.GetLogger("metrics.plugin.elasticsearch")

var metricPlace = map[string][]string{
	"http_opened":                 []string{"http", "total_opened"},
	"total_indexing_index":        []string{"indices", "indexing", "index_total"},
	"total_indexing_delete":       []string{"indices", "indexing", "delete_total"},
	"total_get":                   []string{"indices", "get", "total"},
	"total_search_query":          []string{"indices", "search", "query_total"},
	"total_search_fetch":          []string{"indices", "search", "fetch_total"},
	"total_merges":                []string{"indices", "merges", "total"},
	"total_refresh":               []string{"indices", "refresh", "total"},
	"total_flush":                 []string{"indices", "flush", "total"},
	"total_warmer":                []string{"indices", "warmer", "total"},
	"total_percolate":             []string{"indices", "percolate", "total"},
	"total_suggest":               []string{"indices", "suggest", "total"},
	"docs_count":                  []string{"indices", "docs", "count"},
	"docs_deleted":                []string{"indices", "docs", "deleted"},
	"fielddata_size":              []string{"indices", "fielddata", "memory_size_in_bytes"},
	"filter_cache_size":           []string{"indices", "filter_cache", "memory_size_in_bytes"},
	"segments_size":               []string{"indices", "segments", "memory_in_bytes"},
	"segments_index_writer_size":  []string{"indices", "segments", "index_writer_memory_in_bytes"},
	"segments_version_map_size":   []string{"indices", "segments", "version_map_memory_in_bytes"},
	"segments_fixed_bit_set_size": []string{"indices", "segments", "fixed_bit_set_memory_in_bytes"},
	"evictions_fielddata":         []string{"indices", "fielddata", "evictions"},
	"evictions_filter_cache":      []string{"indices", "filter_cache", "evictions"},
	"heap_used":                   []string{"jvm", "mem", "heap_used_in_bytes"},
	"heap_max":                    []string{"jvm", "mem", "heap_max_in_bytes"},
	"threads_generic":             []string{"thread_pool", "generic", "threads"},
	"threads_index":               []string{"thread_pool", "index", "threads"},
	"threads_snapshot_data":       []string{"thread_pool", "snapshot_data", "threads"},
	"threads_get":                 []string{"thread_pool", "get", "threads"},
	"threads_bench":               []string{"thread_pool", "bench", "threads"},
	"threads_snapshot":            []string{"thread_pool", "snapshot", "threads"},
	"threads_merge":               []string{"thread_pool", "merge", "threads"},
	"threads_suggest":             []string{"thread_pool", "suggest", "threads"},
	"threads_bulk":                []string{"thread_pool", "bulk", "threads"},
	"threads_optimize":            []string{"thread_pool", "optimize", "threads"},
	"threads_warmer":              []string{"thread_pool", "warmer", "threads"},
	"threads_flush":               []string{"thread_pool", "flush", "threads"},
	"threads_search":              []string{"thread_pool", "search", "threads"},
	"threads_percolate":           []string{"thread_pool", "percolate", "threads"},
	"threads_refresh":             []string{"thread_pool", "refresh", "threads"},
	"threads_management":          []string{"thread_pool", "management", "threads"},
	"threads_fetch_shard_started": []string{"thread_pool", "fetch_shard_started", "threads"},
	"threads_fetch_shard_store":   []string{"thread_pool", "fetch_shard_store", "threads"},
	"threads_listener":            []string{"thread_pool", "listener", "threads"},
	"count_rx":                    []string{"transport", "rx_count"},
	"count_tx":                    []string{"transport", "tx_count"},
}

func getFloatValue(s map[string]interface{}, keys []string) (float64, error) {
	var val float64
	sm := s
	for i, k := range keys {
		if i+1 < len(keys) {
			switch sm[k].(type) {
			case map[string]interface{}:
				sm = sm[k].(map[string]interface{})
			default:
				return 0, errors.New("Cannot handle as a hash")
			}
		} else {
			switch sm[k].(type) {
			case float64:
				val = sm[k].(float64)
			default:
				return 0, errors.New("Not float64")
			}
		}
	}

	return val, nil
}

// ElasticsearchPlugin mackerel plugin for Elasticsearch
type ElasticsearchPlugin struct {
	URI         string
	Prefix      string
	LabelPrefix string
}

// FetchMetrics interface for mackerelplugin
func (p ElasticsearchPlugin) FetchMetrics() (map[string]float64, error) {
	resp, err := http.Get(p.URI + "/_nodes/_local/stats")
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	stat := make(map[string]float64)
	decoder := json.NewDecoder(resp.Body)

	var s map[string]interface{}
	err = decoder.Decode(&s)
	if err != nil {
		return nil, err
	}

	nodes := s["nodes"].(map[string]interface{})
	n := ""
	for k := range nodes {
		if n != "" {
			return nil, errors.New("Multiple node found")
		}
		n = k
	}
	node := nodes[n].(map[string]interface{})

	for k, v := range metricPlace {
		val, err := getFloatValue(node, v)
		if err != nil {
			logger.Errorf("Failed to find '%s': %s", k, err)
			continue
		}

		stat[k] = val
	}

	return stat, nil
}

// GraphDefinition interface for mackerelplugin
func (p ElasticsearchPlugin) GraphDefinition() map[string](mp.Graphs) {
	var graphdef = map[string](mp.Graphs){
		p.Prefix + ".http": mp.Graphs{
			Label: (p.LabelPrefix + " HTTP"),
			Unit:  "integer",
			Metrics: [](mp.Metrics){
				mp.Metrics{Name: "http_opened", Label: "Opened", Diff: true},
			},
		},
		p.Prefix + ".indices": mp.Graphs{
			Label: (p.LabelPrefix + " Indices"),
			Unit:  "integer",
			Metrics: [](mp.Metrics){
				mp.Metrics{Name: "total_indexing_index", Label: "Indexing-Index", Diff: true, Stacked: true},
				mp.Metrics{Name: "total_indexing_delete", Label: "Indexing-Delete", Diff: true, Stacked: true},
				mp.Metrics{Name: "total_get", Label: "Get", Diff: true, Stacked: true},
				mp.Metrics{Name: "total_search_query", Label: "Search-Query", Diff: true, Stacked: true},
				mp.Metrics{Name: "total_search_fetch", Label: "Search-fetch", Diff: true, Stacked: true},
				mp.Metrics{Name: "total_merges", Label: "Merges", Diff: true, Stacked: true},
				mp.Metrics{Name: "total_refresh", Label: "Refresh", Diff: true, Stacked: true},
				mp.Metrics{Name: "total_flush", Label: "Flush", Diff: true, Stacked: true},
				mp.Metrics{Name: "total_warmer", Label: "Warmer", Diff: true, Stacked: true},
				mp.Metrics{Name: "total_percolate", Label: "Percolate", Diff: true, Stacked: true},
				mp.Metrics{Name: "total_suggest", Label: "Suggest", Diff: true, Stacked: true},
			},
		},
		p.Prefix + ".indices.docs": mp.Graphs{
			Label: (p.LabelPrefix + " Indices Docs"),
			Unit:  "integer",
			Metrics: [](mp.Metrics){
				mp.Metrics{Name: "docs_count", Label: "Count", Stacked: true},
				mp.Metrics{Name: "docs_deleted", Label: "Deleted", Stacked: true},
			},
		},
		p.Prefix + ".indices.memory_size": mp.Graphs{
			Label: (p.LabelPrefix + " Indices Memory Size"),
			Unit:  "bytes",
			Metrics: [](mp.Metrics){
				mp.Metrics{Name: "fielddata_size", Label: "Fielddata", Stacked: true},
				mp.Metrics{Name: "filter_cache_size", Label: "Filter Cache", Stacked: true},
				mp.Metrics{Name: "segments_size", Label: "Lucene Segments", Stacked: true},
				mp.Metrics{Name: "segments_index_writer_size", Label: "Lucene Segments Index Writer", Stacked: true},
				mp.Metrics{Name: "segments_version_map_size", Label: "Lucene Segments Version Map", Stacked: true},
				mp.Metrics{Name: "segments_fixed_bit_set_size", Label: "Lucene Segments Fixed Bit Set", Stacked: true},
			},
		},
		p.Prefix + ".indices.evictions": mp.Graphs{
			Label: (p.LabelPrefix + " Indices Evictions"),
			Unit:  "integer",
			Metrics: [](mp.Metrics){
				mp.Metrics{Name: "evictions_fielddata", Label: "Fielddata", Diff: true},
				mp.Metrics{Name: "evictions_filter_cache", Label: "Filter Cache", Diff: true},
			},
		},
		p.Prefix + ".jvm.heap": mp.Graphs{
			Label: (p.LabelPrefix + " JVM Heap Mem"),
			Unit:  "bytes",
			Metrics: [](mp.Metrics){
				mp.Metrics{Name: "heap_used", Label: "Used"},
				mp.Metrics{Name: "heap_max", Label: "Max"},
			},
		},
		p.Prefix + ".thread_pool.threads": mp.Graphs{
			Label: (p.LabelPrefix + " Thread-Pool Threads"),
			Unit:  "integer",
			Metrics: [](mp.Metrics){
				mp.Metrics{Name: "threads_generic", Label: "Generic", Stacked: true},
				mp.Metrics{Name: "threads_index", Label: "Index", Stacked: true},
				mp.Metrics{Name: "threads_snapshot_data", Label: "Snapshot Data", Stacked: true},
				mp.Metrics{Name: "threads_get", Label: "Get", Stacked: true},
				mp.Metrics{Name: "threads_bench", Label: "Bench", Stacked: true},
				mp.Metrics{Name: "threads_snapshot", Label: "Snapshot", Stacked: true},
				mp.Metrics{Name: "threads_merge", Label: "Merge", Stacked: true},
				mp.Metrics{Name: "threads_suggest", Label: "Suggest", Stacked: true},
				mp.Metrics{Name: "threads_bulk", Label: "Bulk", Stacked: true},
				mp.Metrics{Name: "threads_optimize", Label: "Optimize", Stacked: true},
				mp.Metrics{Name: "threads_warmer", Label: "Warmer", Stacked: true},
				mp.Metrics{Name: "threads_flush", Label: "Flush", Stacked: true},
				mp.Metrics{Name: "threads_search", Label: "Search", Stacked: true},
				mp.Metrics{Name: "threads_percolate", Label: "Percolate", Stacked: true},
				mp.Metrics{Name: "threads_refresh", Label: "Refresh", Stacked: true},
				mp.Metrics{Name: "threads_management", Label: "Management", Stacked: true},
				mp.Metrics{Name: "threads_fetch_shard_started", Label: "Fetch Shard Started", Stacked: true},
				mp.Metrics{Name: "threads_fetch_shard_store", Label: "Fetch Shard Store", Stacked: true},
				mp.Metrics{Name: "threads_listener", Label: "Listener", Stacked: true},
			},
		},
		p.Prefix + ".transport.count": mp.Graphs{
			Label: (p.LabelPrefix + " Transport Count"),
			Unit:  "integer",
			Metrics: [](mp.Metrics){
				mp.Metrics{Name: "count_rx", Label: "TX", Diff: true},
				mp.Metrics{Name: "count_tx", Label: "RX", Diff: true},
			},
		},
	}

	return graphdef
}

func main() {
	optScheme := flag.String("scheme", "http", "Scheme")
	optHost := flag.String("host", "localhost", "Host")
	optPort := flag.String("port", "9200", "Port")
	optPrefix := flag.String("metric-key-prefix", "elasticsearch", "Metric key prefix")
	optLabelPrefix := flag.String("metric-label-prefix", "", "Metric Label prefix")
	optTempfile := flag.String("tempfile", "", "Temp file name")
	flag.Parse()

	var elasticsearch ElasticsearchPlugin
	elasticsearch.URI = fmt.Sprintf("%s://%s:%s", *optScheme, *optHost, *optPort)
	elasticsearch.Prefix = *optPrefix
	if *optLabelPrefix == "" {
		elasticsearch.LabelPrefix = strings.Title(*optPrefix)
	} else {
		elasticsearch.LabelPrefix = *optLabelPrefix
	}

	helper := mp.NewMackerelPlugin(elasticsearch)
	if *optTempfile != "" {
		helper.Tempfile = *optTempfile
	} else {
		helper.Tempfile = fmt.Sprintf("/tmp/mackerel-plugin-elasticsearch-%s-%s", *optHost, *optPort)
	}

	if os.Getenv("MACKEREL_AGENT_PLUGIN_META") != "" {
		helper.OutputDefinitions()
	} else {
		helper.OutputValues()
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"time"
)

type ConfigFile struct {
	Mongo string
	Mq    struct {
		Host          string
		Port          int
		ComponentUser string
		Password      string
		Vhost         string
	}
}

type Domain struct {
	Username string
	Name     string
	Key      string
	FullUrl  string
}

type DomainInfo struct {
	Domains map[string]Domain `json:"domains"`
}

type ServerInfo struct {
	BuildNumber string
	GitBranch   string
	GitCommit   string
	ConfigUsed  string
	Config      ConfigFile
	Hostname    Hostname
	IP          IP
	MongoLogin  string
}

type Hostname struct {
	Public string
	Local  string
}

type IP struct {
	Public string
	Local  string
}

type JenkinsInfo struct {
	LastCompletedBuild struct {
		Number int    `json:"number"`
		Url    string `json:"url"`
	} `json:"lastCompletedBuild"`
	LastStableBuild struct {
		Number int    `json:"number"`
		Url    string `json:"url"`
	} `json:"lastStableBuild"`
	LastFailedBuild struct {
		Number int    `json:"number"`
		Url    string `json:"url"`
	} `json:"lastFailedBuild"`
}

type WorkerInfo struct {
	Name      string    `json:"name"`
	Uuid      string    `json:"uuid"`
	Hostname  string    `json:"hostname"`
	Version   int       `json:"version"`
	Timestamp time.Time `json:"timestamp"`
	Pid       int       `json:"pid"`
	State     string    `json:"state"`
	Info      string    `json:"info"`
	Clock     string    `json:"clock"`
	Uptime    int       `json:"uptime"`
	Port      int       `json:"port"`
}

type StatusInfo struct {
	BuildNumber string
	NewKoding   struct {
		ServerHost string
		BrokerHost string
	}
	Workers struct {
		Running int
		Dead    int
	}
}

type HomePage struct {
	Status  StatusInfo
	Workers []WorkerInfo
	Jenkins *JenkinsInfo
	Server  *ServerInfo
	Builds  []int
	Domains map[string]Domain
}

func NewServerInfo() *ServerInfo {
	return &ServerInfo{
		BuildNumber: "",
		GitBranch:   "",
		GitCommit:   "",
		ConfigUsed:  "",
		Config:      ConfigFile{},
		Hostname:    Hostname{},
		IP:          IP{},
	}
}

var templates = template.Must(template.ParseFiles("index.html"))

const uptimeLayout = "03:04:00"

func main() {
	http.HandleFunc("/", viewHandler)
	http.Handle("/bootstrap/", http.StripPrefix("/bootstrap/", http.FileServer(http.Dir("bootstrap/"))))

	fmt.Println("koding overview started")
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		fmt.Println(err)
	}
}

func viewHandler(w http.ResponseWriter, r *http.Request) {
	build := r.FormValue("build")
	if build == "" {
		build = "latest"
	}

	workers, status, err := workerInfo(build)
	if err != nil {
		fmt.Println(err)
	}

	jenkins := jenkinsInfo()
	builds := buildsInfo()

	domains, err := domainInfo()
	if err != nil {
		fmt.Println(err)
		domains = &DomainInfo{}
	}

	server, err := serverInfo(build)
	if err != nil {
		fmt.Println(err)
		server = NewServerInfo()
	}

	s, b := keyLookup(domains.Domains["new.koding.com"])
	status.NewKoding.ServerHost = s
	status.NewKoding.BrokerHost = b

	home := HomePage{
		Status:  status,
		Workers: workers,
		Jenkins: jenkins,
		Server:  server,
		Builds:  builds,
		Domains: domains.Domains,
	}

	renderTemplate(w, "index", home)
	return
}

func keyLookup(domain Domain) (string, string) {
	workersApi := "http://kontrol.in.koding.com/workers?version=" + domain.Key
	resp, err := http.Get(workersApi)
	if err != nil {
		fmt.Println(err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
	}

	workers := make([]WorkerInfo, 0)
	err = json.Unmarshal(body, &workers)
	if err != nil {
		fmt.Println(err)
	}

	var server string
	var broker string
	for _, w := range workers {
		if w.Name == "server" {
			server = w.Hostname + ":" + strconv.Itoa(w.Port)
		}

		if w.Name == "broker" {
			broker = w.Hostname + ":" + strconv.Itoa(w.Port)
		}

	}

	return server, broker
}

func renderTemplate(w http.ResponseWriter, tmpl string, home HomePage) {
	err := templates.ExecuteTemplate(w, tmpl+".html", home)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func jenkinsInfo() *JenkinsInfo {
	fmt.Println("getting jenkins info")
	j := &JenkinsInfo{}
	jenkinsApi := "http://salt-master.in.koding.com/job/build-koding/api/json"
	resp, err := http.Get(jenkinsApi)
	if err != nil {
		fmt.Println(err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
	}

	err = json.Unmarshal(body, &j)
	if err != nil {
		fmt.Println(err)
	}

	return j
}

func workerInfo(build string) ([]WorkerInfo, StatusInfo, error) {
	s := StatusInfo{}

	workersApi := "http://kontrol.in.koding.com/workers?version=" + build
	resp, err := http.Get(workersApi)
	if err != nil {
		return nil, s, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, s, err
	}

	workers := make([]WorkerInfo, 0)
	err = json.Unmarshal(body, &workers)
	if err != nil {
		return nil, s, err
	}

	s.BuildNumber = build

	for i, val := range workers {
		switch val.State {
		case "running":
			s.Workers.Running++
			workers[i].Info = "success"
		case "dead":
			s.Workers.Dead++
			workers[i].Info = "error"
		case "stopped":
			workers[i].Info = "warning"
		case "waiting":
			workers[i].Info = "info"
		}

		d, err := time.ParseDuration(strconv.Itoa(workers[i].Uptime) + "s")
		if err != nil {
			fmt.Println(err)
		}
		workers[i].Clock = d.String()
	}

	return workers, s, nil
}

func buildsInfo() []int {
	serverApi := "http://kontrol.in.koding.com/deployments"
	fmt.Println(serverApi)
	resp, err := http.Get(serverApi)
	if err != nil {
		fmt.Println(err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
	}

	s := &[]ServerInfo{}
	err = json.Unmarshal(body, &s)
	if err != nil {
		fmt.Println(err)
	}

	builds := make([]int, 0)
	for _, serv := range *s {
		build, _ := strconv.Atoi(serv.BuildNumber)
		builds = append(builds, build)
	}
	sort.Sort(sort.Reverse(sort.IntSlice(builds)))

	return builds
}

func serverInfo(build string) (*ServerInfo, error) {
	serverApi := "http://kontrol.in.koding.com/deployments/" + build

	resp, err := http.Get(serverApi)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	s := &ServerInfo{}
	err = json.Unmarshal(body, &s)
	if err != nil {
		return nil, err
	}

	s.MongoLogin = parseMongoLogin(s.Config.Mongo)

	return s, nil
}

func parseMongoLogin(login string) string {
	u, err := url.Parse("http://" + login)
	if err != nil {
		fmt.Println(err)
	}

	mPass, _ := u.User.Password()
	return fmt.Sprintf(
		"mongo %s%s -u%s -p%s",
		u.Host,
		u.Path,
		u.User.Username(),
		mPass,
	)
}

func domainInfo() (*DomainInfo, error) {
	domainApi := "http://kontrol.in.koding.com/proxies/proxy.in.koding.com/domains"
	resp, err := http.Get(domainApi)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	d := &DomainInfo{}
	err = json.Unmarshal(body, &d)
	if err != nil {
		return nil, err
	}

	return d, nil
}
<commit_msg>overview: merge a modified version of armagans fix<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"time"
)

type ConfigFile struct {
	Mongo string
	Mq    struct {
		Host          string
		Port          int
		ComponentUser string
		Password      string
		Vhost         string
	}
}

type Domain struct {
	Domainname  string
	Mode        string
	Username    string
	Servicename string
	Key         string
	FullUrl     string
}

type ServerInfo struct {
	BuildNumber string
	GitBranch   string
	GitCommit   string
	ConfigUsed  string
	Config      ConfigFile
	Hostname    Hostname
	IP          IP
	MongoLogin  string
}

type Hostname struct {
	Public string
	Local  string
}

type IP struct {
	Public string
	Local  string
}

type JenkinsInfo struct {
	LastCompletedBuild struct {
		Number int    `json:"number"`
		Url    string `json:"url"`
	} `json:"lastCompletedBuild"`
	LastStableBuild struct {
		Number int    `json:"number"`
		Url    string `json:"url"`
	} `json:"lastStableBuild"`
	LastFailedBuild struct {
		Number int    `json:"number"`
		Url    string `json:"url"`
	} `json:"lastFailedBuild"`
}

type WorkerInfo struct {
	Name      string    `json:"name"`
	Uuid      string    `json:"uuid"`
	Hostname  string    `json:"hostname"`
	Version   int       `json:"version"`
	Timestamp time.Time `json:"timestamp"`
	Pid       int       `json:"pid"`
	State     string    `json:"state"`
	Info      string    `json:"info"`
	Clock     string    `json:"clock"`
	Uptime    int       `json:"uptime"`
	Port      int       `json:"port"`
}

type StatusInfo struct {
	BuildNumber string
	NewKoding   struct {
		ServerHost string
		BrokerHost string
	}
	Workers struct {
		Running int
		Dead    int
	}
}

type HomePage struct {
	Status  StatusInfo
	Workers []WorkerInfo
	Jenkins *JenkinsInfo
	Server  *ServerInfo
	Builds  []int
}

func NewServerInfo() *ServerInfo {
	return &ServerInfo{
		BuildNumber: "",
		GitBranch:   "",
		GitCommit:   "",
		ConfigUsed:  "",
		Config:      ConfigFile{},
		Hostname:    Hostname{},
		IP:          IP{},
	}
}

var templates = template.Must(template.ParseFiles("index.html"))

const uptimeLayout = "03:04:00"

func main() {
	http.HandleFunc("/", viewHandler)
	http.Handle("/bootstrap/", http.StripPrefix("/bootstrap/", http.FileServer(http.Dir("bootstrap/"))))

	fmt.Println("koding overview started")
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		fmt.Println(err)
	}
}

func viewHandler(w http.ResponseWriter, r *http.Request) {
	build := r.FormValue("build")
	if build == "" {
		build = "latest"
	}

	workers, status, err := workerInfo(build)
	if err != nil {
		fmt.Println(err)
	}

	jenkins := jenkinsInfo()
	builds := buildsInfo()

	server, err := serverInfo(build)
	if err != nil {
		fmt.Println(err)
		server = NewServerInfo()
	}

	domain, err := domainInfo("new.koding.com")
	if err != nil {
		fmt.Println(err)
	}

	s, b := keyLookup(domain.Key)
	status.NewKoding.ServerHost = s
	status.NewKoding.BrokerHost = b

	home := HomePage{
		Status:  status,
		Workers: workers,
		Jenkins: jenkins,
		Server:  server,
		Builds:  builds,
	}

	renderTemplate(w, "index", home)
	return
}

func keyLookup(key string) (string, string) {
	workersApi := "http://kontrol.in.koding.com/workers?version=" + key
	resp, err := http.Get(workersApi)
	if err != nil {
		fmt.Println(err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
	}

	workers := make([]WorkerInfo, 0)
	err = json.Unmarshal(body, &workers)
	if err != nil {
		fmt.Println(err)
	}

	var server string
	var broker string
	for _, w := range workers {
		if w.Name == "server" {
			server = w.Hostname + ":" + strconv.Itoa(w.Port)
		}

		if w.Name == "broker" {
			broker = w.Hostname + ":" + strconv.Itoa(w.Port)
		}

	}

	return server, broker
}

func renderTemplate(w http.ResponseWriter, tmpl string, home HomePage) {
	err := templates.ExecuteTemplate(w, tmpl+".html", home)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func jenkinsInfo() *JenkinsInfo {
	fmt.Println("getting jenkins info")
	j := &JenkinsInfo{}
	jenkinsApi := "http://salt-master.in.koding.com/job/build-koding/api/json"
	resp, err := http.Get(jenkinsApi)
	if err != nil {
		fmt.Println(err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
	}

	err = json.Unmarshal(body, &j)
	if err != nil {
		fmt.Println(err)
	}

	return j
}

func workerInfo(build string) ([]WorkerInfo, StatusInfo, error) {
	s := StatusInfo{}

	workersApi := "http://kontrol.in.koding.com/workers?version=" + build
	resp, err := http.Get(workersApi)
	if err != nil {
		return nil, s, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, s, err
	}

	workers := make([]WorkerInfo, 0)
	err = json.Unmarshal(body, &workers)
	if err != nil {
		return nil, s, err
	}

	s.BuildNumber = build

	for i, val := range workers {
		switch val.State {
		case "running":
			s.Workers.Running++
			workers[i].Info = "success"
		case "dead":
			s.Workers.Dead++
			workers[i].Info = "error"
		case "stopped":
			workers[i].Info = "warning"
		case "waiting":
			workers[i].Info = "info"
		}

		d, err := time.ParseDuration(strconv.Itoa(workers[i].Uptime) + "s")
		if err != nil {
			fmt.Println(err)
		}
		workers[i].Clock = d.String()
	}

	return workers, s, nil
}

func buildsInfo() []int {
	serverApi := "http://kontrol.in.koding.com/deployments"
	fmt.Println(serverApi)
	resp, err := http.Get(serverApi)
	if err != nil {
		fmt.Println(err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
	}

	s := &[]ServerInfo{}
	err = json.Unmarshal(body, &s)
	if err != nil {
		fmt.Println(err)
	}

	builds := make([]int, 0)
	for _, serv := range *s {
		build, _ := strconv.Atoi(serv.BuildNumber)
		builds = append(builds, build)
	}
	sort.Sort(sort.Reverse(sort.IntSlice(builds)))

	return builds
}

func serverInfo(build string) (*ServerInfo, error) {
	serverApi := "http://kontrol.in.koding.com/deployments/" + build

	resp, err := http.Get(serverApi)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	s := &ServerInfo{}
	err = json.Unmarshal(body, &s)
	if err != nil {
		return nil, err
	}

	s.MongoLogin = parseMongoLogin(s.Config.Mongo)

	return s, nil
}

func parseMongoLogin(login string) string {
	u, err := url.Parse("http://" + login)
	if err != nil {
		fmt.Println(err)
	}

	mPass, _ := u.User.Password()
	return fmt.Sprintf(
		"mongo %s%s -u%s -p%s",
		u.Host,
		u.Path,
		u.User.Username(),
		mPass,
	)
}

func domainInfo(domainname string) (Domain, error) {
	domainApi := "http://kontrol.in.koding.com/proxies/proxy-2.in.koding.com/domains"
	resp, err := http.Get(domainApi)
	if err != nil {
		return Domain{}, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return Domain{}, err
	}

	d := make([]Domain, 0)
	err = json.Unmarshal(body, &d)
	if err != nil {
		return Domain{}, err
	}

	for _, domain := range d {
		if domain.Domainname == domainname {
			return domain, nil
		}
	}

	return Domain{}, fmt.Errorf("no domain info available for %s", domainname)
}
<|endoftext|>