<commit_before>package k8s

import (
	"fmt"
	"strings"

	"github.com/openebs/maya/types/v1"
	orchProfile "github.com/openebs/maya/types/v1/profile/orchestrator"
	volProfile "github.com/openebs/maya/volume/profiles"
	"k8s.io/client-go/kubernetes"
	k8sCoreV1 "k8s.io/client-go/kubernetes/typed/core/v1"
	k8sExtnsV1Beta1 "k8s.io/client-go/kubernetes/typed/extensions/v1beta1"
	storagev1 "k8s.io/client-go/kubernetes/typed/storage/v1"
	k8sApiV1 "k8s.io/client-go/pkg/api/v1"
	k8sApisExtnsBeta1 "k8s.io/client-go/pkg/apis/extensions/v1beta1"

	"k8s.io/client-go/rest"
)

// K8sUtilGetter is an abstraction to fetch instances of K8sUtilInterface
type K8sUtilGetter interface {
	GetK8sUtil(volProfile.VolumeProvisionerProfile) K8sUtilInterface
}

// K8sUtilInterface is an abstraction over communicating with K8s APIs
type K8sUtilInterface interface {
	// Name of K8s utility
	Name() string

	// K8sClient fetches an instance of K8sClients. Will return
	// false if the util does not support providing K8sClients instance.
	K8sClient() (K8sClient, bool)
}

// K8sClient is an abstraction to operate on various k8s entities.
//
// NOTE:
//    This abstraction makes use of K8s's client-go package.
type K8sClient interface {
	// IsInCluster indicates whether the operation is within cluster or in a
	// different cluster
	IsInCluster() (bool, error)

	// NS provides the namespace where operations will be executed
	NS() (string, error)

	// TODO
	//    Rename to PodOps
	//
	// Pods provides all the CRUD operations associated w.r.t a POD
	Pods() (k8sCoreV1.PodInterface, error)

	// TODO
	//    Rename to ServiceOps
	//
	// Services provides all the CRUD operations associated w.r.t a Service
	Services() (k8sCoreV1.ServiceInterface, error)

	// DeploymentOps provides all the CRUD operations associated w.r.t a Deployment
	DeploymentOps() (k8sExtnsV1Beta1.DeploymentInterface, error)

	// StorageClassOps provides all the CRUD & more operations associated
	// w.r.t a StorageClass
	StorageClassOps() (storagev1.StorageClassInterface, error)
}

// k8sUtil provides the concrete implementation for below interfaces:
//
// 1. k8s.K8sUtilInterface interface
// 2. k8s.K8sClients interface
type k8sUtil struct {

	// inCS refers to the ClientSet capable of communicating
	// within the current K8s cluster i.e. where this binary is
	// running
	inCS *kubernetes.Clientset

	// outCS refers to the ClientSet capable of communicating
	// outside of current K8s cluster i.e. where this binary is
	// running
	outCS *kubernetes.Clientset

	caCert     string
	caPath     string
	clientCert string
	clientKey  string
	insecure   bool

	// volProfile has context related information w.r.t k8s
	volProfile volProfile.VolumeProvisionerProfile
}

// This is a plain k8s utility & hence the name
func (k *k8sUtil) Name() string {
	ns, _ := k.NS()
	return fmt.Sprintf("k8sutil @ '%s'", ns)
}

// k8sUtil implements K8sClient interface. Hence it returns
// self
func (k *k8sUtil) K8sClient() (K8sClient, bool) {
	return k, true
}

// NS provides the namespace where operations will be executed
func (k *k8sUtil) NS() (string, error) {
	if nil == k.volProfile {
		return "", fmt.Errorf("Volume provisioner profile not initialized at '%s'", k.Name())
	}

	// Fetch vol from volume provisioner profile
	vol, err := k.volProfile.Volume()
	if err != nil {
		return "", err
	}

	// Get orchestrator provider profile from vol
	oPrfle, err := orchProfile.GetOrchProviderProfile(vol)
	if err != nil {
		return "", err
	}

	// Get the namespace which will be queried
	ns, err := oPrfle.NS()
	if err != nil {
		return "", err
	}

	return ns, nil
}

// InCluster indicates whether the operation is within cluster or in a
// different cluster
func (k *k8sUtil) IsInCluster() (bool, error) {
	if nil == k.volProfile {
		return false, fmt.Errorf("Volume provisioner profile not initialized at '%s'", k.Name())
	}

	// Fetch vol from volume provisioner profile
	vol, err := k.volProfile.Volume()
	if err != nil {
		return false, err
	}

	// Get orchestrator provider profile from vol
	oPrfle, err := orchProfile.GetOrchProviderProfile(vol)
	if err != nil {
		return false, err
	}

	// Which kind of request ? in-cluster or out-of-cluster ?
	isInCluster, err := oPrfle.InCluster()
	if err != nil {
		return false, err
	}

	return isInCluster, nil
}

// Pods is a utility function that provides a instance capable of
// executing various k8s pod related operations.
func (k *k8sUtil) Pods() (k8sCoreV1.PodInterface, error) {

	// TODO
	// use getClientSet instead of below repeated code
	// refer to StorageClassOps
	var cs *kubernetes.Clientset

	inC, err := k.IsInCluster()
	if err != nil {
		return nil, err
	}

	ns, err := k.NS()
	if err != nil {
		return nil, err
	}

	if inC {
		cs, err = k.getInClusterCS()
	} else {
		cs, err = k.getOutClusterCS()
	}

	if err != nil {
		return nil, err
	}

	return cs.CoreV1().Pods(ns), nil
}

// Services is a utility function that provides a instance capable of
// executing various k8s service related operations.
func (k *k8sUtil) Services() (k8sCoreV1.ServiceInterface, error) {

	// TODO
	// use getClientSet instead of below repeated code
	// refer to StorageClassOps
	var cs *kubernetes.Clientset

	inC, err := k.IsInCluster()
	if err != nil {
		return nil, err
	}

	ns, err := k.NS()
	if err != nil {
		return nil, err
	}

	if inC {
		cs, err = k.getInClusterCS()
	} else {
		cs, err = k.getOutClusterCS()
	}

	if err != nil {
		return nil, err
	}

	return cs.CoreV1().Services(ns), nil
}

// Services is a utility function that provides a instance capable of
// executing various k8s Deployment related operations.
func (k *k8sUtil) DeploymentOps() (k8sExtnsV1Beta1.DeploymentInterface, error) {

	// TODO
	// use getClientSet instead of below repeated code
	// refer to StorageClassOps
	var cs *kubernetes.Clientset

	inC, err := k.IsInCluster()
	if err != nil {
		return nil, err
	}

	ns, err := k.NS()
	if err != nil {
		return nil, err
	}

	if inC {
		cs, err = k.getInClusterCS()
	} else {
		cs, err = k.getOutClusterCS()
	}

	if err != nil {
		return nil, err
	}

	return cs.ExtensionsV1beta1().Deployments(ns), nil
}

func (k *k8sUtil) StorageClassOps() (storagev1.StorageClassInterface, error) {
	cs, err := k.getClientSet()

	if err != nil {
		return nil, err
	}

	// get the required namespace
	//ns, err := k.NS()
	//if err != nil {
	//	return nil, err
	//}

	return cs.StorageV1().StorageClasses(), nil
}

// getClientSet is used to get a new http client capable
// of invoking K8s APIs.
func (k *k8sUtil) getClientSet() (*kubernetes.Clientset, error) {
	var cs *kubernetes.Clientset

	// Get if already available in current instance
	// NOTE: A new instance of k8sUtil is created per http request
	if k.inCS != nil {
		return cs, nil
	}

	if cs == nil && k.outCS != nil {
		return cs, nil
	}

	// Get it fresh for this instance/http request
	inC, err := k.IsInCluster()
	if err != nil {
		return nil, err
	}

	// set based on in-cluster or out-of-cluster
	if inC {
		cs, err = k.getInClusterCS()
		k.inCS = cs
	} else {
		cs, err = k.getOutClusterCS()
		k.outCS = cs
	}

	if err != nil {
		return nil, err
	}

	return cs, nil
}

// getInClusterCS is used to initialize and return a new http client capable
// of invoking K8s APIs.
func (k *k8sUtil) getInClusterCS() (*kubernetes.Clientset, error) {

	// creates the in-cluster config
	config, err := rest.InClusterConfig()
	if err != nil {
		return nil, err
	}

	// creates the in-cluster clientset
	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, err
	}

	return clientset, nil
}

// getOutClusterCS is used to initialize and return a new http client capable
// of invoking outside the cluster K8s APIs.
func (k *k8sUtil) getOutClusterCS() (*kubernetes.Clientset, error) {
	return nil, fmt.Errorf("outClusterCS not supported in '%s'", k.Name())
}

//
func SetControllerIPs(cp k8sApiV1.Pod, annotations map[string]string) {
	current := strings.TrimSpace(cp.Status.PodIP)
	if current == "" {
		// Nothing to be done
		return
	}

	existing := strings.TrimSpace(annotations[string(v1.ControllerIPsAPILbl)])

	// Set the value or add to the existing values if not added earlier
	if existing == "" {
		annotations[string(v1.ControllerIPsAPILbl)] = current
	} else {
		annotations[string(v1.ControllerIPsAPILbl)] = existing + "," + current
	}
}

//
func SetReplicaIPs(rp k8sApiV1.Pod, annotations map[string]string) {
	current := strings.TrimSpace(rp.Status.PodIP)
	if current == "" {
		// Nothing to be done
		return
	}

	existing := strings.TrimSpace(annotations[string(v1.ReplicaIPsAPILbl)])

	// Set the value or add to the existing values if not added earlier
	if existing == "" {
		annotations[string(v1.ReplicaIPsAPILbl)] = current
	} else {
		annotations[string(v1.ReplicaIPsAPILbl)] = existing + "," + current
	}
}

//
func SetControllerStatuses(cp k8sApiV1.Pod, annotations map[string]string) {
	current := strings.TrimSpace(string(cp.Status.Phase))
	if current == "" {
		// Nothing to be done
		return
	}

	existing := strings.TrimSpace(annotations[string(v1.ControllerStatusAPILbl)])

	// Set the value or add to the existing values if not added earlier
	if existing == "" {
		annotations[string(v1.ControllerStatusAPILbl)] = current
	} else {
		annotations[string(v1.ControllerStatusAPILbl)] = existing + "," + current
	}
}

//
func SetReplicaStatuses(rp k8sApiV1.Pod, annotations map[string]string) {
	current := strings.TrimSpace(string(rp.Status.Phase))
	if current == "" {
		// Nothing to be done
		return
	}

	existing := strings.TrimSpace(annotations[string(v1.ReplicaStatusAPILbl)])

	// Set the value or add to the existing values if not added earlier
	if existing == "" {
		annotations[string(v1.ReplicaStatusAPILbl)] = current
	} else {
		annotations[string(v1.ReplicaStatusAPILbl)] = existing + "," + current
	}
}

// TODO
// Not sure !!
func SetServiceStatuses(svc k8sApiV1.Service, annotations map[string]string) {}

func SetReplicaCount(rd k8sApisExtnsBeta1.Deployment, annotations map[string]string) {
	annotations[string(v1.ReplicaCountAPILbl)] = fmt.Sprint(*rd.Spec.Replicas)
}

// TODO Get it from Pod
func SetReplicaVolSize(rd k8sApisExtnsBeta1.Deployment, annotations map[string]string) {
	// TODO
	// Set the size as labels in replica deployment & extract from the label
	// Current way of extraction is a very crude way !!
	con := rd.Spec.Template.Spec.Containers[0]
	size := con.Args[len(con.Args)-2]

	annotations[string(v1.VolumeSizeAPILbl)] = size
}

func SetIQN(vsm string, annotations map[string]string) {
	annotations[string(v1.IQNAPILbl)] = string(v1.JivaIqnFormatPrefix) + ":" + vsm
}

func SetControllerClusterIPs(svc k8sApiV1.Service, annotations map[string]string) {
	current := strings.TrimSpace(svc.Spec.ClusterIP)
	if current == "" {
		// Nothing to be done
		return
	}

	existing := strings.TrimSpace(annotations[string(v1.ClusterIPsAPILbl)])

	// Set the value or add to the existing values if not added earlier
	if existing == "" {
		annotations[string(v1.ClusterIPsAPILbl)] = current
	} else {
		annotations[string(v1.ClusterIPsAPILbl)] = existing + "," + current
	}
}

func SetISCSITargetPortals(svc k8sApiV1.Service, annotations map[string]string) {
	current := strings.TrimSpace(svc.Spec.ClusterIP)
	if current == "" {
		// Nothing to be done
		return
	}
	current = current + ":" + string(v1.JivaISCSIPortDef)

	existing := strings.TrimSpace(annotations[string(v1.TargetPortalsAPILbl)])

	// Set the value or add to the existing values if not added earlier
	if existing == "" {
		annotations[string(v1.TargetPortalsAPILbl)] = current
	} else {
		annotations[string(v1.TargetPortalsAPILbl)] = existing + "," + current
	}
}
<commit_msg>fixes a logic error injected by adding StorageClass ops<commit_after>package k8s

import (
	"fmt"
	"strings"

	"github.com/openebs/maya/types/v1"
	orchProfile "github.com/openebs/maya/types/v1/profile/orchestrator"
	volProfile "github.com/openebs/maya/volume/profiles"
	"k8s.io/client-go/kubernetes"
	k8sCoreV1 "k8s.io/client-go/kubernetes/typed/core/v1"
	k8sExtnsV1Beta1 "k8s.io/client-go/kubernetes/typed/extensions/v1beta1"
	storagev1 "k8s.io/client-go/kubernetes/typed/storage/v1"
	k8sApiV1 "k8s.io/client-go/pkg/api/v1"
	k8sApisExtnsBeta1 "k8s.io/client-go/pkg/apis/extensions/v1beta1"

	"k8s.io/client-go/rest"
)

// K8sUtilGetter is an abstraction to fetch instances of K8sUtilInterface
type K8sUtilGetter interface {
	GetK8sUtil(volProfile.VolumeProvisionerProfile) K8sUtilInterface
}

// K8sUtilInterface is an abstraction over communicating with K8s APIs
type K8sUtilInterface interface {
	// Name of K8s utility
	Name() string

	// K8sClient fetches an instance of K8sClients. Will return
	// false if the util does not support providing K8sClients instance.
	K8sClient() (K8sClient, bool)
}

// K8sClient is an abstraction to operate on various k8s entities.
//
// NOTE:
//    This abstraction makes use of K8s's client-go package.
type K8sClient interface {
	// IsInCluster indicates whether the operation is within cluster or in a
	// different cluster
	IsInCluster() (bool, error)

	// NS provides the namespace where operations will be executed
	NS() (string, error)

	// TODO
	//    Rename to PodOps
	//
	// Pods provides all the CRUD operations associated w.r.t a POD
	Pods() (k8sCoreV1.PodInterface, error)

	// TODO
	//    Rename to ServiceOps
	//
	// Services provides all the CRUD operations associated w.r.t a Service
	Services() (k8sCoreV1.ServiceInterface, error)

	// DeploymentOps provides all the CRUD operations associated w.r.t a Deployment
	DeploymentOps() (k8sExtnsV1Beta1.DeploymentInterface, error)

	// StorageClassOps provides all the CRUD & more operations associated
	// w.r.t a StorageClass
	StorageClassOps() (storagev1.StorageClassInterface, error)
}

// k8sUtil provides the concrete implementation for below interfaces:
//
// 1. k8s.K8sUtilInterface interface
// 2. k8s.K8sClients interface
type k8sUtil struct {

	// inCS refers to the ClientSet capable of communicating
	// within the current K8s cluster i.e. where this binary is
	// running
	inCS *kubernetes.Clientset

	// outCS refers to the ClientSet capable of communicating
	// outside of current K8s cluster i.e. where this binary is
	// running
	outCS *kubernetes.Clientset

	caCert     string
	caPath     string
	clientCert string
	clientKey  string
	insecure   bool

	// volProfile has context related information w.r.t k8s
	volProfile volProfile.VolumeProvisionerProfile
}

// This is a plain k8s utility & hence the name
func (k *k8sUtil) Name() string {
	ns, _ := k.NS()
	return fmt.Sprintf("k8sutil @ '%s'", ns)
}

// k8sUtil implements K8sClient interface. Hence it returns
// self
func (k *k8sUtil) K8sClient() (K8sClient, bool) {
	return k, true
}

// NS provides the namespace where operations will be executed
func (k *k8sUtil) NS() (string, error) {
	if nil == k.volProfile {
		return "", fmt.Errorf("Volume provisioner profile not initialized at '%s'", k.Name())
	}

	// Fetch vol from volume provisioner profile
	vol, err := k.volProfile.Volume()
	if err != nil {
		return "", err
	}

	// Get orchestrator provider profile from vol
	oPrfle, err := orchProfile.GetOrchProviderProfile(vol)
	if err != nil {
		return "", err
	}

	// Get the namespace which will be queried
	ns, err := oPrfle.NS()
	if err != nil {
		return "", err
	}

	return ns, nil
}

// InCluster indicates whether the operation is within cluster or in a
// different cluster
func (k *k8sUtil) IsInCluster() (bool, error) {
	if nil == k.volProfile {
		return false, fmt.Errorf("Volume provisioner profile not initialized at '%s'", k.Name())
	}

	// Fetch vol from volume provisioner profile
	vol, err := k.volProfile.Volume()
	if err != nil {
		return false, err
	}

	// Get orchestrator provider profile from vol
	oPrfle, err := orchProfile.GetOrchProviderProfile(vol)
	if err != nil {
		return false, err
	}

	// Which kind of request ? in-cluster or out-of-cluster ?
	isInCluster, err := oPrfle.InCluster()
	if err != nil {
		return false, err
	}

	return isInCluster, nil
}

// Pods is a utility function that provides a instance capable of
// executing various k8s pod related operations.
func (k *k8sUtil) Pods() (k8sCoreV1.PodInterface, error) {

	// TODO
	// use getClientSet instead of below repeated code
	// refer to StorageClassOps
	var cs *kubernetes.Clientset

	inC, err := k.IsInCluster()
	if err != nil {
		return nil, err
	}

	ns, err := k.NS()
	if err != nil {
		return nil, err
	}

	if inC {
		cs, err = k.getInClusterCS()
	} else {
		cs, err = k.getOutClusterCS()
	}

	if err != nil {
		return nil, err
	}

	return cs.CoreV1().Pods(ns), nil
}

// Services is a utility function that provides a instance capable of
// executing various k8s service related operations.
func (k *k8sUtil) Services() (k8sCoreV1.ServiceInterface, error) {

	// TODO
	// use getClientSet instead of below repeated code
	// refer to StorageClassOps
	var cs *kubernetes.Clientset

	inC, err := k.IsInCluster()
	if err != nil {
		return nil, err
	}

	ns, err := k.NS()
	if err != nil {
		return nil, err
	}

	if inC {
		cs, err = k.getInClusterCS()
	} else {
		cs, err = k.getOutClusterCS()
	}

	if err != nil {
		return nil, err
	}

	return cs.CoreV1().Services(ns), nil
}

// Services is a utility function that provides a instance capable of
// executing various k8s Deployment related operations.
func (k *k8sUtil) DeploymentOps() (k8sExtnsV1Beta1.DeploymentInterface, error) {

	// TODO
	// use getClientSet instead of below repeated code
	// refer to StorageClassOps
	var cs *kubernetes.Clientset

	inC, err := k.IsInCluster()
	if err != nil {
		return nil, err
	}

	ns, err := k.NS()
	if err != nil {
		return nil, err
	}

	if inC {
		cs, err = k.getInClusterCS()
	} else {
		cs, err = k.getOutClusterCS()
	}

	if err != nil {
		return nil, err
	}

	return cs.ExtensionsV1beta1().Deployments(ns), nil
}

func (k *k8sUtil) StorageClassOps() (storagev1.StorageClassInterface, error) {
	cs, err := k.getClientSet()

	if err != nil {
		return nil, err
	}

	// get the required namespace
	//ns, err := k.NS()
	//if err != nil {
	//	return nil, err
	//}

	return cs.StorageV1().StorageClasses(), nil
}

// getClientSet is used to get a new http client capable
// of invoking K8s APIs.
func (k *k8sUtil) getClientSet() (*kubernetes.Clientset, error) {
	var cs *kubernetes.Clientset

	// Get if already available in current instance
	// NOTE: A new instance of k8sUtil is created per http request
	if k.inCS != nil {
		return k.inCS, nil
	}

	if k.outCS != nil {
		return k.outCS, nil
	}

	// Else get it fresh for this instance/http request
	inC, err := k.IsInCluster()
	if err != nil {
		return nil, err
	}

	// set based on in-cluster or out-of-cluster
	if inC {
		cs, err = k.getInClusterCS()
		// set it for future retrievals in same http request
		k.inCS = cs
	} else {
		cs, err = k.getOutClusterCS()
		// set it for future retrievals in same http request
		k.outCS = cs
	}

	if err != nil {
		return nil, err
	}

	return cs, nil
}

// getInClusterCS is used to initialize and return a new http client capable
// of invoking K8s APIs.
func (k *k8sUtil) getInClusterCS() (*kubernetes.Clientset, error) {

	// creates the in-cluster config
	config, err := rest.InClusterConfig()
	if err != nil {
		return nil, err
	}

	// creates the in-cluster clientset
	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, err
	}

	return clientset, nil
}

// getOutClusterCS is used to initialize and return a new http client capable
// of invoking outside the cluster K8s APIs.
func (k *k8sUtil) getOutClusterCS() (*kubernetes.Clientset, error) {
	return nil, fmt.Errorf("outClusterCS not supported in '%s'", k.Name())
}

//
func SetControllerIPs(cp k8sApiV1.Pod, annotations map[string]string) {
	current := strings.TrimSpace(cp.Status.PodIP)
	if current == "" {
		// Nothing to be done
		return
	}

	existing := strings.TrimSpace(annotations[string(v1.ControllerIPsAPILbl)])

	// Set the value or add to the existing values if not added earlier
	if existing == "" {
		annotations[string(v1.ControllerIPsAPILbl)] = current
	} else {
		annotations[string(v1.ControllerIPsAPILbl)] = existing + "," + current
	}
}

//
func SetReplicaIPs(rp k8sApiV1.Pod, annotations map[string]string) {
	current := strings.TrimSpace(rp.Status.PodIP)
	if current == "" {
		// Nothing to be done
		return
	}

	existing := strings.TrimSpace(annotations[string(v1.ReplicaIPsAPILbl)])

	// Set the value or add to the existing values if not added earlier
	if existing == "" {
		annotations[string(v1.ReplicaIPsAPILbl)] = current
	} else {
		annotations[string(v1.ReplicaIPsAPILbl)] = existing + "," + current
	}
}

//
func SetControllerStatuses(cp k8sApiV1.Pod, annotations map[string]string) {
	current := strings.TrimSpace(string(cp.Status.Phase))
	if current == "" {
		// Nothing to be done
		return
	}

	existing := strings.TrimSpace(annotations[string(v1.ControllerStatusAPILbl)])

	// Set the value or add to the existing values if not added earlier
	if existing == "" {
		annotations[string(v1.ControllerStatusAPILbl)] = current
	} else {
		annotations[string(v1.ControllerStatusAPILbl)] = existing + "," + current
	}
}

//
func SetReplicaStatuses(rp k8sApiV1.Pod, annotations map[string]string) {
	current := strings.TrimSpace(string(rp.Status.Phase))
	if current == "" {
		// Nothing to be done
		return
	}

	existing := strings.TrimSpace(annotations[string(v1.ReplicaStatusAPILbl)])

	// Set the value or add to the existing values if not added earlier
	if existing == "" {
		annotations[string(v1.ReplicaStatusAPILbl)] = current
	} else {
		annotations[string(v1.ReplicaStatusAPILbl)] = existing + "," + current
	}
}

// TODO
// Not sure !!
func SetServiceStatuses(svc k8sApiV1.Service, annotations map[string]string) {}

func SetReplicaCount(rd k8sApisExtnsBeta1.Deployment, annotations map[string]string) {
	annotations[string(v1.ReplicaCountAPILbl)] = fmt.Sprint(*rd.Spec.Replicas)
}

// TODO Get it from Pod
func SetReplicaVolSize(rd k8sApisExtnsBeta1.Deployment, annotations map[string]string) {
	// TODO
	// Set the size as labels in replica deployment & extract from the label
	// Current way of extraction is a very crude way !!
	con := rd.Spec.Template.Spec.Containers[0]
	size := con.Args[len(con.Args)-2]

	annotations[string(v1.VolumeSizeAPILbl)] = size
}

func SetIQN(vsm string, annotations map[string]string) {
	annotations[string(v1.IQNAPILbl)] = string(v1.JivaIqnFormatPrefix) + ":" + vsm
}

func SetControllerClusterIPs(svc k8sApiV1.Service, annotations map[string]string) {
	current := strings.TrimSpace(svc.Spec.ClusterIP)
	if current == "" {
		// Nothing to be done
		return
	}

	existing := strings.TrimSpace(annotations[string(v1.ClusterIPsAPILbl)])

	// Set the value or add to the existing values if not added earlier
	if existing == "" {
		annotations[string(v1.ClusterIPsAPILbl)] = current
	} else {
		annotations[string(v1.ClusterIPsAPILbl)] = existing + "," + current
	}
}

func SetISCSITargetPortals(svc k8sApiV1.Service, annotations map[string]string) {
	current := strings.TrimSpace(svc.Spec.ClusterIP)
	if current == "" {
		// Nothing to be done
		return
	}
	current = current + ":" + string(v1.JivaISCSIPortDef)

	existing := strings.TrimSpace(annotations[string(v1.TargetPortalsAPILbl)])

	// Set the value or add to the existing values if not added earlier
	if existing == "" {
		annotations[string(v1.TargetPortalsAPILbl)] = current
	} else {
		annotations[string(v1.TargetPortalsAPILbl)] = existing + "," + current
	}
}
<|endoftext|>


<commit_before>package meta

import (
	"errors"
	"testing"

	"strings"

	"github.com/stretchr/testify/assert"
)

// TestVersion unit test.
func TestVersion(t *testing.T) {
	assert.NotNil(t, Version)
	assert.NotEmpty(t, Version)
	assert.NotContains(t, Version, " ")
}

// TestAppname unit test.
func TestAppname(t *testing.T) {
	assert.NotNil(t, ApplicationName)
	assert.NotEmpty(t, ApplicationName)
	assert.NotContains(t, ApplicationName, " ")
}

// TestConfigfilename unit test.
func TestConfigfilename(t *testing.T) {
	assert.NotNil(t, ConfigFilename)
	assert.NotEmpty(t, ConfigFilename)
	assert.NotContains(t, ConfigFilename, " ")
}

// TestConfigfile unit test.
func TestConfigfile(t *testing.T) {
	f, err := ConfigFile()
	assert.NoError(t, err)
	assert.NotNil(t, f)
	assert.NotEmpty(t, f)
}

// TestConfigfileWithSpecialDir unit test.
func TestConfigfileWithSpecialDir(t *testing.T) {
	ConfigDir = "./"
	f, err := ConfigFile()
	assert.NoError(t, err)
	assert.NotNil(t, f)
	assert.NotEmpty(t, f)
}

// TestFirstWithoutErrorButNoOkFunction unit test.
func TestFirstWithoutErrorButNoOkFunction(t *testing.T) {
	_, err := firstWithoutError(func() (string, error) {
		return "", errors.New("not ok")
	})
	assert.Error(t, err)
}

// TestCurry unit test.
func TestCurry(t *testing.T) {
	upper := curry("arg", func(arg string) (string, error) {
		return strings.ToUpper(arg), nil
	})
	assert.NotNil(t, upper)
	asUppercase, err := upper()
	assert.NoError(t, err)
	assert.Equal(t, "ARG", asUppercase)
}

// TestComposeWithError unit test.
func TestComposeWithError(t *testing.T) {
	composed := compose("arg", func(arg string) (string, error) {
		return "OK", nil
	}, func(arg string) (string, error) {
		return "Not ok", errors.New("an error")

	})
	assert.NotNil(t, composed)
	_, err := composed()
	assert.Error(t, err)
}
<commit_msg>fix tests<commit_after>package meta

import (
	"errors"
	"testing"

	"strings"

	"github.com/stretchr/testify/assert"
)

// TestVersion unit test.
func TestVersion(t *testing.T) {
	assert.NotNil(t, Version)
	assert.NotEmpty(t, Version)
	assert.NotContains(t, Version, " ")
}

// TestAppname unit test.
func TestAppname(t *testing.T) {
	assert.NotNil(t, ApplicationName)
	assert.NotEmpty(t, ApplicationName)
	assert.NotContains(t, ApplicationName, " ")
}

// TestConfigfilename unit test.
func TestConfigfilename(t *testing.T) {
	assert.NotNil(t, ConfigFilename)
	assert.NotEmpty(t, ConfigFilename)
	assert.NotContains(t, ConfigFilename, " ")
}

// TestConfigfile unit test.
func TestConfigfile(t *testing.T) {
	f, _ := ConfigFile()
	assert.NotNil(t, f)
}

// TestFirstWithoutErrorButNoOkFunction unit test.
func TestFirstWithoutErrorButNoOkFunction(t *testing.T) {
	_, err := firstWithoutError(func() (string, error) {
		return "", errors.New("not ok")
	})
	assert.Error(t, err)
}

// TestCurry unit test.
func TestCurry(t *testing.T) {
	upper := curry("arg", func(arg string) (string, error) {
		return strings.ToUpper(arg), nil
	})
	assert.NotNil(t, upper)
	asUppercase, err := upper()
	assert.NoError(t, err)
	assert.Equal(t, "ARG", asUppercase)
}

// TestComposeWithError unit test.
func TestComposeWithError(t *testing.T) {
	composed := compose("arg", func(arg string) (string, error) {
		return "OK", nil
	}, func(arg string) (string, error) {
		return "Not ok", errors.New("an error")

	})
	assert.NotNil(t, composed)
	_, err := composed()
	assert.Error(t, err)
}
<|endoftext|>


<commit_before>package mid

import (
	"fmt"
	"io"
	"os"

	"gitlab.com/gomidi/midi/smf"
	"gitlab.com/gomidi/midi/smf/smfreader"
)

// ReadSMFFile open, reads and closes a complete SMF file.
// If the read content was a valid midi file, nil is returned.
//
// The messages are dispatched to the corresponding attached functions of the handler.
//
// They must be attached before Reader.ReadSMF is called
// and they must not be unset or replaced until ReadSMF returns.
// For more infomation about dealing with the SMF midi messages, see Reader and
// SMFPosition.
func (r *Reader) ReadSMFFile(file string, options ...smfreader.Option) error {
	r.errSMF = nil
	r.pos = &Position{}
	r.reset()
	err := smfreader.ReadFile(file, r.readSMF, options...)
	if err != nil && err != smf.ErrFinished {
		return err
	}
	if r.errSMF == smf.ErrFinished {
		return nil
	}
	return nil
}

// ReadSMFFileHeader reads just the header of a SMF file
func (r *Reader) ReadSMFFileHeader(file string, options ...smfreader.Option) (smf.Header, error) {
	r.errSMF = nil
	r.pos = &Position{}
	r.reset()
	f, err := os.Open(file)
	if err != nil {
		fmt.Printf("can't open file: %v", err)
		return smf.Header{}, err
	}
	rd := smfreader.New(f, options...)

	err2 := rd.ReadHeader()

	r.setHeader(rd.Header())

	f.Close()

	if err2 != nil && err2 != smf.ErrFinished {
		return rd.Header(), err2
	}
	return rd.Header(), nil
}

// ReadSMF reads midi messages from src (which is supposed to be the content of a standard midi file (SMF))
// until an error or io.EOF happens.
//
// ReadSMF does not close the src.
//
// If the read content was a valid midi file, nil is returned.
//
// The messages are dispatched to the corresponding attached functions of the Reader.
//
// They must be attached before Reader.ReadSMF is called
// and they must not be unset or replaced until ReadSMF returns.
// For more infomation about dealing with the SMF midi messages, see Reader and
// SMFPosition.
func (r *Reader) ReadSMF(src io.Reader, options ...smfreader.Option) error {
	r.errSMF = nil
	r.pos = &Position{}
	r.reset()
	rd := smfreader.New(src, options...)

	err := rd.ReadHeader()
	if err != nil {
		return err
	}
	r.setHeader(rd.Header())
	r.readSMF(rd)

	if r.errSMF == smf.ErrFinished {
		return nil
	}
	return r.errSMF
}

func (r *Reader) setHeader(hd smf.Header) {
	r.header = hd

	if metric, isMetric := r.header.TimeFormat.(smf.MetricTicks); isMetric {
		r.resolution = metric
	}

	if r.SMFHeader != nil {
		r.SMFHeader(r.header)
	}
}

func (r *Reader) readSMF(rd smf.Reader) {
	err := r.dispatch(rd)
	if err != io.EOF {
		r.errSMF = err
	}
}

/*
These are differences  some differences between the two:
  - SMF events have a message and a delta time while live MIDI just has messages
  - a SMF file has a header
  - a SMF file may have meta messages (e.g. track name, copyright etc) while live MIDI must not have them
  - live MIDI may have realtime and syscommon messages while a SMF must not have them

In sum the only MIDI message handling that could be shared in practice is the handling of channel
and sysex messages.

That is reflected in the type signature of the callbacks and results in three kinds of callbacks:
  - The ones that don't receive a SMFPosition are called for messages that may only appear live
  - The ones with that receive a SMFPosition are called for messages that may only appear
    within a SMF file
  - The onse with that receive a *SMFPosition are called for messages that may appear live
    and within a SMF file. In a SMF file the pointer is never nil while in a live situation it always is.

Due to the nature of a SMF file, the tracks have no "assigned numbers", and can in the worst case just be
distinguished by the order in which they appear inside the file.

In addition the delta times are just relative to the previous message inside the track.

This has some severe consequences:

  - Eeven if the user is just interested in some kind of messages (e.g. note on and note off),
    he still has to deal with the delta times of every message.
  - At the beginning of each track he has to reset the tracking time.

This procedure is error prone and therefor SMFPosition provides a helper that contains not just
the original delta (which shouldn't be needed most of the time), but also the absolute position (in ticks)
and order number of the track in which the message appeared.

This way the user can ignore the messages and tracks he is not interested in.

See the example for a handler that handles both live and SMF messages.

For writing there is a LiveWriter for "over the wire" writing and a SMFWriter to write SMF files.

*/
<commit_msg>read header when ReadFile<commit_after>package mid

import (
	"fmt"
	"io"
	"os"

	"gitlab.com/gomidi/midi/smf"
	"gitlab.com/gomidi/midi/smf/smfreader"
)

// ReadSMFFile open, reads and closes a complete SMF file.
// If the read content was a valid midi file, nil is returned.
//
// The messages are dispatched to the corresponding attached functions of the handler.
//
// They must be attached before Reader.ReadSMF is called
// and they must not be unset or replaced until ReadSMF returns.
// For more infomation about dealing with the SMF midi messages, see Reader and
// SMFPosition.
func (r *Reader) ReadSMFFile(file string, options ...smfreader.Option) error {
	r.errSMF = nil
	r.pos = &Position{}
	r.reset()
	err := smfreader.ReadFile(file, r.readSMF2, options...)
	if err != nil && err != smf.ErrFinished {
		return err
	}
	if r.errSMF == smf.ErrFinished {
		return nil
	}
	return nil
}

// ReadSMFFileHeader reads just the header of a SMF file
func (r *Reader) ReadSMFFileHeader(file string, options ...smfreader.Option) (smf.Header, error) {
	r.errSMF = nil
	r.pos = &Position{}
	r.reset()
	f, err := os.Open(file)
	if err != nil {
		fmt.Printf("can't open file: %v", err)
		return smf.Header{}, err
	}
	rd := smfreader.New(f, options...)

	err2 := rd.ReadHeader()

	r.setHeader(rd.Header())

	f.Close()

	if err2 != nil && err2 != smf.ErrFinished {
		return rd.Header(), err2
	}
	return rd.Header(), nil
}

// ReadSMF reads midi messages from src (which is supposed to be the content of a standard midi file (SMF))
// until an error or io.EOF happens.
//
// ReadSMF does not close the src.
//
// If the read content was a valid midi file, nil is returned.
//
// The messages are dispatched to the corresponding attached functions of the Reader.
//
// They must be attached before Reader.ReadSMF is called
// and they must not be unset or replaced until ReadSMF returns.
// For more infomation about dealing with the SMF midi messages, see Reader and
// SMFPosition.
func (r *Reader) ReadSMF(src io.Reader, options ...smfreader.Option) error {
	r.errSMF = nil
	r.pos = &Position{}
	r.reset()
	rd := smfreader.New(src, options...)

	err := rd.ReadHeader()
	if err != nil {
		return err
	}
	r.setHeader(rd.Header())
	r.readSMF(rd)

	if r.errSMF == smf.ErrFinished {
		return nil
	}
	return r.errSMF
}

func (r *Reader) setHeader(hd smf.Header) {
	r.header = hd

	if metric, isMetric := r.header.TimeFormat.(smf.MetricTicks); isMetric {
		r.resolution = metric
	}

	if r.SMFHeader != nil {
		r.SMFHeader(r.header)
	}
}

func (r *Reader) readSMF2(rd smf.Reader) {
	err := rd.ReadHeader()
	if err != nil {
		r.errSMF = err
	}
	r.setHeader(rd.Header())
	r.readSMF(rd)
}

func (r *Reader) readSMF(rd smf.Reader) {
	err := r.dispatch(rd)
	if err != io.EOF {
		r.errSMF = err
	}
}

/*
These are differences  some differences between the two:
  - SMF events have a message and a delta time while live MIDI just has messages
  - a SMF file has a header
  - a SMF file may have meta messages (e.g. track name, copyright etc) while live MIDI must not have them
  - live MIDI may have realtime and syscommon messages while a SMF must not have them

In sum the only MIDI message handling that could be shared in practice is the handling of channel
and sysex messages.

That is reflected in the type signature of the callbacks and results in three kinds of callbacks:
  - The ones that don't receive a SMFPosition are called for messages that may only appear live
  - The ones with that receive a SMFPosition are called for messages that may only appear
    within a SMF file
  - The onse with that receive a *SMFPosition are called for messages that may appear live
    and within a SMF file. In a SMF file the pointer is never nil while in a live situation it always is.

Due to the nature of a SMF file, the tracks have no "assigned numbers", and can in the worst case just be
distinguished by the order in which they appear inside the file.

In addition the delta times are just relative to the previous message inside the track.

This has some severe consequences:

  - Eeven if the user is just interested in some kind of messages (e.g. note on and note off),
    he still has to deal with the delta times of every message.
  - At the beginning of each track he has to reset the tracking time.

This procedure is error prone and therefor SMFPosition provides a helper that contains not just
the original delta (which shouldn't be needed most of the time), but also the absolute position (in ticks)
and order number of the track in which the message appeared.

This way the user can ignore the messages and tracks he is not interested in.

See the example for a handler that handles both live and SMF messages.

For writing there is a LiveWriter for "over the wire" writing and a SMFWriter to write SMF files.

*/
<|endoftext|>


<commit_before>// Package static provides go.h compatible hashed static assets. This allows
// for providing long lived cache headers for resources which change URLs as
// their content changes.
package static

import (
	"bytes"
	"crypto/md5"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"mime"
	"net/http"
	"path"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/daaku/go.h"
)

const (
	maxAge  = time.Hour * 24 * 365 * 10
	hashLen = 8
)

var (
	errZeroNames = errors.New("static: zero names given")
	cacheControl = fmt.Sprintf("public, max-age=%d", int(maxAge.Seconds()))
)

func disableCaching(w http.ResponseWriter) {
	header := w.Header()
	header.Set("Cache-Control", "no-cache")
	header.Set("Pragma", "no-cache")
}

func notFound(w http.ResponseWriter) {
	disableCaching(w)
	w.WriteHeader(http.StatusNotFound)
	io.WriteString(w, http.StatusText(http.StatusNotFound))
}

func badRequest(w http.ResponseWriter) {
	disableCaching(w)
	w.WriteHeader(http.StatusBadRequest)
	io.WriteString(w, http.StatusText(http.StatusBadRequest))
}

type errInvalidURL string

func (e errInvalidURL) Error() string {
	return fmt.Sprintf("static: invalid URL %q", string(e))
}

type file struct {
	Name    string
	Content []byte
	Hash    string
}

func dropPadding(s string) string {
	return strings.TrimRight(s, "=")
}

// so we only allocate these strings once
var paddings = [...]string{
	"=",
	"==",
	"===",
}

func addPadding(s string) string {
	if l := len(s) % 4; l > 0 {
		return s + paddings[3-l]
	}
	return s
}

func encode(files []*file) (string, error) {
	var parts [][]string
	for _, f := range files {
		parts = append(parts, []string{f.Name, f.Hash})
	}

	b, err := json.Marshal(parts)
	if err != nil {
		return "", errors.New("static: could not encode URL")
	}

	return dropPadding(base64.URLEncoding.EncodeToString(b)), nil
}

func decode(value string) ([]*file, error) {
	decoded, err := base64.URLEncoding.DecodeString(addPadding(value))
	if err != nil {
		return nil, errInvalidURL(value)
	}

	var parts [][]string
	if err := json.NewDecoder(bytes.NewReader(decoded)).Decode(&parts); err != nil {
		return nil, errInvalidURL(value)
	}

	var files []*file
	for _, part := range parts {
		if len(part) != 2 {
			return nil, errInvalidURL(value)
		}
		files = append(files, &file{
			Name: part[0],
			Hash: part[1],
		})
	}

	return files, nil
}

// Box is where the files are loaded from. Practically you'll probably want to
// use https://github.com/GeertJohan/go.rice.
type Box interface {
	Bytes(name string) ([]byte, error)
}

// Handler serves and provides URLs for static resources.
type Handler struct {
	Path string // Path at which Handler is configured.
	Box  Box    // Box of files to serve.

	mu    sync.RWMutex
	files map[string]*file
}

func (h *Handler) load(name string) (*file, error) {
	// fast path
	h.mu.RLock()
	f := h.files[name]
	h.mu.RUnlock()

	if f != nil {
		return f, nil
	}

	// slow path
	h.mu.Lock()
	defer h.mu.Unlock()

	// check again in case someone else populated it
	f = h.files[name]
	if f != nil {
		return f, nil
	}

	contents, err := h.Box.Bytes(name)
	if err != nil {
		return nil, err
	}

	hash := fmt.Sprintf("%x", md5.Sum(contents))
	f = &file{
		Name:    name,
		Content: contents,
		Hash:    hash[:hashLen],
	}
	if h.files == nil {
		h.files = make(map[string]*file)
	}
	h.files[name] = f

	return f, nil
}

// URL returns a hashed URL for all the given component names.
func (h *Handler) URL(names ...string) (string, error) {
	if len(names) == 0 {
		return "", errZeroNames
	}

	var files []*file
	for _, name := range names {
		f, err := h.load(name)
		if err != nil {
			return "", err
		}
		files = append(files, f)
	}

	value, err := encode(files)
	if err != nil {
		return "", err
	}

	if ext := filepath.Ext(names[0]); ext != "" {
		value = value + ext
	}

	return path.Join(h.Path, value), nil
}

// ServeHTTP handles requests for hashed URLs.
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	path := r.URL.Path
	if !strings.HasPrefix(path, h.Path) {
		notFound(w)
		return
	}

	contentType := ""
	encoded := path[len(h.Path):]
	if ext := filepath.Ext(encoded); ext != "" {
		encoded = encoded[:len(encoded)-len(ext)]
		contentType = mime.TypeByExtension(ext)
	}

	files, err := decode(encoded)
	if err != nil {
		badRequest(w)
		return
	}

	// fill in the contents and calculate the length
	var contentLength int
	for i, f := range files {
		loaded, err := h.load(f.Name)
		if err != nil {
			notFound(w)
			return
		}
		if loaded.Hash != f.Hash {
			notFound(w)
			return
		}
		contentLength += len(loaded.Content)
		files[i] = loaded
	}

	header := w.Header()
	header.Set("Cache-Control", cacheControl)
	header.Set("Content-Length", strconv.Itoa(contentLength))
	if contentType != "" {
		header.Set("Content-Type", contentType)
	}

	for _, f := range files {
		w.Write(f.Content)
	}
}

// LinkStyle provides a h.LinkStyle where the HREFs are combined and served
// using the specified Handler.
type LinkStyle struct {
	Handler *Handler
	HREF    []string
}

// HTML returns the <link> tag with the appropriate attributes.
func (l *LinkStyle) HTML() (h.HTML, error) {
	url, err := l.Handler.URL(l.HREF...)
	if err != nil {
		return nil, err
	}
	return &h.LinkStyle{HREF: url}, nil
}

// Script provides a h.Script where the Srcs are combined and served using the
// specified Handler.
type Script struct {
	Handler *Handler
	Src     []string
	Async   bool
}

// HTML returns the <script> tag with the appropriate attributes.
func (l *Script) HTML() (h.HTML, error) {
	url, err := l.Handler.URL(l.Src...)
	if err != nil {
		return nil, err
	}
	return &h.Script{
		Src:   url,
		Async: l.Async,
	}, nil
}

// Img provides a h.Img where the src is served using the specified Handler.
type Img struct {
	Handler *Handler
	ID      string
	Class   string
	Style   string
	Src     string
	Alt     string
}

// HTML returns the <img> tag with the appropriate attributes.
func (i *Img) HTML() (h.HTML, error) {
	src, err := i.Handler.URL(i.Src)
	if err != nil {
		return nil, err
	}
	return &h.Img{
		ID:    i.ID,
		Class: i.Class,
		Style: i.Style,
		Src:   src,
		Alt:   i.Alt,
	}, nil
}
<commit_msg>style nits<commit_after>// Package static provides go.h compatible hashed static assets. This allows
// for providing long lived cache headers for resources which change URLs as
// their content changes.
package static

import (
	"bytes"
	"crypto/md5"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"mime"
	"net/http"
	"path"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/daaku/go.h"
)

const (
	maxAge  = time.Hour * 24 * 365 * 10
	hashLen = 8
)

var (
	errZeroNames = errors.New("static: zero names given")
	cacheControl = fmt.Sprintf("public, max-age=%d", int(maxAge.Seconds()))
)

func disableCaching(w http.ResponseWriter) {
	header := w.Header()
	header.Set("Cache-Control", "no-cache")
	header.Set("Pragma", "no-cache")
}

func notFound(w http.ResponseWriter) {
	disableCaching(w)
	w.WriteHeader(http.StatusNotFound)
	io.WriteString(w, http.StatusText(http.StatusNotFound))
}

func badRequest(w http.ResponseWriter) {
	disableCaching(w)
	w.WriteHeader(http.StatusBadRequest)
	io.WriteString(w, http.StatusText(http.StatusBadRequest))
}

type errInvalidURL string

func (e errInvalidURL) Error() string {
	return fmt.Sprintf("static: invalid URL %q", string(e))
}

// we drop base64 padding in our URLs
func dropPadding(s string) string {
	return strings.TrimRight(s, "=")
}

// so we only allocate these strings once
var paddings = [...]string{
	"=",
	"==",
	"===",
}

func addPadding(s string) string {
	if l := len(s) % 4; l > 0 {
		return s + paddings[3-l]
	}
	return s
}

type file struct {
	Name    string
	Content []byte
	Hash    string
}

func encode(files []*file) (string, error) {
	var parts [][]string
	for _, f := range files {
		parts = append(parts, []string{f.Name, f.Hash})
	}

	b, err := json.Marshal(parts)
	if err != nil {
		return "", errors.New("static: could not encode URL")
	}

	return dropPadding(base64.URLEncoding.EncodeToString(b)), nil
}

func decode(value string) ([]*file, error) {
	decoded, err := base64.URLEncoding.DecodeString(addPadding(value))
	if err != nil {
		return nil, errInvalidURL(value)
	}

	var parts [][]string
	if err := json.NewDecoder(bytes.NewReader(decoded)).Decode(&parts); err != nil {
		return nil, errInvalidURL(value)
	}

	var files []*file
	for _, part := range parts {
		if len(part) != 2 {
			return nil, errInvalidURL(value)
		}
		files = append(files, &file{
			Name: part[0],
			Hash: part[1],
		})
	}

	return files, nil
}

// Box is where the files are loaded from. Practically you'll probably want to
// use https://github.com/GeertJohan/go.rice.
type Box interface {
	Bytes(name string) ([]byte, error)
}

// Handler serves and provides URLs for static resources.
type Handler struct {
	Path string // Path at which Handler is configured.
	Box  Box    // Box of files to serve.

	mu    sync.RWMutex
	files map[string]*file
}

func (h *Handler) load(name string) (*file, error) {
	// fast path
	h.mu.RLock()
	f := h.files[name]
	h.mu.RUnlock()

	if f != nil {
		return f, nil
	}

	// slow path
	h.mu.Lock()
	defer h.mu.Unlock()

	// check again in case someone else populated it
	f = h.files[name]
	if f != nil {
		return f, nil
	}

	contents, err := h.Box.Bytes(name)
	if err != nil {
		return nil, err
	}

	hash := fmt.Sprintf("%x", md5.Sum(contents))
	f = &file{
		Name:    name,
		Content: contents,
		Hash:    hash[:hashLen],
	}
	if h.files == nil {
		h.files = make(map[string]*file)
	}
	h.files[name] = f

	return f, nil
}

// URL returns a hashed URL for all the given component names. It uses the
// extension of the first file as the extension for the generated URL.
func (h *Handler) URL(names ...string) (string, error) {
	if len(names) == 0 {
		return "", errZeroNames
	}

	var files []*file
	for _, name := range names {
		f, err := h.load(name)
		if err != nil {
			return "", err
		}
		files = append(files, f)
	}

	value, err := encode(files)
	if err != nil {
		return "", err
	}

	if ext := filepath.Ext(names[0]); ext != "" {
		value = value + ext
	}

	return path.Join(h.Path, value), nil
}

// ServeHTTP handles requests for hashed URLs.
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	path := r.URL.Path
	if !strings.HasPrefix(path, h.Path) {
		notFound(w)
		return
	}

	contentType := ""
	encoded := path[len(h.Path):]
	if ext := filepath.Ext(encoded); ext != "" {
		encoded = encoded[:len(encoded)-len(ext)]
		contentType = mime.TypeByExtension(ext)
	}

	files, err := decode(encoded)
	if err != nil {
		badRequest(w)
		return
	}

	// fill in the contents and calculate the length
	var contentLength int
	for i, f := range files {
		loaded, err := h.load(f.Name)
		if err != nil {
			notFound(w)
			return
		}
		if loaded.Hash != f.Hash {
			notFound(w)
			return
		}
		contentLength += len(loaded.Content)
		files[i] = loaded
	}

	header := w.Header()
	header.Set("Cache-Control", cacheControl)
	header.Set("Content-Length", strconv.Itoa(contentLength))
	if contentType != "" {
		header.Set("Content-Type", contentType)
	}

	for _, f := range files {
		w.Write(f.Content)
	}
}

// LinkStyle provides a h.LinkStyle where the HREFs are combined and served
// using the specified Handler.
type LinkStyle struct {
	Handler *Handler
	HREF    []string
}

// HTML returns the <link> tag with the appropriate attributes.
func (l *LinkStyle) HTML() (h.HTML, error) {
	url, err := l.Handler.URL(l.HREF...)
	if err != nil {
		return nil, err
	}
	return &h.LinkStyle{HREF: url}, nil
}

// Script provides a h.Script where the Srcs are combined and served using the
// specified Handler.
type Script struct {
	Handler *Handler
	Src     []string
	Async   bool
}

// HTML returns the <script> tag with the appropriate attributes.
func (l *Script) HTML() (h.HTML, error) {
	url, err := l.Handler.URL(l.Src...)
	if err != nil {
		return nil, err
	}
	return &h.Script{
		Src:   url,
		Async: l.Async,
	}, nil
}

// Img provides a h.Img where the src is served using the specified Handler.
type Img struct {
	Handler *Handler
	ID      string
	Class   string
	Style   string
	Src     string
	Alt     string
}

// HTML returns the <img> tag with the appropriate attributes.
func (i *Img) HTML() (h.HTML, error) {
	src, err := i.Handler.URL(i.Src)
	if err != nil {
		return nil, err
	}
	return &h.Img{
		ID:    i.ID,
		Class: i.Class,
		Style: i.Style,
		Src:   src,
		Alt:   i.Alt,
	}, nil
}
<|endoftext|>


<commit_before>// Copyright 2014 Google Inc. All Rights Reserved.
//
// 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 contains a program that generates code to register
// a directory and its contents as zip data for statik file system.
package main

import (
	"archive/zip"
	"bytes"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"
)

const (
	namePackage    = "statik"
	nameSourceFile = "statik.go"
)

var (
	flagSrc        = flag.String("src", path.Join(".", "public"), "The path of the source directory.")
	flagDest       = flag.String("dest", ".", "The destination path of the generated package.")
	flagNoMtime    = flag.Bool("m", false, "Ignore modification times on files.")
	flagNoCompress = flag.Bool("Z", false, "Do not use compression to shrink the files.")
	flagForce      = flag.Bool("f", false, "Overwrite destination file if it already exists.")
)

// mtimeDate holds the arbitrary mtime that we assign to files when
// flagNoMtime is set.
var mtimeDate = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC)

func main() {
	flag.Parse()

	file, err := generateSource(*flagSrc)
	if err != nil {
		exitWithError(err)
	}

	destDir := path.Join(*flagDest, namePackage)
	err = os.MkdirAll(destDir, 0755)
	if err != nil {
		exitWithError(err)
	}

	err = rename(file.Name(), path.Join(destDir, nameSourceFile))
	if err != nil {
		exitWithError(err)
	}
}

// rename tries to os.Rename, but fall backs to copying from src
// to dest and unlink the source if os.Rename fails.
func rename(src, dest string) error {
	// Try to rename generated source.
	if err := os.Rename(src, dest); err == nil {
		return nil
	}
	// If the rename failed (might do so due to temporary file residing on a
	// different device), try to copy byte by byte.
	rc, err := os.Open(src)
	if err != nil {
		return err
	}
	defer func() {
		rc.Close()
		os.Remove(src) // ignore the error, source is in tmp.
	}()

	if _, err = os.Stat(dest); !os.IsNotExist(err) {
		if *flagForce {
			if err = os.Remove(dest); err != nil {
				return fmt.Errorf("file %q could not be deleted", dest)
			}
		} else {
			return fmt.Errorf("file %q already exists; use -f to overwrite", dest)
		}
	}

	wc, err := os.Create(dest)
	if err != nil {
		return err
	}
	defer wc.Close()

	if _, err = io.Copy(wc, rc); err != nil {
		// Delete remains of failed copy attempt.
		os.Remove(dest)
	}
	return err
}

// Walks on the source path and generates source code
// that contains source directory's contents as zip contents.
// Generates source registers generated zip contents data to
// be read by the statik/fs HTTP file system.
func generateSource(srcPath string) (file *os.File, err error) {
	var (
		buffer    bytes.Buffer
		zipWriter io.Writer
	)

	zipWriter = &buffer
	f, err := ioutil.TempFile("", namePackage)
	if err != nil {
		return
	}

	zipWriter = io.MultiWriter(zipWriter, f)
	defer f.Close()

	w := zip.NewWriter(zipWriter)
	if err = filepath.Walk(srcPath, func(path string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		// Ignore directories and hidden files.
		// No entry is needed for directories in a zip file.
		// Each file is represented with a path, no directory
		// entities are required to build the hierarchy.
		if fi.IsDir() || strings.HasPrefix(fi.Name(), ".") {
			return nil
		}
		relPath, err := filepath.Rel(srcPath, path)
		if err != nil {
			return err
		}
		b, err := ioutil.ReadFile(path)
		if err != nil {
			return err
		}
		fHeader, err := zip.FileInfoHeader(fi)
		if err != nil {
			return err
		}
		if *flagNoMtime {
			// Always use the same modification time so that
			// the output is deterministic with respect to the file contents.
			fHeader.SetModTime(mtimeDate)
		}
		fHeader.Name = filepath.ToSlash(relPath)
		if !*flagNoCompress {
			fHeader.Method = zip.Deflate
		}
		f, err := w.CreateHeader(fHeader)
		if err != nil {
			return err
		}
		_, err = f.Write(b)
		return err
	}); err != nil {
		return
	}
	if err = w.Close(); err != nil {
		return
	}

	// then embed it as a quoted string
	var qb bytes.Buffer
	fmt.Fprintf(&qb, `// Code generated by statik. DO NOT EDIT.

package %s

import (
	"github.com/rakyll/statik/fs"
)

func init() {
	data := "`, namePackage)
	FprintZipData(&qb, buffer.Bytes())
	fmt.Fprint(&qb, `"
	fs.Register(data)
}
`)

	if err = ioutil.WriteFile(f.Name(), qb.Bytes(), 0644); err != nil {
		return
	}
	return f, nil
}

// FprintZipData converts zip binary contents to a string literal.
func FprintZipData(dest *bytes.Buffer, zipData []byte) {
	for _, b := range zipData {
		if b == '\n' {
			dest.WriteString(`\n`)
			continue
		}
		if b == '\\' {
			dest.WriteString(`\\`)
			continue
		}
		if b == '"' {
			dest.WriteString(`\"`)
			continue
		}
		if (b >= 32 && b <= 126) || b == '\t' {
			dest.WriteByte(b)
			continue
		}
		fmt.Fprintf(dest, "\\x%02x", b)
	}
}

// Prints out the error message and exists with a non-success signal.
func exitWithError(err error) {
	fmt.Println(err)
	os.Exit(1)
}
<commit_msg>Write build constraint tags with -tags (#38)<commit_after>// Copyright 2014 Google Inc. All Rights Reserved.
//
// 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 contains a program that generates code to register
// a directory and its contents as zip data for statik file system.
package main

import (
	"archive/zip"
	"bytes"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"
)

const (
	namePackage    = "statik"
	nameSourceFile = "statik.go"
)

var (
	flagSrc        = flag.String("src", path.Join(".", "public"), "The path of the source directory.")
	flagDest       = flag.String("dest", ".", "The destination path of the generated package.")
	flagNoMtime    = flag.Bool("m", false, "Ignore modification times on files.")
	flagNoCompress = flag.Bool("Z", false, "Do not use compression to shrink the files.")
	flagForce      = flag.Bool("f", false, "Overwrite destination file if it already exists.")
	flagTags       = flag.String("tags", "", "Write build constraint tags")
)

// mtimeDate holds the arbitrary mtime that we assign to files when
// flagNoMtime is set.
var mtimeDate = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC)

func main() {
	flag.Parse()

	file, err := generateSource(*flagSrc)
	if err != nil {
		exitWithError(err)
	}

	destDir := path.Join(*flagDest, namePackage)
	err = os.MkdirAll(destDir, 0755)
	if err != nil {
		exitWithError(err)
	}

	err = rename(file.Name(), path.Join(destDir, nameSourceFile))
	if err != nil {
		exitWithError(err)
	}
}

// rename tries to os.Rename, but fall backs to copying from src
// to dest and unlink the source if os.Rename fails.
func rename(src, dest string) error {
	// Try to rename generated source.
	if err := os.Rename(src, dest); err == nil {
		return nil
	}
	// If the rename failed (might do so due to temporary file residing on a
	// different device), try to copy byte by byte.
	rc, err := os.Open(src)
	if err != nil {
		return err
	}
	defer func() {
		rc.Close()
		os.Remove(src) // ignore the error, source is in tmp.
	}()

	if _, err = os.Stat(dest); !os.IsNotExist(err) {
		if *flagForce {
			if err = os.Remove(dest); err != nil {
				return fmt.Errorf("file %q could not be deleted", dest)
			}
		} else {
			return fmt.Errorf("file %q already exists; use -f to overwrite", dest)
		}
	}

	wc, err := os.Create(dest)
	if err != nil {
		return err
	}
	defer wc.Close()

	if _, err = io.Copy(wc, rc); err != nil {
		// Delete remains of failed copy attempt.
		os.Remove(dest)
	}
	return err
}

// Walks on the source path and generates source code
// that contains source directory's contents as zip contents.
// Generates source registers generated zip contents data to
// be read by the statik/fs HTTP file system.
func generateSource(srcPath string) (file *os.File, err error) {
	var (
		buffer    bytes.Buffer
		zipWriter io.Writer
	)

	zipWriter = &buffer
	f, err := ioutil.TempFile("", namePackage)
	if err != nil {
		return
	}

	zipWriter = io.MultiWriter(zipWriter, f)
	defer f.Close()

	w := zip.NewWriter(zipWriter)
	if err = filepath.Walk(srcPath, func(path string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		// Ignore directories and hidden files.
		// No entry is needed for directories in a zip file.
		// Each file is represented with a path, no directory
		// entities are required to build the hierarchy.
		if fi.IsDir() || strings.HasPrefix(fi.Name(), ".") {
			return nil
		}
		relPath, err := filepath.Rel(srcPath, path)
		if err != nil {
			return err
		}
		b, err := ioutil.ReadFile(path)
		if err != nil {
			return err
		}
		fHeader, err := zip.FileInfoHeader(fi)
		if err != nil {
			return err
		}
		if *flagNoMtime {
			// Always use the same modification time so that
			// the output is deterministic with respect to the file contents.
			fHeader.SetModTime(mtimeDate)
		}
		fHeader.Name = filepath.ToSlash(relPath)
		if !*flagNoCompress {
			fHeader.Method = zip.Deflate
		}
		f, err := w.CreateHeader(fHeader)
		if err != nil {
			return err
		}
		_, err = f.Write(b)
		return err
	}); err != nil {
		return
	}
	if err = w.Close(); err != nil {
		return
	}

	var tags string
	if *flagTags != "" {
		tags = "\n// +build " + *flagTags + "\n"
	}

	// then embed it as a quoted string
	var qb bytes.Buffer
	fmt.Fprintf(&qb, `// Code generated by statik. DO NOT EDIT.
%s
package %s

import (
	"github.com/rakyll/statik/fs"
)

func init() {
	data := "`, tags, namePackage)
	FprintZipData(&qb, buffer.Bytes())
	fmt.Fprint(&qb, `"
	fs.Register(data)
}
`)

	if err = ioutil.WriteFile(f.Name(), qb.Bytes(), 0644); err != nil {
		return
	}
	return f, nil
}

// FprintZipData converts zip binary contents to a string literal.
func FprintZipData(dest *bytes.Buffer, zipData []byte) {
	for _, b := range zipData {
		if b == '\n' {
			dest.WriteString(`\n`)
			continue
		}
		if b == '\\' {
			dest.WriteString(`\\`)
			continue
		}
		if b == '"' {
			dest.WriteString(`\"`)
			continue
		}
		if (b >= 32 && b <= 126) || b == '\t' {
			dest.WriteByte(b)
			continue
		}
		fmt.Fprintf(dest, "\\x%02x", b)
	}
}

// Prints out the error message and exists with a non-success signal.
func exitWithError(err error) {
	fmt.Println(err)
	os.Exit(1)
}
<|endoftext|>


<commit_before>package mpo

import (
	"image"
	"image/draw"
)

func (m *MPO) ConvertToStereo() image.Image {
	mx := 0
	my := 0
	for _, i := range m.Image {
		mx += i.Bounds().Max.X
		if i.Bounds().Max.Y > my {
			my = i.Bounds().Max.Y
		}
	}

	img := image.NewRGBA(image.Rect(0, 0, mx, my))

	dx := 0
	for _, i := range m.Image {
		b := i.Bounds()
		b = b.Add(image.Point{dx, 0})

		draw.Draw(img, b, i, image.Point{0, 0}, draw.Src)

		dx += i.Bounds().Max.X
	}

	return img
}
<commit_msg>Adds more docs<commit_after>package mpo

import (
	"image"
	"image/draw"
)

// Converts an MPO to StereoScopic image
func (m *MPO) ConvertToStereo() image.Image {
	mx := 0
	my := 0
	for _, i := range m.Image {
		mx += i.Bounds().Max.X
		if i.Bounds().Max.Y > my {
			my = i.Bounds().Max.Y
		}
	}

	img := image.NewRGBA(image.Rect(0, 0, mx, my))

	dx := 0
	for _, i := range m.Image {
		b := i.Bounds()
		b = b.Add(image.Point{dx, 0})

		draw.Draw(img, b, i, image.Point{0, 0}, draw.Src)

		dx += i.Bounds().Max.X
	}

	return img
}
<|endoftext|>


<commit_before>package service

import (
	"context"

	. "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/swarm"
	"github.com/docker/docker/client"
)

type fakeClient struct {
	client.Client
	serviceInspectWithRawFunc func(ctx context.Context, serviceID string, options types.ServiceInspectOptions) (swarm.Service, []byte, error)
	serviceUpdateFunc         func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error)
	serviceListFunc           func(context.Context, types.ServiceListOptions) ([]swarm.Service, error)
	taskListFunc              func(context.Context, types.TaskListOptions) ([]swarm.Task, error)
	infoFunc                  func(ctx context.Context) (types.Info, error)
	networkInspectFunc        func(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error)
	nodeListFunc              func(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error)
}

func (f *fakeClient) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) {
	if f.nodeListFunc != nil {
		return f.nodeListFunc(ctx, options)
	}
	return nil, nil
}

func (f *fakeClient) TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) {
	if f.taskListFunc != nil {
		return f.taskListFunc(ctx, options)
	}
	return nil, nil
}

func (f *fakeClient) ServiceInspectWithRaw(ctx context.Context, serviceID string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) {
	if f.serviceInspectWithRawFunc != nil {
		return f.serviceInspectWithRawFunc(ctx, serviceID, options)
	}

	return *Service(ServiceID(serviceID)), []byte{}, nil
}

func (f *fakeClient) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
	if f.serviceListFunc != nil {
		return f.serviceListFunc(ctx, options)
	}

	return nil, nil
}

func (f *fakeClient) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) {
	if f.serviceUpdateFunc != nil {
		return f.serviceUpdateFunc(ctx, serviceID, version, service, options)
	}

	return types.ServiceUpdateResponse{}, nil
}

func (f *fakeClient) Info(ctx context.Context) (types.Info, error) {
	if f.infoFunc == nil {
		return types.Info{}, nil
	}
	return f.infoFunc(ctx)
}

func (f *fakeClient) NetworkInspect(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error) {
	if f.networkInspectFunc != nil {
		return f.networkInspectFunc(ctx, networkID, options)
	}
	return types.NetworkResource{}, nil
}

func newService(id string, name string, opts ...func(*swarm.Service)) swarm.Service {
	return *Service(append(opts, ServiceID(id), ServiceName(name))...)
}
<commit_msg>service: remove unused opts from newService() (unparam)<commit_after>package service

import (
	"context"

	. "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/swarm"
	"github.com/docker/docker/client"
)

type fakeClient struct {
	client.Client
	serviceInspectWithRawFunc func(ctx context.Context, serviceID string, options types.ServiceInspectOptions) (swarm.Service, []byte, error)
	serviceUpdateFunc         func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error)
	serviceListFunc           func(context.Context, types.ServiceListOptions) ([]swarm.Service, error)
	taskListFunc              func(context.Context, types.TaskListOptions) ([]swarm.Task, error)
	infoFunc                  func(ctx context.Context) (types.Info, error)
	networkInspectFunc        func(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error)
	nodeListFunc              func(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error)
}

func (f *fakeClient) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) {
	if f.nodeListFunc != nil {
		return f.nodeListFunc(ctx, options)
	}
	return nil, nil
}

func (f *fakeClient) TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) {
	if f.taskListFunc != nil {
		return f.taskListFunc(ctx, options)
	}
	return nil, nil
}

func (f *fakeClient) ServiceInspectWithRaw(ctx context.Context, serviceID string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) {
	if f.serviceInspectWithRawFunc != nil {
		return f.serviceInspectWithRawFunc(ctx, serviceID, options)
	}

	return *Service(ServiceID(serviceID)), []byte{}, nil
}

func (f *fakeClient) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
	if f.serviceListFunc != nil {
		return f.serviceListFunc(ctx, options)
	}

	return nil, nil
}

func (f *fakeClient) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) {
	if f.serviceUpdateFunc != nil {
		return f.serviceUpdateFunc(ctx, serviceID, version, service, options)
	}

	return types.ServiceUpdateResponse{}, nil
}

func (f *fakeClient) Info(ctx context.Context) (types.Info, error) {
	if f.infoFunc == nil {
		return types.Info{}, nil
	}
	return f.infoFunc(ctx)
}

func (f *fakeClient) NetworkInspect(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error) {
	if f.networkInspectFunc != nil {
		return f.networkInspectFunc(ctx, networkID, options)
	}
	return types.NetworkResource{}, nil
}

func newService(id string, name string) swarm.Service {
	return *Service(ServiceID(id), ServiceName(name))
}
<|endoftext|>


<commit_before>package ante

import (
	authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
)

// TODO issue 99: add GetParams and GetModuleAddress stubs in dclauth.AccountKeeper.
type AccountKeeper authante.AccountKeeper

// which is
/*
// AccountKeeper defines the contract needed for AccountKeeper related APIs.
// Interface provides support to use non-sdk AccountKeeper for AnteHandler's decorators.
type AccountKeeper interface {
	GetParams(ctx sdk.Context) (params types.Params)
	GetAccount(ctx sdk.Context, addr sdk.AccAddress) dclauthtypes.Account
	SetAccount(ctx sdk.Context, acc dclauthtypes.Account)
	GetModuleAddress(moduleName string) sdk.AccAddress
}
*/
<commit_msg>Clean up AccountKeeper<commit_after>package ante

import (
	authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
)

type AccountKeeper authante.AccountKeeper
<|endoftext|>


<commit_before>// Copyright 2015 The LUCI 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 lib

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"time"

	"github.com/maruel/subcommands"

	"go.chromium.org/luci/client/archiver"
	"go.chromium.org/luci/client/isolated"
	"go.chromium.org/luci/common/data/text/units"
	"go.chromium.org/luci/common/errors"
	"go.chromium.org/luci/common/system/signals"
)

// CmdArchive returns an object for the `archive` subcommand.
func CmdArchive(options CommandOptions) *subcommands.Command {
	return &subcommands.Command{
		UsageLine: "archive <options>...",
		ShortDesc: "creates a .isolated file and uploads the tree to an isolate server",
		LongDesc: `Given a list of files and directories, creates a .isolated file and uploads the
tree to to an isolate server.

When specifying directories and files, you must also specify a current working
directory for that file or directory. The current working directory will not
be included in the archived path. For example, to isolate './usr/foo/bar' and
have it appear as 'foo/bar' in the .isolated, specify '-files ./usr:foo/bar' or
'-files usr:foo/bar'. When the .isolated is then downloaded, it will then appear
under 'foo/bar' in the desired directory.

Note that '.' may be omitted in general, so to upload 'foo' from the current
working directory, '-files :foo' is sufficient.`,
		CommandRun: func() subcommands.CommandRun {
			c := archiveRun{
				CommandOptions: options,
			}
			c.commonFlags.Init(options.DefaultAuthOpts)
			c.Flags.Var(&c.dirs, "dirs", "Directory(ies) to archive. Specify as <working directory>:<relative path to dir>")
			c.Flags.Var(&c.files, "files", "Individual file(s) to archive. Specify as <working directory>:<relative path to file>")
			c.Flags.StringVar(&c.dumpHash, "dump-hash", "",
				"Write the composite isolated hash to a file")
			c.Flags.StringVar(&c.isolated, "isolated", "",
				"Write the composite isolated to a file")
			return &c
		},
	}
}

type archiveRun struct {
	commonFlags
	CommandOptions
	dirs     isolated.ScatterGather
	files    isolated.ScatterGather
	dumpHash string
	isolated string
}

func (c *archiveRun) Parse(a subcommands.Application, args []string) error {
	if err := c.commonFlags.Parse(); err != nil {
		return err
	}
	if len(args) != 0 {
		return errors.Reason("position arguments not expected").Err()
	}
	return nil
}

func (c *archiveRun) main(a subcommands.Application, args []string) (err error) {
	start := time.Now()
	out := os.Stdout
	ctx, cancel := context.WithCancel(c.defaultFlags.MakeLoggingContext(os.Stderr))
	signals.HandleInterrupt(cancel)

	isolatedClient, isolErr := c.createIsolatedClient(ctx, c.CommandOptions)
	if isolErr != nil {
		err = errors.Annotate(isolErr, "failed to create isolated client").Err()
		return
	}

	arch := archiver.New(ctx, isolatedClient, out)
	defer func() {
		// This waits for all uploads.
		if cerr := arch.Close(); err == nil {
			err = cerr
			return
		}
	}()

	opts := isolated.ArchiveOptions{
		Files:    c.files,
		Dirs:     c.dirs,
		Isolated: c.isolated,
	}
	if len(c.isolated) != 0 {
		var dumpIsolated *os.File
		dumpIsolated, err = os.Create(c.isolated)
		if err != nil {
			return
		}
		// This is OK to close before arch because isolated.Archive
		// does the writing (it's not handed off elsewhere).
		defer dumpIsolated.Close()
		opts.LeakIsolated = dumpIsolated
	}
	item := isolated.Archive(ctx, arch, &opts)
	if err = item.Error(); err != nil {
		return
	}

	item.WaitForHashed()
	if len(c.dumpHash) != 0 {
		if err = ioutil.WriteFile(c.dumpHash, []byte(item.Digest()), 0644); err != nil {
			return
		}
	}
	if !c.defaultFlags.Quiet {
		duration := time.Since(start)
		stats := arch.Stats()
		fmt.Fprintf(os.Stderr, "Hits    : %5d (%s)\n", stats.TotalHits(), stats.TotalBytesHits())
		fmt.Fprintf(os.Stderr, "Misses  : %5d (%s)\n", stats.TotalMisses(), stats.TotalBytesPushed())
		fmt.Fprintf(os.Stderr, "Duration: %s\n", units.Round(duration, time.Millisecond))
	}
	return
}

func (c *archiveRun) Run(a subcommands.Application, args []string, _ subcommands.Env) int {
	if err := c.Parse(a, args); err != nil {
		fmt.Fprintf(a.GetErr(), "%s: %s\n", a.GetName(), err)
		return 1
	}
	cl, err := c.defaultFlags.StartTracing()
	if err != nil {
		fmt.Fprintf(a.GetErr(), "%s: %s\n", a.GetName(), err)
		return 1
	}
	defer cl.Close()
	defer c.profilerFlags.Stop()
	if err := c.main(a, args); err != nil {
		fmt.Fprintf(a.GetErr(), "%s: %s\n", a.GetName(), err)
		return 1
	}
	return 0
}
<commit_msg>[isolated] Add -dump-stats-json to dump upload stats<commit_after>// Copyright 2015 The LUCI 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 lib

import (
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"sort"
	"time"

	"github.com/maruel/subcommands"

	"go.chromium.org/luci/client/archiver"
	"go.chromium.org/luci/client/isolated"
	"go.chromium.org/luci/common/data/text/units"
	"go.chromium.org/luci/common/errors"
	isol "go.chromium.org/luci/common/isolated"
	"go.chromium.org/luci/common/system/signals"
)

// CmdArchive returns an object for the `archive` subcommand.
func CmdArchive(options CommandOptions) *subcommands.Command {
	return &subcommands.Command{
		UsageLine: "archive <options>...",
		ShortDesc: "creates a .isolated file and uploads the tree to an isolate server",
		LongDesc: `Given a list of files and directories, creates a .isolated file and uploads the
tree to to an isolate server.

When specifying directories and files, you must also specify a current working
directory for that file or directory. The current working directory will not
be included in the archived path. For example, to isolate './usr/foo/bar' and
have it appear as 'foo/bar' in the .isolated, specify '-files ./usr:foo/bar' or
'-files usr:foo/bar'. When the .isolated is then downloaded, it will then appear
under 'foo/bar' in the desired directory.

Note that '.' may be omitted in general, so to upload 'foo' from the current
working directory, '-files :foo' is sufficient.`,
		CommandRun: func() subcommands.CommandRun {
			c := archiveRun{
				CommandOptions: options,
			}
			c.commonFlags.Init(options.DefaultAuthOpts)
			c.Flags.Var(&c.dirs, "dirs", "Directory(ies) to archive. Specify as <working directory>:<relative path to dir>")
			c.Flags.Var(&c.files, "files", "Individual file(s) to archive. Specify as <working directory>:<relative path to file>")
			c.Flags.StringVar(&c.dumpHash, "dump-hash", "",
				"Write the composite isolated hash to a file")
			c.Flags.StringVar(&c.isolated, "isolated", "",
				"Write the composite isolated to a file")
			c.Flags.StringVar(&c.dumpStatsJSON, "dump-stats-json", "",
				"Write the upload stats to this file as JSON")
			return &c
		},
	}
}

type archiveRun struct {
	commonFlags
	CommandOptions
	dirs          isolated.ScatterGather
	files         isolated.ScatterGather
	dumpHash      string
	isolated      string
	dumpStatsJSON string
}

func (c *archiveRun) Parse(a subcommands.Application, args []string) error {
	if err := c.commonFlags.Parse(); err != nil {
		return err
	}
	if len(args) != 0 {
		return errors.Reason("position arguments not expected").Err()
	}
	return nil
}

func (c *archiveRun) main(a subcommands.Application, args []string) (err error) {
	start := time.Now()
	out := os.Stdout
	ctx, cancel := context.WithCancel(c.defaultFlags.MakeLoggingContext(os.Stderr))
	signals.HandleInterrupt(cancel)

	isolatedClient, isolErr := c.createIsolatedClient(ctx, c.CommandOptions)
	if isolErr != nil {
		err = errors.Annotate(isolErr, "failed to create isolated client").Err()
		return
	}

	arch := archiver.New(ctx, isolatedClient, out)
	defer func() {
		// This waits for all uploads.
		if cerr := arch.Close(); err == nil {
			err = cerr
			return
		}
	}()

	opts := isolated.ArchiveOptions{
		Files:    c.files,
		Dirs:     c.dirs,
		Isolated: c.isolated,
	}
	if len(c.isolated) != 0 {
		var dumpIsolated *os.File
		dumpIsolated, err = os.Create(c.isolated)
		if err != nil {
			return
		}
		// This is OK to close before arch because isolated.Archive
		// does the writing (it's not handed off elsewhere).
		defer dumpIsolated.Close()
		opts.LeakIsolated = dumpIsolated
	}
	item := isolated.Archive(ctx, arch, &opts)
	if err = item.Error(); err != nil {
		return
	}

	item.WaitForHashed()
	if len(c.dumpHash) != 0 {
		if err = ioutil.WriteFile(c.dumpHash, []byte(item.Digest()), 0644); err != nil {
			return
		}
	}
	stats := arch.Stats()
	if !c.defaultFlags.Quiet {
		duration := time.Since(start)
		fmt.Fprintf(os.Stderr, "Hits    : %5d (%s)\n", stats.TotalHits(), stats.TotalBytesHits())
		fmt.Fprintf(os.Stderr, "Misses  : %5d (%s)\n", stats.TotalMisses(), stats.TotalBytesPushed())
		fmt.Fprintf(os.Stderr, "Duration: %s\n", units.Round(duration, time.Millisecond))
	}
	if c.dumpStatsJSON != "" {
		if err = dumpStatsJSON(c.dumpStatsJSON, stats); err != nil {
			return
		}
	}
	return
}

func (c *archiveRun) Run(a subcommands.Application, args []string, _ subcommands.Env) int {
	if err := c.Parse(a, args); err != nil {
		fmt.Fprintf(a.GetErr(), "%s: %s\n", a.GetName(), err)
		return 1
	}
	cl, err := c.defaultFlags.StartTracing()
	if err != nil {
		fmt.Fprintf(a.GetErr(), "%s: %s\n", a.GetName(), err)
		return 1
	}
	defer cl.Close()
	defer c.profilerFlags.Stop()
	if err := c.main(a, args); err != nil {
		fmt.Fprintf(a.GetErr(), "%s: %s\n", a.GetName(), err)
		return 1
	}
	return 0
}

func dumpStatsJSON(jsonPath string, stats *archiver.Stats) error {
	hits := make([]int64, len(stats.Hits))
	for i, h := range stats.Hits {
		hits[i] = int64(h)
	}
	sort.Slice(hits, func(i, j int) bool { return hits[i] < hits[j] })
	itemsHot, err := isol.Pack(hits)
	if err != nil {
		return errors.Annotate(err, "failed to pack itemsHot").Err()
	}

	pushed := make([]int64, len(stats.Pushed))
	for i, p := range stats.Pushed {
		pushed[i] = int64(p.Size)
	}
	sort.Slice(pushed, func(i, j int) bool { return pushed[i] < pushed[j] })
	itemsCold, err := isol.Pack(pushed)
	if err != nil {
		return errors.Annotate(err, "failed to pack itemsCold").Err()
	}

	statsJSON, err := json.Marshal(struct {
		ItemsCold []byte `json:"items_cold"`
		ItemsHot  []byte `json:"items_hot"`
	}{
		ItemsCold: itemsCold,
		ItemsHot:  itemsHot,
	})
	if err != nil {
		return errors.Annotate(err, "failed to marshal result json").Err()
	}
	if err := ioutil.WriteFile(jsonPath, statsJSON, 0664); err != nil {
		return errors.Annotate(err, "failed to write stats json to %s", jsonPath).Err()
	}
	return nil
}
<|endoftext|>


<commit_before>package mss

import (
	"log"
	"os"
	"os/signal"
	"time"

	"golang.org/x/net/context"
)

var (
	// holds a CancelFunc when it's running
	shutdown context.CancelFunc

	// holds a channel that will be closed once shutdown is complete
	done chan interface{}
)

// Set up signal handling to always properly shutdown
func init() {
	c := make(chan os.Signal)
	signal.Notify(c, os.Interrupt, os.Kill)

	go func() {
		<-c
		shutdown()
	}()
}

func StartAgent(d Driver, maxBatchSize uint, maxElapsedTime time.Duration) chan<- *Measurement {
	var ctx context.Context

	ctx, shutdown = context.WithCancel(context.Background())
	done = make(chan interface{})

	agent := newAgent(d, maxBatchSize, maxElapsedTime)
	go func() {
		agent.Run(ctx)
		close(done)
	}()

	return agent.ch
}

func Shutdown() {
	if shutdown != nil {
		shutdown()
		<-done
	}
}

// agent handles the tracking and persistence of measurements, allowing them
// to be batched for example.
type agent struct {
	driver         Driver
	maxBatchSize   uint
	maxElapsedTime time.Duration
	ch             chan *Measurement
}

// NewAgent creates a new Agent based on the provided configuration.
func newAgent(d Driver, maxBatchSize uint, maxElapsedTime time.Duration) *agent {
	return &agent{
		driver:         d,
		maxBatchSize:   maxBatchSize,
		maxElapsedTime: maxElapsedTime,
		ch:             make(chan *Measurement),
	}
}

// Run loops continuosly processing batch until the context gets canceled.
func (a *agent) Run(ctx context.Context) {
	var (
		timeC = time.After(a.maxElapsedTime)
		batch = make([]*Measurement, 0, a.maxBatchSize)
	)

	log.Println("mss: agent started")

loop:
	for {
		select {
		case m := <-a.ch:
			batch = append(batch, m)
			if len(batch) == cap(batch) {
				log.Printf("mss: persisting %d measurements", len(batch))
				if err := a.driver.Persist(batch); err != nil {
					log.Printf("mss: [error] %s", err)
				}
				batch = batch[0:0]
				timeC = time.After(a.maxElapsedTime)
			}

		case <-timeC:
			if len(batch) > 0 {
				log.Printf("mss: %s passed, persisting %d measurements", a.maxElapsedTime, len(batch))
				if err := a.driver.Persist(batch); err != nil {
					log.Printf("mss: [error] %s", err)
				}
				batch = batch[0:0]
			}
			timeC = time.After(a.maxElapsedTime)

		case <-ctx.Done():
			if len(batch) > 0 {
				log.Printf("mss: shuttind down, persisting %d measurements", len(batch))
				if err := a.driver.Persist(batch); err != nil {
					log.Printf("mss: [error] %s", err)
				}
			}

			break loop
		}
	}

	log.Println("mss: agent stopped")
}

// Track takes a measurement and tracks it, and eventually persists it depending
// on batching and timing.
func (a *agent) Track(m *Measurement) error {
	if err := m.Finish(); err != nil {
		return err
	}

	a.ch <- m
	return nil
}
<commit_msg>Simplify shutdown<commit_after>package mss

import (
	"log"
	"os"
	"os/signal"
	"time"
)

var (
	// channel for coordinating shutdown
	shutdown chan interface{}
)

// Set up signal handling to always properly shutdown
func init() {
	c := make(chan os.Signal)
	signal.Notify(c, os.Interrupt, os.Kill)

	go func() {
		<-c
		Shutdown()
	}()
}

func StartAgent(d Driver, maxBatchSize uint, maxElapsedTime time.Duration) chan<- *Measurement {
	shutdown = make(chan interface{})

	agent := newAgent(d, maxBatchSize, maxElapsedTime)
	go func() {
		agent.Run(shutdown)
		close(shutdown)
	}()

	return agent.ch
}

func Shutdown() {
	if shutdown != nil {
		shutdown <- nil
		<-shutdown
	}
}

// agent handles the tracking and persistence of measurements, allowing them
// to be batched for example.
type agent struct {
	driver         Driver
	maxBatchSize   uint
	maxElapsedTime time.Duration
	ch             chan *Measurement
}

// NewAgent creates a new Agent based on the provided configuration.
func newAgent(d Driver, maxBatchSize uint, maxElapsedTime time.Duration) *agent {
	return &agent{
		driver:         d,
		maxBatchSize:   maxBatchSize,
		maxElapsedTime: maxElapsedTime,
		ch:             make(chan *Measurement),
	}
}

// Run loops continuosly processing batch until the context gets canceled.
func (a *agent) Run(shutdown <-chan interface{}) {
	var (
		timeC = time.After(a.maxElapsedTime)
		batch = make([]*Measurement, 0, a.maxBatchSize)
	)

	log.Println("mss: agent started")

loop:
	for {
		select {
		case m := <-a.ch:
			batch = append(batch, m)
			if len(batch) == cap(batch) {
				log.Printf("mss: persisting %d measurements", len(batch))
				if err := a.driver.Persist(batch); err != nil {
					log.Printf("mss: [error] %s", err)
				}
				batch = batch[0:0]
				timeC = time.After(a.maxElapsedTime)
			}

		case <-timeC:
			if len(batch) > 0 {
				log.Printf("mss: %s passed, persisting %d measurements", a.maxElapsedTime, len(batch))
				if err := a.driver.Persist(batch); err != nil {
					log.Printf("mss: [error] %s", err)
				}
				batch = batch[0:0]
			}
			timeC = time.After(a.maxElapsedTime)

		case <-shutdown:
			if len(batch) > 0 {
				log.Printf("mss: shuttind down, persisting %d measurements", len(batch))
				if err := a.driver.Persist(batch); err != nil {
					log.Printf("mss: [error] %s", err)
				}
			}

			break loop
		}
	}

	log.Println("mss: agent stopped")
}

// Track takes a measurement and tracks it, and eventually persists it depending
// on batching and timing.
func (a *agent) Track(m *Measurement) error {
	if err := m.Finish(); err != nil {
		return err
	}

	a.ch <- m
	return nil
}
<|endoftext|>


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

package circonusgometrics

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

	"github.com/circonus-labs/circonus-gometrics/api"
	"github.com/hashicorp/go-retryablehttp"
)

func (m *CirconusMetrics) submit(output Metrics, newMetrics map[string]*api.CheckBundleMetric) {

	// if there is nowhere to send metrics to, just return.
	if !m.check.IsReady() {
		m.Log.Printf("[WARN] check not ready, skipping metric submission")
		return
	}

	// update check if there are any new metrics or, if metric tags have been added since last submit
	m.check.UpdateCheck(newMetrics)

	str, err := json.Marshal(output)
	if err != nil {
		m.Log.Printf("[ERROR] marshaling output %+v", err)
		return
	}

	numStats, err := m.trapCall(str)
	if err != nil {
		m.Log.Printf("[ERROR] %+v\n", err)
		return
	}

	if m.Debug {
		m.Log.Printf("[DEBUG] %d stats sent\n", numStats)
	}
}

func (m *CirconusMetrics) trapCall(payload []byte) (int, error) {
	trap, err := m.check.GetSubmissionURL()
	if err != nil {
		return 0, err
	}

	dataReader := bytes.NewReader(payload)

	req, err := retryablehttp.NewRequest("PUT", trap.URL.String(), dataReader)
	if err != nil {
		return 0, err
	}
	req.Header.Add("Content-Type", "application/json")
	req.Header.Add("Accept", "application/json")

	// keep last HTTP error in the event of retry failure
	var lastHTTPError error
	retryPolicy := func(resp *http.Response, err error) (bool, error) {
		if err != nil {
			lastHTTPError = err
			return true, err
		}
		// Check the response code. We retry on 500-range responses to allow
		// the server time to recover, as 500's are typically not permanent
		// errors and may relate to outages on the server side. This will catch
		// invalid response codes as well, like 0 and 999.
		if resp.StatusCode == 0 || resp.StatusCode >= 500 {
			body, readErr := ioutil.ReadAll(resp.Body)
			if readErr != nil {
				lastHTTPError = fmt.Errorf("- last HTTP error: %d %+v", resp.StatusCode, readErr)
			} else {
				lastHTTPError = fmt.Errorf("- last HTTP error: %d %s", resp.StatusCode, string(body))
			}
			return true, nil
		}
		return false, nil
	}

	client := retryablehttp.NewClient()
	if trap.URL.Scheme == "https" {
		client.HTTPClient.Transport = &http.Transport{
			Proxy: http.ProxyFromEnvironment,
			Dial: (&net.Dialer{
				Timeout:   30 * time.Second,
				KeepAlive: 30 * time.Second,
			}).Dial,
			TLSHandshakeTimeout: 10 * time.Second,
			TLSClientConfig:     trap.TLS,
			DisableKeepAlives:   true,
			MaxIdleConnsPerHost: -1,
			DisableCompression:  true,
		}
	} else {
		client.HTTPClient.Transport = &http.Transport{
			Proxy: http.ProxyFromEnvironment,
			Dial: (&net.Dialer{
				Timeout:   30 * time.Second,
				KeepAlive: 30 * time.Second,
			}).Dial,
			TLSHandshakeTimeout: 10 * time.Second,
			DisableKeepAlives:   true,
			MaxIdleConnsPerHost: -1,
			DisableCompression:  true,
		}
	}
	client.RetryWaitMin = 1 * time.Second
	client.RetryWaitMax = 5 * time.Second
	client.RetryMax = 3
	// retryablehttp only groks log or no log
	// but, outputs everything as [DEBUG] messages
	if m.Debug {
		client.Logger = m.Log
	} else {
		client.Logger = log.New(ioutil.Discard, "", log.LstdFlags)
	}
	client.CheckRetry = retryPolicy

	attempts := -1
	client.RequestLogHook = func(logger *log.Logger, req *http.Request, retryNumber int) {
		attempts = retryNumber
	}

	resp, err := client.Do(req)
	if err != nil {
		if lastHTTPError != nil {
			return 0, fmt.Errorf("[ERROR] submitting: %+v %+v", err, lastHTTPError)
		}
		if attempts == client.RetryMax {
			m.check.RefreshTrap()
		}
		return 0, err
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		m.Log.Printf("[ERROR] reading body, proceeding. %s\n", err)
	}

	var response map[string]interface{}
	if err := json.Unmarshal(body, &response); err != nil {
		m.Log.Printf("[ERROR] parsing body, proceeding. %v (%s)\n", err, body)
	}

	if resp.StatusCode != 200 {
		return 0, errors.New("[ERROR] bad response code: " + strconv.Itoa(resp.StatusCode))
	}
	switch v := response["stats"].(type) {
	case float64:
		return int(v), nil
	case int:
		return v, nil
	default:
	}
	return 0, errors.New("[ERROR] bad response type")
}
<commit_msg>Handle 204 from circonus-agent<commit_after>// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package circonusgometrics

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

	"github.com/circonus-labs/circonus-gometrics/api"
	"github.com/hashicorp/go-retryablehttp"
)

func (m *CirconusMetrics) submit(output Metrics, newMetrics map[string]*api.CheckBundleMetric) {

	// if there is nowhere to send metrics to, just return.
	if !m.check.IsReady() {
		m.Log.Printf("[WARN] check not ready, skipping metric submission")
		return
	}

	// update check if there are any new metrics or, if metric tags have been added since last submit
	m.check.UpdateCheck(newMetrics)

	str, err := json.Marshal(output)
	if err != nil {
		m.Log.Printf("[ERROR] marshaling output %+v", err)
		return
	}

	numStats, err := m.trapCall(str)
	if err != nil {
		m.Log.Printf("[ERROR] %+v\n", err)
		return
	}

	if m.Debug {
		m.Log.Printf("[DEBUG] %d stats sent\n", numStats)
	}
}

func (m *CirconusMetrics) trapCall(payload []byte) (int, error) {
	trap, err := m.check.GetSubmissionURL()
	if err != nil {
		return 0, err
	}

	dataReader := bytes.NewReader(payload)

	req, err := retryablehttp.NewRequest("PUT", trap.URL.String(), dataReader)
	if err != nil {
		return 0, err
	}
	req.Header.Add("Content-Type", "application/json")
	req.Header.Add("Accept", "application/json")

	// keep last HTTP error in the event of retry failure
	var lastHTTPError error
	retryPolicy := func(resp *http.Response, err error) (bool, error) {
		if err != nil {
			lastHTTPError = err
			return true, err
		}
		// Check the response code. We retry on 500-range responses to allow
		// the server time to recover, as 500's are typically not permanent
		// errors and may relate to outages on the server side. This will catch
		// invalid response codes as well, like 0 and 999.
		if resp.StatusCode == 0 || resp.StatusCode >= 500 {
			body, readErr := ioutil.ReadAll(resp.Body)
			if readErr != nil {
				lastHTTPError = fmt.Errorf("- last HTTP error: %d %+v", resp.StatusCode, readErr)
			} else {
				lastHTTPError = fmt.Errorf("- last HTTP error: %d %s", resp.StatusCode, string(body))
			}
			return true, nil
		}
		return false, nil
	}

	client := retryablehttp.NewClient()
	if trap.URL.Scheme == "https" {
		client.HTTPClient.Transport = &http.Transport{
			Proxy: http.ProxyFromEnvironment,
			Dial: (&net.Dialer{
				Timeout:   30 * time.Second,
				KeepAlive: 30 * time.Second,
			}).Dial,
			TLSHandshakeTimeout: 10 * time.Second,
			TLSClientConfig:     trap.TLS,
			DisableKeepAlives:   true,
			MaxIdleConnsPerHost: -1,
			DisableCompression:  true,
		}
	} else {
		client.HTTPClient.Transport = &http.Transport{
			Proxy: http.ProxyFromEnvironment,
			Dial: (&net.Dialer{
				Timeout:   30 * time.Second,
				KeepAlive: 30 * time.Second,
			}).Dial,
			TLSHandshakeTimeout: 10 * time.Second,
			DisableKeepAlives:   true,
			MaxIdleConnsPerHost: -1,
			DisableCompression:  true,
		}
	}
	client.RetryWaitMin = 1 * time.Second
	client.RetryWaitMax = 5 * time.Second
	client.RetryMax = 3
	// retryablehttp only groks log or no log
	// but, outputs everything as [DEBUG] messages
	if m.Debug {
		client.Logger = m.Log
	} else {
		client.Logger = log.New(ioutil.Discard, "", log.LstdFlags)
	}
	client.CheckRetry = retryPolicy

	attempts := -1
	client.RequestLogHook = func(logger *log.Logger, req *http.Request, retryNumber int) {
		attempts = retryNumber
	}

	resp, err := client.Do(req)
	if err != nil {
		if lastHTTPError != nil {
			return 0, fmt.Errorf("[ERROR] submitting: %+v %+v", err, lastHTTPError)
		}
		if attempts == client.RetryMax {
			m.check.RefreshTrap()
		}
		return 0, err
	}

	defer resp.Body.Close()

	// no content - expected result from circonus-agent when metrics accepted
	if resp.StatusCode == http.StatusNoContent {
		return -1, nil
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		m.Log.Printf("[ERROR] reading body, proceeding. %s\n", err)
	}

	var response map[string]interface{}
	if err := json.Unmarshal(body, &response); err != nil {
		m.Log.Printf("[ERROR] parsing body, proceeding. %v (%s)\n", err, body)
	}

	if resp.StatusCode != http.StatusOK {
		return 0, errors.New("[ERROR] bad response code: " + strconv.Itoa(resp.StatusCode))
	}
	switch v := response["stats"].(type) {
	case float64:
		return int(v), nil
	case int:
		return v, nil
	default:
	}
	return 0, errors.New("[ERROR] bad response type")
}
<|endoftext|>