<commit_before>package kubernetes

import (
	"fmt"
	"log"
	"time"

	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
	"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
	api "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	pkgApi "k8s.io/apimachinery/pkg/types"
)

func resourceKubernetesService() *schema.Resource {
	return &schema.Resource{
		Create: resourceKubernetesServiceCreate,
		Read:   resourceKubernetesServiceRead,
		Exists: resourceKubernetesServiceExists,
		Update: resourceKubernetesServiceUpdate,
		Delete: resourceKubernetesServiceDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Timeouts: &schema.ResourceTimeout{
			Create: schema.DefaultTimeout(10 * time.Minute),
		},

		Schema: map[string]*schema.Schema{
			"metadata": namespacedMetadataSchema("service", true),
			"spec": {
				Type:        schema.TypeList,
				Description: "Spec defines the behavior of a service. https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status",
				Required:    true,
				MaxItems:    1,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"cluster_ip": {
							Type:        schema.TypeString,
							Description: "The IP address of the service. It is usually assigned randomly by the master. If an address is specified manually and is not in use by others, it will be allocated to the service; otherwise, creation of the service will fail. `None` can be specified for headless services when proxying is not required. Ignored if type is `ExternalName`. More info: http://kubernetes.io/docs/user-guide/services#virtual-ips-and-service-proxies",
							Optional:    true,
							ForceNew:    true,
							Computed:    true,
						},
						"external_ips": {
							Type:        schema.TypeSet,
							Description: "A list of IP addresses for which nodes in the cluster will also accept traffic for this service. These IPs are not managed by Kubernetes. The user is responsible for ensuring that traffic arrives at a node with this IP.  A common example is external load-balancers that are not part of the Kubernetes system.",
							Optional:    true,
							Elem:        &schema.Schema{Type: schema.TypeString},
							Set:         schema.HashString,
						},
						"external_name": {
							Type:        schema.TypeString,
							Description: "The external reference that kubedns or equivalent will return as a CNAME record for this service. No proxying will be involved. Must be a valid DNS name and requires `type` to be `ExternalName`.",
							Optional:    true,
						},
						"external_traffic_policy": {
							Type:         schema.TypeString,
							Description:  "Denotes if this Service desires to route external traffic to node-local or cluster-wide endpoints. `Local` preserves the client source IP and avoids a second hop for LoadBalancer and Nodeport type services, but risks potentially imbalanced traffic spreading. `Cluster` obscures the client source IP and may cause a second hop to another node, but should have good overall load-spreading. More info: https://kubernetes.io/docs/tutorials/services/source-ip/",
							Optional:     true,
							Computed:     true,
							ValidateFunc: validation.StringInSlice([]string{"Local", "Cluster"}, false),
						},
						"load_balancer_ip": {
							Type:        schema.TypeString,
							Description: "Only applies to `type = LoadBalancer`. LoadBalancer will get created with the IP specified in this field. This feature depends on whether the underlying cloud-provider supports specifying this field when a load balancer is created. This field will be ignored if the cloud-provider does not support the feature.",
							Optional:    true,
						},
						"load_balancer_source_ranges": {
							Type:        schema.TypeSet,
							Description: "If specified and supported by the platform, this will restrict traffic through the cloud-provider load-balancer will be restricted to the specified client IPs. This field will be ignored if the cloud-provider does not support the feature. More info: http://kubernetes.io/docs/user-guide/services-firewalls",
							Optional:    true,
							Elem:        &schema.Schema{Type: schema.TypeString},
							Set:         schema.HashString,
						},
						"port": {
							Type:        schema.TypeList,
							Description: "The list of ports that are exposed by this service. More info: http://kubernetes.io/docs/user-guide/services#virtual-ips-and-service-proxies",
							Optional:    true,
							MinItems:    1,
							Elem: &schema.Resource{
								Schema: map[string]*schema.Schema{
									"name": {
										Type:        schema.TypeString,
										Description: "The name of this port within the service. All ports within the service must have unique names. Optional if only one ServicePort is defined on this service.",
										Optional:    true,
									},
									"node_port": {
										Type:        schema.TypeInt,
										Description: "The port on each node on which this service is exposed when `type` is `NodePort` or `LoadBalancer`. Usually assigned by the system. If specified, it will be allocated to the service if unused or else creation of the service will fail. Default is to auto-allocate a port if the `type` of this service requires one. More info: http://kubernetes.io/docs/user-guide/services#type--nodeport",
										Computed:    true,
										Optional:    true,
									},
									"port": {
										Type:        schema.TypeInt,
										Description: "The port that will be exposed by this service.",
										Required:    true,
									},
									"protocol": {
										Type:        schema.TypeString,
										Description: "The IP protocol for this port. Supports `TCP` and `UDP`. Default is `TCP`.",
										Optional:    true,
										Default:     "TCP",
										ValidateFunc: validation.StringInSlice([]string{
											"TCP",
											"UDP",
											"SCTP",
										}, false),
									},
									"target_port": {
										Type:        schema.TypeString,
										Description: "Number or name of the port to access on the pods targeted by the service. Number must be in the range 1 to 65535. This field is ignored for services with `cluster_ip = \"None\"`. More info: http://kubernetes.io/docs/user-guide/services#defining-a-service",
										Optional:    true,
										Computed:    true,
									},
								},
							},
						},
						"publish_not_ready_addresses": {
							Type:        schema.TypeBool,
							Optional:    true,
							Default:     false,
							Description: "When set to true, indicates that DNS implementations must publish the `notReadyAddresses` of subsets for the Endpoints associated with the Service. The default value is `false`. The primary use case for setting this field is to use a StatefulSet's Headless Service to propagate `SRV` records for its Pods without respect to their readiness for purpose of peer discovery.",
						},
						"selector": {
							Type:        schema.TypeMap,
							Description: "Route service traffic to pods with label keys and values matching this selector. Only applies to types `ClusterIP`, `NodePort`, and `LoadBalancer`. More info: http://kubernetes.io/docs/user-guide/services#overview",
							Optional:    true,
						},
						"session_affinity": {
							Type:        schema.TypeString,
							Description: "Used to maintain session affinity. Supports `ClientIP` and `None`. Defaults to `None`. More info: http://kubernetes.io/docs/user-guide/services#virtual-ips-and-service-proxies",
							Optional:    true,
							Default:     "None",
							ValidateFunc: validation.StringInSlice([]string{
								"ClientIP",
								"None",
							}, false),
						},
						"type": {
							Type:        schema.TypeString,
							Description: "Determines how the service is exposed. Defaults to `ClusterIP`. Valid options are `ExternalName`, `ClusterIP`, `NodePort`, and `LoadBalancer`. `ExternalName` maps to the specified `external_name`. More info: http://kubernetes.io/docs/user-guide/services#overview",
							Optional:    true,
							Default:     "ClusterIP",
							ValidateFunc: validation.StringInSlice([]string{
								"ClusterIP",
								"ExternalName",
								"NodePort",
								"LoadBalancer",
							}, false),
						},
						"health_check_node_port": {
							Type:        schema.TypeInt,
							Description: "Specifies the Healthcheck NodePort for the service. Only effects when type is set to `LoadBalancer` and external_traffic_policy is set to `Local`.",
							Optional:    true,
							Computed:    true,
						},
					},
				},
			},
			"load_balancer_ingress": {
				Type:     schema.TypeList,
				Computed: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"ip": {
							Type:     schema.TypeString,
							Computed: true,
						},
						"hostname": {
							Type:     schema.TypeString,
							Computed: true,
						},
					},
				},
			},
		},
	}
}

func resourceKubernetesServiceCreate(d *schema.ResourceData, meta interface{}) error {
	conn, err := meta.(KubeClientsets).MainClientset()
	if err != nil {
		return err
	}

	metadata := expandMetadata(d.Get("metadata").([]interface{}))
	svc := api.Service{
		ObjectMeta: metadata,
		Spec:       expandServiceSpec(d.Get("spec").([]interface{})),
	}
	log.Printf("[INFO] Creating new service: %#v", svc)
	out, err := conn.CoreV1().Services(metadata.Namespace).Create(&svc)
	if err != nil {
		return err
	}
	log.Printf("[INFO] Submitted new service: %#v", out)
	d.SetId(buildId(out.ObjectMeta))

	if out.Spec.Type == api.ServiceTypeLoadBalancer {
		log.Printf("[DEBUG] Waiting for load balancer to assign IP/hostname")

		err = resource.Retry(d.Timeout(schema.TimeoutCreate), func() *resource.RetryError {
			svc, err := conn.CoreV1().Services(out.Namespace).Get(out.Name, meta_v1.GetOptions{})
			if err != nil {
				log.Printf("[DEBUG] Received error: %#v", err)
				return resource.NonRetryableError(err)
			}

			lbIngress := svc.Status.LoadBalancer.Ingress

			log.Printf("[INFO] Received service status: %#v", svc.Status)
			if len(lbIngress) > 0 {
				return nil
			}

			return resource.RetryableError(fmt.Errorf(
				"Waiting for service %q to assign IP/hostname for a load balancer", d.Id()))
		})
		if err != nil {
			lastWarnings, wErr := getLastWarningsForObject(conn, out.ObjectMeta, "Service", 3)
			if wErr != nil {
				return wErr
			}
			return fmt.Errorf("%s%s", err, stringifyEvents(lastWarnings))
		}
	}

	return resourceKubernetesServiceRead(d, meta)
}

func resourceKubernetesServiceRead(d *schema.ResourceData, meta interface{}) error {
	conn, err := meta.(KubeClientsets).MainClientset()
	if err != nil {
		return err
	}

	namespace, name, err := idParts(d.Id())
	if err != nil {
		return err
	}

	log.Printf("[INFO] Reading service %s", name)
	svc, err := conn.CoreV1().Services(namespace).Get(name, meta_v1.GetOptions{})
	if err != nil {
		log.Printf("[DEBUG] Received error: %#v", err)
		return err
	}
	log.Printf("[INFO] Received service: %#v", svc)
	err = d.Set("metadata", flattenMetadata(svc.ObjectMeta, d))
	if err != nil {
		return err
	}

	err = d.Set("load_balancer_ingress", flattenLoadBalancerIngress(svc.Status.LoadBalancer.Ingress))
	if err != nil {
		return err
	}

	flattened := flattenServiceSpec(svc.Spec)
	log.Printf("[DEBUG] Flattened service spec: %#v", flattened)
	err = d.Set("spec", flattened)
	if err != nil {
		return err
	}

	return nil
}

func resourceKubernetesServiceUpdate(d *schema.ResourceData, meta interface{}) error {
	conn, err := meta.(KubeClientsets).MainClientset()
	if err != nil {
		return err
	}

	namespace, name, err := idParts(d.Id())
	if err != nil {
		return err
	}

	ops := patchMetadata("metadata.0.", "/metadata/", d)
	if d.HasChange("spec") {
		serverVersion, err := conn.ServerVersion()
		if err != nil {
			return err
		}
		diffOps, err := patchServiceSpec("spec.0.", "/spec/", d, serverVersion)
		if err != nil {
			return err
		}
		ops = append(ops, diffOps...)
	}
	data, err := ops.MarshalJSON()
	if err != nil {
		return fmt.Errorf("Failed to marshal update operations: %s", err)
	}
	log.Printf("[INFO] Updating service %q: %v", name, string(data))
	out, err := conn.CoreV1().Services(namespace).Patch(name, pkgApi.JSONPatchType, data)
	if err != nil {
		return fmt.Errorf("Failed to update service: %s", err)
	}
	log.Printf("[INFO] Submitted updated service: %#v", out)
	d.SetId(buildId(out.ObjectMeta))

	return resourceKubernetesServiceRead(d, meta)
}

func resourceKubernetesServiceDelete(d *schema.ResourceData, meta interface{}) error {
	conn, err := meta.(KubeClientsets).MainClientset()
	if err != nil {
		return err
	}

	namespace, name, err := idParts(d.Id())
	if err != nil {
		return err
	}

	log.Printf("[INFO] Deleting service: %#v", name)
	err = conn.CoreV1().Services(namespace).Delete(name, &meta_v1.DeleteOptions{})
	if err != nil {
		return err
	}

	log.Printf("[INFO] Service %s deleted", name)

	d.SetId("")
	return nil
}

func resourceKubernetesServiceExists(d *schema.ResourceData, meta interface{}) (bool, error) {
	conn, err := meta.(KubeClientsets).MainClientset()
	if err != nil {
		return false, err
	}

	namespace, name, err := idParts(d.Id())
	if err != nil {
		return false, err
	}

	log.Printf("[INFO] Checking service %s", name)
	_, err = conn.CoreV1().Services(namespace).Get(name, meta_v1.GetOptions{})
	if err != nil {
		if statusErr, ok := err.(*errors.StatusError); ok && statusErr.ErrStatus.Code == 404 {
			return false, nil
		}
		log.Printf("[DEBUG] Received error: %#v", err)
	}
	return true, err
}
<commit_msg>wait for service to be deleted (#976)<commit_after>package kubernetes

import (
	"fmt"
	"log"
	"time"

	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
	"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
	api "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	pkgApi "k8s.io/apimachinery/pkg/types"
)

func resourceKubernetesService() *schema.Resource {
	return &schema.Resource{
		Create: resourceKubernetesServiceCreate,
		Read:   resourceKubernetesServiceRead,
		Exists: resourceKubernetesServiceExists,
		Update: resourceKubernetesServiceUpdate,
		Delete: resourceKubernetesServiceDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Timeouts: &schema.ResourceTimeout{
			Create: schema.DefaultTimeout(10 * time.Minute),
		},

		Schema: map[string]*schema.Schema{
			"metadata": namespacedMetadataSchema("service", true),
			"spec": {
				Type:        schema.TypeList,
				Description: "Spec defines the behavior of a service. https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status",
				Required:    true,
				MaxItems:    1,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"cluster_ip": {
							Type:        schema.TypeString,
							Description: "The IP address of the service. It is usually assigned randomly by the master. If an address is specified manually and is not in use by others, it will be allocated to the service; otherwise, creation of the service will fail. `None` can be specified for headless services when proxying is not required. Ignored if type is `ExternalName`. More info: http://kubernetes.io/docs/user-guide/services#virtual-ips-and-service-proxies",
							Optional:    true,
							ForceNew:    true,
							Computed:    true,
						},
						"external_ips": {
							Type:        schema.TypeSet,
							Description: "A list of IP addresses for which nodes in the cluster will also accept traffic for this service. These IPs are not managed by Kubernetes. The user is responsible for ensuring that traffic arrives at a node with this IP.  A common example is external load-balancers that are not part of the Kubernetes system.",
							Optional:    true,
							Elem:        &schema.Schema{Type: schema.TypeString},
							Set:         schema.HashString,
						},
						"external_name": {
							Type:        schema.TypeString,
							Description: "The external reference that kubedns or equivalent will return as a CNAME record for this service. No proxying will be involved. Must be a valid DNS name and requires `type` to be `ExternalName`.",
							Optional:    true,
						},
						"external_traffic_policy": {
							Type:         schema.TypeString,
							Description:  "Denotes if this Service desires to route external traffic to node-local or cluster-wide endpoints. `Local` preserves the client source IP and avoids a second hop for LoadBalancer and Nodeport type services, but risks potentially imbalanced traffic spreading. `Cluster` obscures the client source IP and may cause a second hop to another node, but should have good overall load-spreading. More info: https://kubernetes.io/docs/tutorials/services/source-ip/",
							Optional:     true,
							Computed:     true,
							ValidateFunc: validation.StringInSlice([]string{"Local", "Cluster"}, false),
						},
						"load_balancer_ip": {
							Type:        schema.TypeString,
							Description: "Only applies to `type = LoadBalancer`. LoadBalancer will get created with the IP specified in this field. This feature depends on whether the underlying cloud-provider supports specifying this field when a load balancer is created. This field will be ignored if the cloud-provider does not support the feature.",
							Optional:    true,
						},
						"load_balancer_source_ranges": {
							Type:        schema.TypeSet,
							Description: "If specified and supported by the platform, this will restrict traffic through the cloud-provider load-balancer will be restricted to the specified client IPs. This field will be ignored if the cloud-provider does not support the feature. More info: http://kubernetes.io/docs/user-guide/services-firewalls",
							Optional:    true,
							Elem:        &schema.Schema{Type: schema.TypeString},
							Set:         schema.HashString,
						},
						"port": {
							Type:        schema.TypeList,
							Description: "The list of ports that are exposed by this service. More info: http://kubernetes.io/docs/user-guide/services#virtual-ips-and-service-proxies",
							Optional:    true,
							MinItems:    1,
							Elem: &schema.Resource{
								Schema: map[string]*schema.Schema{
									"name": {
										Type:        schema.TypeString,
										Description: "The name of this port within the service. All ports within the service must have unique names. Optional if only one ServicePort is defined on this service.",
										Optional:    true,
									},
									"node_port": {
										Type:        schema.TypeInt,
										Description: "The port on each node on which this service is exposed when `type` is `NodePort` or `LoadBalancer`. Usually assigned by the system. If specified, it will be allocated to the service if unused or else creation of the service will fail. Default is to auto-allocate a port if the `type` of this service requires one. More info: http://kubernetes.io/docs/user-guide/services#type--nodeport",
										Computed:    true,
										Optional:    true,
									},
									"port": {
										Type:        schema.TypeInt,
										Description: "The port that will be exposed by this service.",
										Required:    true,
									},
									"protocol": {
										Type:        schema.TypeString,
										Description: "The IP protocol for this port. Supports `TCP` and `UDP`. Default is `TCP`.",
										Optional:    true,
										Default:     "TCP",
										ValidateFunc: validation.StringInSlice([]string{
											"TCP",
											"UDP",
											"SCTP",
										}, false),
									},
									"target_port": {
										Type:        schema.TypeString,
										Description: "Number or name of the port to access on the pods targeted by the service. Number must be in the range 1 to 65535. This field is ignored for services with `cluster_ip = \"None\"`. More info: http://kubernetes.io/docs/user-guide/services#defining-a-service",
										Optional:    true,
										Computed:    true,
									},
								},
							},
						},
						"publish_not_ready_addresses": {
							Type:        schema.TypeBool,
							Optional:    true,
							Default:     false,
							Description: "When set to true, indicates that DNS implementations must publish the `notReadyAddresses` of subsets for the Endpoints associated with the Service. The default value is `false`. The primary use case for setting this field is to use a StatefulSet's Headless Service to propagate `SRV` records for its Pods without respect to their readiness for purpose of peer discovery.",
						},
						"selector": {
							Type:        schema.TypeMap,
							Description: "Route service traffic to pods with label keys and values matching this selector. Only applies to types `ClusterIP`, `NodePort`, and `LoadBalancer`. More info: http://kubernetes.io/docs/user-guide/services#overview",
							Optional:    true,
						},
						"session_affinity": {
							Type:        schema.TypeString,
							Description: "Used to maintain session affinity. Supports `ClientIP` and `None`. Defaults to `None`. More info: http://kubernetes.io/docs/user-guide/services#virtual-ips-and-service-proxies",
							Optional:    true,
							Default:     "None",
							ValidateFunc: validation.StringInSlice([]string{
								"ClientIP",
								"None",
							}, false),
						},
						"type": {
							Type:        schema.TypeString,
							Description: "Determines how the service is exposed. Defaults to `ClusterIP`. Valid options are `ExternalName`, `ClusterIP`, `NodePort`, and `LoadBalancer`. `ExternalName` maps to the specified `external_name`. More info: http://kubernetes.io/docs/user-guide/services#overview",
							Optional:    true,
							Default:     "ClusterIP",
							ValidateFunc: validation.StringInSlice([]string{
								"ClusterIP",
								"ExternalName",
								"NodePort",
								"LoadBalancer",
							}, false),
						},
						"health_check_node_port": {
							Type:        schema.TypeInt,
							Description: "Specifies the Healthcheck NodePort for the service. Only effects when type is set to `LoadBalancer` and external_traffic_policy is set to `Local`.",
							Optional:    true,
							Computed:    true,
						},
					},
				},
			},
			"load_balancer_ingress": {
				Type:     schema.TypeList,
				Computed: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"ip": {
							Type:     schema.TypeString,
							Computed: true,
						},
						"hostname": {
							Type:     schema.TypeString,
							Computed: true,
						},
					},
				},
			},
		},
	}
}

func resourceKubernetesServiceCreate(d *schema.ResourceData, meta interface{}) error {
	conn, err := meta.(KubeClientsets).MainClientset()
	if err != nil {
		return err
	}

	metadata := expandMetadata(d.Get("metadata").([]interface{}))
	svc := api.Service{
		ObjectMeta: metadata,
		Spec:       expandServiceSpec(d.Get("spec").([]interface{})),
	}
	log.Printf("[INFO] Creating new service: %#v", svc)
	out, err := conn.CoreV1().Services(metadata.Namespace).Create(&svc)
	if err != nil {
		return err
	}
	log.Printf("[INFO] Submitted new service: %#v", out)
	d.SetId(buildId(out.ObjectMeta))

	if out.Spec.Type == api.ServiceTypeLoadBalancer {
		log.Printf("[DEBUG] Waiting for load balancer to assign IP/hostname")

		err = resource.Retry(d.Timeout(schema.TimeoutCreate), func() *resource.RetryError {
			svc, err := conn.CoreV1().Services(out.Namespace).Get(out.Name, meta_v1.GetOptions{})
			if err != nil {
				log.Printf("[DEBUG] Received error: %#v", err)
				return resource.NonRetryableError(err)
			}

			lbIngress := svc.Status.LoadBalancer.Ingress

			log.Printf("[INFO] Received service status: %#v", svc.Status)
			if len(lbIngress) > 0 {
				return nil
			}

			return resource.RetryableError(fmt.Errorf(
				"Waiting for service %q to assign IP/hostname for a load balancer", d.Id()))
		})
		if err != nil {
			lastWarnings, wErr := getLastWarningsForObject(conn, out.ObjectMeta, "Service", 3)
			if wErr != nil {
				return wErr
			}
			return fmt.Errorf("%s%s", err, stringifyEvents(lastWarnings))
		}
	}

	return resourceKubernetesServiceRead(d, meta)
}

func resourceKubernetesServiceRead(d *schema.ResourceData, meta interface{}) error {
	conn, err := meta.(KubeClientsets).MainClientset()
	if err != nil {
		return err
	}

	namespace, name, err := idParts(d.Id())
	if err != nil {
		return err
	}

	log.Printf("[INFO] Reading service %s", name)
	svc, err := conn.CoreV1().Services(namespace).Get(name, meta_v1.GetOptions{})
	if err != nil {
		log.Printf("[DEBUG] Received error: %#v", err)
		return err
	}
	log.Printf("[INFO] Received service: %#v", svc)
	err = d.Set("metadata", flattenMetadata(svc.ObjectMeta, d))
	if err != nil {
		return err
	}

	err = d.Set("load_balancer_ingress", flattenLoadBalancerIngress(svc.Status.LoadBalancer.Ingress))
	if err != nil {
		return err
	}

	flattened := flattenServiceSpec(svc.Spec)
	log.Printf("[DEBUG] Flattened service spec: %#v", flattened)
	err = d.Set("spec", flattened)
	if err != nil {
		return err
	}

	return nil
}

func resourceKubernetesServiceUpdate(d *schema.ResourceData, meta interface{}) error {
	conn, err := meta.(KubeClientsets).MainClientset()
	if err != nil {
		return err
	}

	namespace, name, err := idParts(d.Id())
	if err != nil {
		return err
	}

	ops := patchMetadata("metadata.0.", "/metadata/", d)
	if d.HasChange("spec") {
		serverVersion, err := conn.ServerVersion()
		if err != nil {
			return err
		}
		diffOps, err := patchServiceSpec("spec.0.", "/spec/", d, serverVersion)
		if err != nil {
			return err
		}
		ops = append(ops, diffOps...)
	}
	data, err := ops.MarshalJSON()
	if err != nil {
		return fmt.Errorf("Failed to marshal update operations: %s", err)
	}
	log.Printf("[INFO] Updating service %q: %v", name, string(data))
	out, err := conn.CoreV1().Services(namespace).Patch(name, pkgApi.JSONPatchType, data)
	if err != nil {
		return fmt.Errorf("Failed to update service: %s", err)
	}
	log.Printf("[INFO] Submitted updated service: %#v", out)
	d.SetId(buildId(out.ObjectMeta))

	return resourceKubernetesServiceRead(d, meta)
}

func resourceKubernetesServiceDelete(d *schema.ResourceData, meta interface{}) error {
	conn, err := meta.(KubeClientsets).MainClientset()
	if err != nil {
		return err
	}

	namespace, name, err := idParts(d.Id())
	if err != nil {
		return err
	}

	log.Printf("[INFO] Deleting service: %#v", name)
	err = conn.CoreV1().Services(namespace).Delete(name, &meta_v1.DeleteOptions{})
	if err != nil {
		return err
	}

	err = resource.Retry(d.Timeout(schema.TimeoutDelete), func() *resource.RetryError {
		_, err := conn.CoreV1().Services(namespace).Get(name, meta_v1.GetOptions{})
		if err != nil {
			if statusErr, ok := err.(*errors.StatusError); ok && statusErr.ErrStatus.Code == 404 {
				return nil
			}
			return resource.NonRetryableError(err)
		}

		e := fmt.Errorf("Service (%s) still exists", d.Id())
		return resource.RetryableError(e)
	})
	if err != nil {
		return err
	}

	log.Printf("[INFO] Service %s deleted", name)

	d.SetId("")
	return nil
}

func resourceKubernetesServiceExists(d *schema.ResourceData, meta interface{}) (bool, error) {
	conn, err := meta.(KubeClientsets).MainClientset()
	if err != nil {
		return false, err
	}

	namespace, name, err := idParts(d.Id())
	if err != nil {
		return false, err
	}

	log.Printf("[INFO] Checking service %s", name)
	_, err = conn.CoreV1().Services(namespace).Get(name, meta_v1.GetOptions{})
	if err != nil {
		if statusErr, ok := err.(*errors.StatusError); ok && statusErr.ErrStatus.Code == 404 {
			return false, nil
		}
		log.Printf("[DEBUG] Received error: %#v", err)
	}
	return true, err
}
<|endoftext|>


<commit_before>package pgproto3

import (
	"io"

	"github.com/jackc/chunkreader"
)

// ChunkReader is an interface to decouple github.com/jackc/chunkreader from this package.
type ChunkReader interface {
	// Next returns buf filled with the next n bytes. If an error occurs, buf will be nil. Next must
	// not reuse buf. In case of error, Next must preserve partially read data.
	Next(n int) (buf []byte, err error)
}

func NewChunkReader(r io.Reader) ChunkReader {
	return chunkreader.NewChunkReader(r)
}
<commit_msg>Clarify ChunkReader.Next contract<commit_after>package pgproto3

import (
	"io"

	"github.com/jackc/chunkreader"
)

// ChunkReader is an interface to decouple github.com/jackc/chunkreader from this package.
type ChunkReader interface {
	// Next returns buf filled with the next n bytes. If an error (including a partial read) occurs,
	// buf must be nil. Next must preserve any partially read data. Next must not reuse buf.
	Next(n int) (buf []byte, err error)
}

func NewChunkReader(r io.Reader) ChunkReader {
	return chunkreader.NewChunkReader(r)
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"fmt"
	"os"
)

const (
	// Alphabet soup for decoding
	Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
)

var (
	shift = flag.Int("shift", 0, "shift parameter for Ceaser Cipher")
	key   = flag.String("key", "", "keyword for Vigenere Cipher")
)

func abort(msg string) {
	fmt.Fprintln(os.Stderr, "Error:", msg)
	flag.Usage()
	os.Exit(1)
}

func main() {
	flag.Parse()
	if *shift == 0 && *key == "" {
		abort("Must give -shift (ceaser) or -key (vigenere) flag")
	}

	if *shift < 0 || *shift > 25 {
		abort("-shift option must range between 1 and 25")
	}

	reader := bufio.NewReader(os.Stdin)
	input, err := reader.ReadString('\n')
	if err != nil {
		fmt.Println("Failed to read input:", err)
		return
	}

	var res string
	if *shift > 0 {
		res = ceaserCipher(input)
	} else {
		res = vigeneteCipher(input)
	}

	fmt.Println("Response:")
	fmt.Println(res)
}

func ceaserRune(char rune, shift int) rune {
	if char < 'A' || char > 'Z' {
		return ' '
	}

	offset := (int(char) + shift - 'A') % 26
	return rune(Alphabet[offset])
}

func ceaserCipher(input string) string {
	result := ""

	for _, char := range input {
		result += string(ceaserRune(char, *shift))
	}

	return result
}

func vigeneteCipher(r string) string {
	return "Vigenete: Not Implemented"
}
<commit_msg>add simple flag to reverse our encryption<commit_after>package main

import (
	"bufio"
	"flag"
	"fmt"
	"os"
)

const (
	// Alphabet soup for decoding
	Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
)

var (
	shift   = flag.Int("shift", 0, "shift parameter for Ceaser Cipher")
	key     = flag.String("key", "", "keyword for Vigenere Cipher")
	decrypt = flag.Bool("decrypt", false, "Decrypt an encrypted string. Default is Encrypt")
)

func abort(msg string) {
	fmt.Fprintln(os.Stderr, "Error:", msg)
	flag.Usage()
	os.Exit(1)
}

func main() {
	flag.Parse()
	if *shift == 0 && *key == "" {
		abort("Must give -shift (ceaser) or -key (vigenere) flag")
	}

	if *shift < 0 || *shift > 25 {
		abort("-shift option must range between 1 and 25")
	}

	reader := bufio.NewReader(os.Stdin)
	input, err := reader.ReadString('\n')
	if err != nil {
		fmt.Println("Failed to read input:", err)
		return
	}

	var res string
	if *shift > 0 {
		if *decrypt {
			*shift = 26 - *shift
		}

		res = ceaserCipher(input)
	} else {
		res = vigeneteCipher(input)
	}

	fmt.Println("Response:")
	fmt.Println(res)
}

func ceaserRune(char rune, shift int) rune {
	if char < 'A' || char > 'Z' {
		return ' '
	}

	offset := (int(char) + shift - 'A') % 26
	return rune(Alphabet[offset])
}

func ceaserCipher(input string) string {
	result := ""

	for _, char := range input {
		result += string(ceaserRune(char, *shift))
	}

	return result
}

func vigeneteCipher(r string) string {
	return "Vigenete: Not Implemented"
}
<|endoftext|>


<commit_before>/*
 Copyright 2015 Stack Inc.
 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 cli

import (
	"fmt"
	"log"
	"os"
	"strings"
)

// Structure representing a project
type Project struct {
	// Project name
	Name string

	// Base path of project
	BasePath string

	// Packages
	Packages []string

	// Capabilities
	Capabilities []string

	// Assembler compiler flags
	Aflags string

	// Compiler flags
	Cflags string

	// Linker flags
	Lflags string

	// The repository the project is located in
	Repo *Repo

	// The target associated with this project
	Target *Target
}

// Load and initialize a project specified by name
// r & t are the repository and target to associate the project with
func LoadProject(r *Repo, t *Target, name string) (*Project, error) {
	p := &Project{
		Name:   name,
		Repo:   r,
		Target: t,
	}

	log.Printf("[DEBUG] Loading project %s for repo %s, target %s",
		name, r.BasePath, t.Name)

	if err := p.Init(); err != nil {
		return nil, err
	} else {
		return p, nil
	}
}

// Get the packages associated with the project
func (p *Project) GetPackages() []string {
	return p.Packages
}

// Load project configuration
func (p *Project) loadConfig() error {
	log.Printf("[DEBUG] Reading Project configuration for %s in %s",
		p.Name, p.BasePath)

	v, err := ReadConfig(p.BasePath, p.Name)
	if err != nil {
		return err
	}

	t := p.Target

	p.Packages = GetStringSliceIdentities(v, t, "project.pkgs")

	idents := GetStringSliceIdentities(v, t, "project.identities")
	t.Identities = append(t.Identities, idents...)

	p.Capabilities = GetStringSliceIdentities(v, t, "project.caps")

	p.Cflags = GetStringIdentities(v, t, "project.cflags")
	p.Lflags = GetStringIdentities(v, t, "project.lflags")
	p.Aflags = GetStringIdentities(v, t, "project.aflags")

	return nil
}

// Clean the project build, and all packages that were built with the
// project, if cleanAll is true, then clean everything, not just the current
// architecture
func (p *Project) BuildClean(cleanAll bool) error {
	pm, err := NewPkgMgr(p.Repo, p.Target)
	if err != nil {
		return err
	}

	// first, clean packages
	log.Printf("[DEBUG] Cleaning all the packages associated with project %s",
		p.Name)
	for _, pkgName := range p.GetPackages() {
		err = pm.BuildClean(p.Target, pkgName, cleanAll)
		if err != nil {
			return err
		}
	}

	// clean the BSP
	if err := pm.BuildClean(p.Target, p.Target.Bsp, cleanAll); err != nil {
		return err
	}

	c, err := NewCompiler(p.Target.GetCompiler(), p.Target.Cdef, p.Target.Name, []string{})
	if err != nil {
		return err
	}

	tName := p.Target.Name
	if cleanAll {
		tName = ""
	}

	if err := c.RecursiveClean(p.BasePath, tName); err != nil {
		return err
	}

	return nil
}

// Build the packages that this project depends on
// pm is an initialized package manager, incls is an array of includes to append to
// (package includes get append as they are built), and libs is an array of archive
// files to append to (package libraries get appended as they are built)
func (p *Project) buildDeps(pm *PkgMgr, incls *[]string, libs *[]string) error {
	pkgList := p.GetPackages()
	if pkgList == nil {
		return nil
	}

	log.Printf("[INFO] Building package dependencies for project %s", p.Name)

	t := p.Target

	// Append project variables to target variables, so that all package builds
	// inherit from them
	pkgList = append(pkgList, t.Dependencies...)
	t.Capabilities = append(t.Capabilities, p.Capabilities...)
	t.Cflags += " " + p.Cflags
	t.Lflags += " " + p.Lflags
	t.Aflags += " " + p.Aflags

	// XXX: Should set these with target capabilities and dependencies!
	deps := map[string]*DependencyRequirement{}
	reqcaps := map[string]*DependencyRequirement{}
	caps := map[string]*DependencyRequirement{}

	for _, pkgName := range pkgList {
		if pkgName == "" {
			continue
		}

		pkg, err := pm.ResolvePkgName(pkgName)
		if err != nil {
			return err
		}

		if err := pm.CheckPkgDeps(pkg, deps, reqcaps, caps); err != nil {
			return err
		}
	}

	// After processing all the dependencies, verify that the package's capability
	// requirements are satisfies as well
	if err := pm.VerifyCaps(reqcaps, caps); err != nil {
		return err
	}

	// now go through and build everything
	for _, pkgName := range pkgList {
		if pkgName == "" {
			continue
		}

		pkg, err := pm.ResolvePkgName(pkgName)
		if err != nil {
			return err
		}

		if err = pm.Build(p.Target, pkgName); err != nil {
			return err
		}

		if lib := pm.GetPackageLib(p.Target, pkg); NodeExist(lib) {
			*libs = append(*libs, lib)
		} else {
			return NewStackError(fmt.Sprintf("Library %s failed to build", lib))
		}

		*incls = append(*incls, pkg.Includes...)
	}

	return nil
}

// Build the BSP for this project.
// The BSP is specified by the Target attached to the project.
// pm is an initialized pkg mgr, containing all the packages
// incls and libs are pointers to an array of includes and libraries, when buildBsp()
// builds the BSP, it appends the include directories for the BSP, and the archive file
// to these variables.
func (p *Project) buildBsp(pm *PkgMgr, incls *[]string, libs *[]string) (string, error) {
	log.Printf("[INFO] Building BSP %s for Project %s", p.Target.Bsp, p.Name)

	if p.Target.Bsp == "" {
		return "", NewStackError("Must specify a BSP to build project")
	}

	bspPackage, err := pm.ResolvePkgName(p.Target.Bsp)
	if err != nil {
		return "", NewStackError("No BSP package for " + p.Target.Bsp + " exists")
	}

	if err = pm.Build(p.Target, p.Target.Bsp); err != nil {
		return "", err
	}

	*libs = append(*libs, pm.GetPackageLib(p.Target, bspPackage))
	*incls = append(*incls, bspPackage.Includes...)

	linkerScript := bspPackage.BasePath + "/" + bspPackage.LinkerScript

	return linkerScript, nil
}

// Build the project
func (p *Project) Build() error {
	log.Printf("[INFO] Building project %s", p.Name)

	// First build project package dependencies
	pm, err := NewPkgMgr(p.Repo, p.Target)
	if err != nil {
		return err
	}

	incls := []string{}
	libs := []string{}

	if err := p.buildDeps(pm, &incls, &libs); err != nil {
		return err
	}

	linkerScript := ""
	if p.Target.Bsp != "" {
		linkerScript, err = p.buildBsp(pm, &incls, &libs)
		if err != nil {
			return err
		}
	}

	// Append project includes
	projIncls := []string{
		p.BasePath + "/include/",
		p.BasePath + "/arch/" + p.Target.Arch + "/include/",
	}

	incls = append(incls, projIncls...)

	c, err := NewCompiler(p.Target.GetCompiler(), p.Target.Cdef, p.Target.Name,
		incls)
	if err != nil {
		return err
	}

	c.LinkerScript = linkerScript

	os.Chdir(p.BasePath + "/src/")
	if err = c.Compile("*.c"); err != nil {
		return err
	}

	if !NodeNotExist(p.BasePath + "/src/arch/" + p.Target.Arch + "/") {
		os.Chdir(p.BasePath + "/src/arch/" + p.Target.Arch + "/")
		if err = c.Compile("*.c"); err != nil {
			return err
		}
	}

	// Create binaries in the project bin/ directory, under:
	// bin/<arch>/
	binDir := p.BasePath + "/bin/" + p.Target.Name + "/"
	if NodeNotExist(binDir) {
		os.MkdirAll(binDir, 0755)
	}
	log.Printf("[DEBUG] Compiling a binary %s from libs %s", binDir+p.Name,
		strings.Join(libs, " "))

	c.CompileElf(binDir+p.Name, map[string]bool{"mapFile": c.ldMapFile, "listFile": true},
		strings.Join(libs, " "))

	return nil
}

// Initialize the project, and project definition
func (p *Project) Init() error {
	p.BasePath = p.Repo.BasePath + "/project/" + p.Name + "/"
	if NodeNotExist(p.BasePath) {
		return NewStackError("Project directory does not exist")
	}

	if err := p.loadConfig(); err != nil {
		return err
	}
	return nil
}
<commit_msg>inherit capabilities from target and project.<commit_after>/*
 Copyright 2015 Stack Inc.
 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 cli

import (
	"fmt"
	"log"
	"os"
	"strings"
)

// Structure representing a project
type Project struct {
	// Project name
	Name string

	// Base path of project
	BasePath string

	// Packages
	Packages []string

	// Capabilities
	Capabilities []string

	// Assembler compiler flags
	Aflags string

	// Compiler flags
	Cflags string

	// Linker flags
	Lflags string

	// The repository the project is located in
	Repo *Repo

	// The target associated with this project
	Target *Target
}

// Load and initialize a project specified by name
// r & t are the repository and target to associate the project with
func LoadProject(r *Repo, t *Target, name string) (*Project, error) {
	p := &Project{
		Name:   name,
		Repo:   r,
		Target: t,
	}

	log.Printf("[DEBUG] Loading project %s for repo %s, target %s",
		name, r.BasePath, t.Name)

	if err := p.Init(); err != nil {
		return nil, err
	} else {
		return p, nil
	}
}

// Get the packages associated with the project
func (p *Project) GetPackages() []string {
	return p.Packages
}

// Load project configuration
func (p *Project) loadConfig() error {
	log.Printf("[DEBUG] Reading Project configuration for %s in %s",
		p.Name, p.BasePath)

	v, err := ReadConfig(p.BasePath, p.Name)
	if err != nil {
		return err
	}

	t := p.Target

	p.Packages = GetStringSliceIdentities(v, t, "project.pkgs")

	idents := GetStringSliceIdentities(v, t, "project.identities")
	t.Identities = append(t.Identities, idents...)

	p.Capabilities = GetStringSliceIdentities(v, t, "project.caps")

	p.Cflags = GetStringIdentities(v, t, "project.cflags")
	p.Lflags = GetStringIdentities(v, t, "project.lflags")
	p.Aflags = GetStringIdentities(v, t, "project.aflags")

	return nil
}

// Clean the project build, and all packages that were built with the
// project, if cleanAll is true, then clean everything, not just the current
// architecture
func (p *Project) BuildClean(cleanAll bool) error {
	pm, err := NewPkgMgr(p.Repo, p.Target)
	if err != nil {
		return err
	}

	// first, clean packages
	log.Printf("[DEBUG] Cleaning all the packages associated with project %s",
		p.Name)
	for _, pkgName := range p.GetPackages() {
		err = pm.BuildClean(p.Target, pkgName, cleanAll)
		if err != nil {
			return err
		}
	}

	// clean the BSP
	if err := pm.BuildClean(p.Target, p.Target.Bsp, cleanAll); err != nil {
		return err
	}

	c, err := NewCompiler(p.Target.GetCompiler(), p.Target.Cdef, p.Target.Name, []string{})
	if err != nil {
		return err
	}

	tName := p.Target.Name
	if cleanAll {
		tName = ""
	}

	if err := c.RecursiveClean(p.BasePath, tName); err != nil {
		return err
	}

	return nil
}

// Build the packages that this project depends on
// pm is an initialized package manager, incls is an array of includes to append to
// (package includes get append as they are built), and libs is an array of archive
// files to append to (package libraries get appended as they are built)
func (p *Project) buildDeps(pm *PkgMgr, incls *[]string, libs *[]string) error {
	pkgList := p.GetPackages()
	if pkgList == nil {
		return nil
	}

	log.Printf("[INFO] Building package dependencies for project %s", p.Name)

	t := p.Target

	// Append project variables to target variables, so that all package builds
	// inherit from them
	pkgList = append(pkgList, t.Dependencies...)
	t.Capabilities = append(t.Capabilities, p.Capabilities...)
	t.Cflags += " " + p.Cflags
	t.Lflags += " " + p.Lflags
	t.Aflags += " " + p.Aflags

	deps := map[string]*DependencyRequirement{}
	reqcaps := map[string]*DependencyRequirement{}
	caps := map[string]*DependencyRequirement{}

	// inherit project capabilities, mark these capabilities as supported.
	for _, cName := range t.Capabilities {
		dr, err := NewDependencyRequirementParseString(cName)
		if err != nil {
			return err
		}

		caps[dr.String()] = dr
	}

	for _, pkgName := range pkgList {
		if pkgName == "" {
			continue
		}

		pkg, err := pm.ResolvePkgName(pkgName)
		if err != nil {
			return err
		}

		if err := pm.CheckPkgDeps(pkg, deps, reqcaps, caps); err != nil {
			return err
		}
	}

	// After processing all the dependencies, verify that the package's capability
	// requirements are satisfies as well
	if err := pm.VerifyCaps(reqcaps, caps); err != nil {
		return err
	}

	// now go through and build everything
	for _, pkgName := range pkgList {
		if pkgName == "" {
			continue
		}

		pkg, err := pm.ResolvePkgName(pkgName)
		if err != nil {
			return err
		}

		if err = pm.Build(p.Target, pkgName); err != nil {
			return err
		}

		if lib := pm.GetPackageLib(p.Target, pkg); NodeExist(lib) {
			*libs = append(*libs, lib)
		} else {
			return NewStackError(fmt.Sprintf("Library %s failed to build", lib))
		}

		*incls = append(*incls, pkg.Includes...)
	}

	return nil
}

// Build the BSP for this project.
// The BSP is specified by the Target attached to the project.
// pm is an initialized pkg mgr, containing all the packages
// incls and libs are pointers to an array of includes and libraries, when buildBsp()
// builds the BSP, it appends the include directories for the BSP, and the archive file
// to these variables.
func (p *Project) buildBsp(pm *PkgMgr, incls *[]string, libs *[]string) (string, error) {
	log.Printf("[INFO] Building BSP %s for Project %s", p.Target.Bsp, p.Name)

	if p.Target.Bsp == "" {
		return "", NewStackError("Must specify a BSP to build project")
	}

	bspPackage, err := pm.ResolvePkgName(p.Target.Bsp)
	if err != nil {
		return "", NewStackError("No BSP package for " + p.Target.Bsp + " exists")
	}

	if err = pm.Build(p.Target, p.Target.Bsp); err != nil {
		return "", err
	}

	*libs = append(*libs, pm.GetPackageLib(p.Target, bspPackage))
	*incls = append(*incls, bspPackage.Includes...)

	linkerScript := bspPackage.BasePath + "/" + bspPackage.LinkerScript

	return linkerScript, nil
}

// Build the project
func (p *Project) Build() error {
	log.Printf("[INFO] Building project %s", p.Name)

	// First build project package dependencies
	pm, err := NewPkgMgr(p.Repo, p.Target)
	if err != nil {
		return err
	}

	incls := []string{}
	libs := []string{}

	if err := p.buildDeps(pm, &incls, &libs); err != nil {
		return err
	}

	linkerScript := ""
	if p.Target.Bsp != "" {
		linkerScript, err = p.buildBsp(pm, &incls, &libs)
		if err != nil {
			return err
		}
	}

	// Append project includes
	projIncls := []string{
		p.BasePath + "/include/",
		p.BasePath + "/arch/" + p.Target.Arch + "/include/",
	}

	incls = append(incls, projIncls...)

	c, err := NewCompiler(p.Target.GetCompiler(), p.Target.Cdef, p.Target.Name,
		incls)
	if err != nil {
		return err
	}

	c.LinkerScript = linkerScript

	os.Chdir(p.BasePath + "/src/")
	if err = c.Compile("*.c"); err != nil {
		return err
	}

	if !NodeNotExist(p.BasePath + "/src/arch/" + p.Target.Arch + "/") {
		os.Chdir(p.BasePath + "/src/arch/" + p.Target.Arch + "/")
		if err = c.Compile("*.c"); err != nil {
			return err
		}
	}

	// Create binaries in the project bin/ directory, under:
	// bin/<arch>/
	binDir := p.BasePath + "/bin/" + p.Target.Name + "/"
	if NodeNotExist(binDir) {
		os.MkdirAll(binDir, 0755)
	}
	log.Printf("[DEBUG] Compiling a binary %s from libs %s", binDir+p.Name,
		strings.Join(libs, " "))

	c.CompileElf(binDir+p.Name, map[string]bool{"mapFile": c.ldMapFile, "listFile": true},
		strings.Join(libs, " "))

	return nil
}

// Initialize the project, and project definition
func (p *Project) Init() error {
	p.BasePath = p.Repo.BasePath + "/project/" + p.Name + "/"
	if NodeNotExist(p.BasePath) {
		return NewStackError("Project directory does not exist")
	}

	if err := p.loadConfig(); err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2015 CoreOS, Inc.
//
// 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 clientv3

import (
	"sync"

	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
	"github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc"
	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
)

type (
	PutResponse    pb.PutResponse
	GetResponse    pb.RangeResponse
	DeleteResponse pb.DeleteRangeResponse
	TxnResponse    pb.TxnResponse
)

type KV interface {
	// PUT puts a key-value pair into etcd.
	// Note that key,value can be plain bytes array and string is
	// an immutable representation of that bytes array.
	// To get a string of bytes, do string([]byte(0x10, 0x20)).
	Put(ctx context.Context, key, val string, opts ...OpOption) (*PutResponse, error)

	// Get retrieves keys.
	// By default, Get will return the value for "key", if any.
	// When passed WithRange(end), Get will return the keys in the range [key, end).
	// When passed WithFromKey(), Get returns keys greater than or equal to key.
	// When passed WithRev(rev) with rev > 0, Get retrieves keys at the given revision;
	// if the required revision is compacted, the request will fail with ErrCompacted .
	// When passed WithLimit(limit), the number of returned keys is bounded by limit.
	// When passed WithSort(), the keys will be sorted.
	Get(ctx context.Context, key string, opts ...OpOption) (*GetResponse, error)

	// Delete deletes a key, or optionallly using WithRange(end), [key, end).
	Delete(ctx context.Context, key string, opts ...OpOption) (*DeleteResponse, error)

	// Compact compacts etcd KV history before the given rev.
	Compact(ctx context.Context, rev int64) error

	// Txn creates a transaction.
	Txn(ctx context.Context) Txn
}

type kv struct {
	c *Client

	mu     sync.Mutex       // guards all fields
	conn   *grpc.ClientConn // conn in-use
	remote pb.KVClient
}

func NewKV(c *Client) KV {
	conn := c.ActiveConnection()
	remote := pb.NewKVClient(conn)

	return &kv{
		conn:   c.ActiveConnection(),
		remote: remote,

		c: c,
	}
}

func (kv *kv) Put(ctx context.Context, key, val string, opts ...OpOption) (*PutResponse, error) {
	r, err := kv.do(ctx, OpPut(key, val, opts...))
	if err != nil {
		return nil, err
	}
	return (*PutResponse)(r.GetResponsePut()), nil
}

func (kv *kv) Get(ctx context.Context, key string, opts ...OpOption) (*GetResponse, error) {
	r, err := kv.do(ctx, OpGet(key, opts...))
	if err != nil {
		return nil, err
	}
	return (*GetResponse)(r.GetResponseRange()), nil
}

func (kv *kv) Delete(ctx context.Context, key string, opts ...OpOption) (*DeleteResponse, error) {
	r, err := kv.do(ctx, OpDelete(key, opts...))
	if err != nil {
		return nil, err
	}
	return (*DeleteResponse)(r.GetResponseDeleteRange()), nil
}

func (kv *kv) Compact(ctx context.Context, rev int64) error {
	r := &pb.CompactionRequest{Revision: rev}
	_, err := kv.getRemote().Compact(ctx, r)
	if err == nil {
		return nil
	}

	if isRPCError(err) {
		return err
	}

	go kv.switchRemote(err)
	return err
}

func (kv *kv) Txn(ctx context.Context) Txn {
	return &txn{
		kv:  kv,
		ctx: ctx,
	}
}

func (kv *kv) do(ctx context.Context, op Op) (*pb.ResponseUnion, error) {
	for {
		var err error
		switch op.t {
		// TODO: handle other ops
		case tRange:
			var resp *pb.RangeResponse
			r := &pb.RangeRequest{Key: op.key, RangeEnd: op.end, Limit: op.limit, Revision: op.rev}
			if op.sort != nil {
				r.SortOrder = pb.RangeRequest_SortOrder(op.sort.Order)
				r.SortTarget = pb.RangeRequest_SortTarget(op.sort.Target)
			}

			resp, err = kv.getRemote().Range(ctx, r)
			if err == nil {
				respu := &pb.ResponseUnion_ResponseRange{ResponseRange: resp}
				return &pb.ResponseUnion{Response: respu}, nil
			}
		case tPut:
			var resp *pb.PutResponse
			r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID)}
			resp, err = kv.getRemote().Put(ctx, r)
			if err == nil {
				respu := &pb.ResponseUnion_ResponsePut{ResponsePut: resp}
				return &pb.ResponseUnion{Response: respu}, nil
			}
		case tDeleteRange:
			var resp *pb.DeleteRangeResponse
			r := &pb.DeleteRangeRequest{Key: op.key, RangeEnd: op.end}
			resp, err = kv.getRemote().DeleteRange(ctx, r)
			if err == nil {
				respu := &pb.ResponseUnion_ResponseDeleteRange{ResponseDeleteRange: resp}
				return &pb.ResponseUnion{Response: respu}, nil
			}
		default:
			panic("Unknown op")
		}

		if isRPCError(err) {
			return nil, err
		}

		// do not retry on modifications
		if op.isWrite() {
			go kv.switchRemote(err)
			return nil, err
		}

		if nerr := kv.switchRemote(err); nerr != nil {
			return nil, nerr
		}
	}
}

func (kv *kv) switchRemote(prevErr error) error {
	newConn, err := kv.c.retryConnection(kv.conn, prevErr)
	if err != nil {
		return err
	}

	kv.mu.Lock()
	defer kv.mu.Unlock()

	kv.conn = newConn
	kv.remote = pb.NewKVClient(kv.conn)
	return nil
}

func (kv *kv) getRemote() pb.KVClient {
	kv.mu.Lock()
	defer kv.mu.Unlock()
	return kv.remote
}
<commit_msg>clientv3: kv.Delete typo<commit_after>// Copyright 2015 CoreOS, Inc.
//
// 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 clientv3

import (
	"sync"

	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
	"github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc"
	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
)

type (
	PutResponse    pb.PutResponse
	GetResponse    pb.RangeResponse
	DeleteResponse pb.DeleteRangeResponse
	TxnResponse    pb.TxnResponse
)

type KV interface {
	// PUT puts a key-value pair into etcd.
	// Note that key,value can be plain bytes array and string is
	// an immutable representation of that bytes array.
	// To get a string of bytes, do string([]byte(0x10, 0x20)).
	Put(ctx context.Context, key, val string, opts ...OpOption) (*PutResponse, error)

	// Get retrieves keys.
	// By default, Get will return the value for "key", if any.
	// When passed WithRange(end), Get will return the keys in the range [key, end).
	// When passed WithFromKey(), Get returns keys greater than or equal to key.
	// When passed WithRev(rev) with rev > 0, Get retrieves keys at the given revision;
	// if the required revision is compacted, the request will fail with ErrCompacted .
	// When passed WithLimit(limit), the number of returned keys is bounded by limit.
	// When passed WithSort(), the keys will be sorted.
	Get(ctx context.Context, key string, opts ...OpOption) (*GetResponse, error)

	// Delete deletes a key, or optionally using WithRange(end), [key, end).
	Delete(ctx context.Context, key string, opts ...OpOption) (*DeleteResponse, error)

	// Compact compacts etcd KV history before the given rev.
	Compact(ctx context.Context, rev int64) error

	// Txn creates a transaction.
	Txn(ctx context.Context) Txn
}

type kv struct {
	c *Client

	mu     sync.Mutex       // guards all fields
	conn   *grpc.ClientConn // conn in-use
	remote pb.KVClient
}

func NewKV(c *Client) KV {
	conn := c.ActiveConnection()
	remote := pb.NewKVClient(conn)

	return &kv{
		conn:   c.ActiveConnection(),
		remote: remote,

		c: c,
	}
}

func (kv *kv) Put(ctx context.Context, key, val string, opts ...OpOption) (*PutResponse, error) {
	r, err := kv.do(ctx, OpPut(key, val, opts...))
	if err != nil {
		return nil, err
	}
	return (*PutResponse)(r.GetResponsePut()), nil
}

func (kv *kv) Get(ctx context.Context, key string, opts ...OpOption) (*GetResponse, error) {
	r, err := kv.do(ctx, OpGet(key, opts...))
	if err != nil {
		return nil, err
	}
	return (*GetResponse)(r.GetResponseRange()), nil
}

func (kv *kv) Delete(ctx context.Context, key string, opts ...OpOption) (*DeleteResponse, error) {
	r, err := kv.do(ctx, OpDelete(key, opts...))
	if err != nil {
		return nil, err
	}
	return (*DeleteResponse)(r.GetResponseDeleteRange()), nil
}

func (kv *kv) Compact(ctx context.Context, rev int64) error {
	r := &pb.CompactionRequest{Revision: rev}
	_, err := kv.getRemote().Compact(ctx, r)
	if err == nil {
		return nil
	}

	if isRPCError(err) {
		return err
	}

	go kv.switchRemote(err)
	return err
}

func (kv *kv) Txn(ctx context.Context) Txn {
	return &txn{
		kv:  kv,
		ctx: ctx,
	}
}

func (kv *kv) do(ctx context.Context, op Op) (*pb.ResponseUnion, error) {
	for {
		var err error
		switch op.t {
		// TODO: handle other ops
		case tRange:
			var resp *pb.RangeResponse
			r := &pb.RangeRequest{Key: op.key, RangeEnd: op.end, Limit: op.limit, Revision: op.rev}
			if op.sort != nil {
				r.SortOrder = pb.RangeRequest_SortOrder(op.sort.Order)
				r.SortTarget = pb.RangeRequest_SortTarget(op.sort.Target)
			}

			resp, err = kv.getRemote().Range(ctx, r)
			if err == nil {
				respu := &pb.ResponseUnion_ResponseRange{ResponseRange: resp}
				return &pb.ResponseUnion{Response: respu}, nil
			}
		case tPut:
			var resp *pb.PutResponse
			r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID)}
			resp, err = kv.getRemote().Put(ctx, r)
			if err == nil {
				respu := &pb.ResponseUnion_ResponsePut{ResponsePut: resp}
				return &pb.ResponseUnion{Response: respu}, nil
			}
		case tDeleteRange:
			var resp *pb.DeleteRangeResponse
			r := &pb.DeleteRangeRequest{Key: op.key, RangeEnd: op.end}
			resp, err = kv.getRemote().DeleteRange(ctx, r)
			if err == nil {
				respu := &pb.ResponseUnion_ResponseDeleteRange{ResponseDeleteRange: resp}
				return &pb.ResponseUnion{Response: respu}, nil
			}
		default:
			panic("Unknown op")
		}

		if isRPCError(err) {
			return nil, err
		}

		// do not retry on modifications
		if op.isWrite() {
			go kv.switchRemote(err)
			return nil, err
		}

		if nerr := kv.switchRemote(err); nerr != nil {
			return nil, nerr
		}
	}
}

func (kv *kv) switchRemote(prevErr error) error {
	newConn, err := kv.c.retryConnection(kv.conn, prevErr)
	if err != nil {
		return err
	}

	kv.mu.Lock()
	defer kv.mu.Unlock()

	kv.conn = newConn
	kv.remote = pb.NewKVClient(kv.conn)
	return nil
}

func (kv *kv) getRemote() pb.KVClient {
	kv.mu.Lock()
	defer kv.mu.Unlock()
	return kv.remote
}
<|endoftext|>


<commit_before>// Copyright 2015 The etcd 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 clientv3

import (
	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
)

type (
	CompactResponse pb.CompactionResponse
	PutResponse     pb.PutResponse
	GetResponse     pb.RangeResponse
	DeleteResponse  pb.DeleteRangeResponse
	TxnResponse     pb.TxnResponse
)

type KV interface {
	// Put puts a key-value pair into etcd.
	// Note that key,value can be plain bytes array and string is
	// an immutable representation of that bytes array.
	// To get a string of bytes, do string([]byte(0x10, 0x20)).
	Put(ctx context.Context, key, val string, opts ...OpOption) (*PutResponse, error)

	// Get retrieves keys.
	// By default, Get will return the value for "key", if any.
	// When passed WithRange(end), Get will return the keys in the range [key, end).
	// When passed WithFromKey(), Get returns keys greater than or equal to key.
	// When passed WithRev(rev) with rev > 0, Get retrieves keys at the given revision;
	// if the required revision is compacted, the request will fail with ErrCompacted .
	// When passed WithLimit(limit), the number of returned keys is bounded by limit.
	// When passed WithSort(), the keys will be sorted.
	Get(ctx context.Context, key string, opts ...OpOption) (*GetResponse, error)

	// Delete deletes a key, or optionally using WithRange(end), [key, end).
	Delete(ctx context.Context, key string, opts ...OpOption) (*DeleteResponse, error)

	// Compact compacts etcd KV history before the given rev.
	Compact(ctx context.Context, rev int64, opts ...CompactOption) (*CompactResponse, error)

	// Do applies a single Op on KV without a transaction.
	// Do is useful when declaring operations to be issued at a later time
	// whereas Get/Put/Delete are for better suited for when the operation
	// should be immediately issued at time of declaration.

	// Do applies a single Op on KV without a transaction.
	// Do is useful when creating arbitrary operations to be issued at a
	// later time; the user can range over the operations, calling Do to
	// execute them. Get/Put/Delete, on the other hand, are best suited
	// for when the operation should be issued at the time of declaration.
	Do(ctx context.Context, op Op) (OpResponse, error)

	// Txn creates a transaction.
	Txn(ctx context.Context) Txn
}

type OpResponse struct {
	put *PutResponse
	get *GetResponse
	del *DeleteResponse
}

func (op OpResponse) Put() *PutResponse    { return op.put }
func (op OpResponse) Get() *GetResponse    { return op.get }
func (op OpResponse) Del() *DeleteResponse { return op.del }

type kv struct {
	remote pb.KVClient
}

func NewKV(c *Client) KV {
	return &kv{remote: RetryKVClient(c)}
}

func NewKVFromKVClient(remote pb.KVClient) KV {
	return &kv{remote: remote}
}

func (kv *kv) Put(ctx context.Context, key, val string, opts ...OpOption) (*PutResponse, error) {
	r, err := kv.Do(ctx, OpPut(key, val, opts...))
	return r.put, toErr(ctx, err)
}

func (kv *kv) Get(ctx context.Context, key string, opts ...OpOption) (*GetResponse, error) {
	r, err := kv.Do(ctx, OpGet(key, opts...))
	return r.get, toErr(ctx, err)
}

func (kv *kv) Delete(ctx context.Context, key string, opts ...OpOption) (*DeleteResponse, error) {
	r, err := kv.Do(ctx, OpDelete(key, opts...))
	return r.del, toErr(ctx, err)
}

func (kv *kv) Compact(ctx context.Context, rev int64, opts ...CompactOption) (*CompactResponse, error) {
	resp, err := kv.remote.Compact(ctx, OpCompact(rev, opts...).toRequest())
	if err != nil {
		return nil, toErr(ctx, err)
	}
	return (*CompactResponse)(resp), err
}

func (kv *kv) Txn(ctx context.Context) Txn {
	return &txn{
		kv:  kv,
		ctx: ctx,
	}
}

func (kv *kv) Do(ctx context.Context, op Op) (OpResponse, error) {
	for {
		resp, err := kv.do(ctx, op)
		if err == nil {
			return resp, nil
		}

		if isHaltErr(ctx, err) {
			return resp, toErr(ctx, err)
		}
		// do not retry on modifications
		if op.isWrite() {
			return resp, toErr(ctx, err)
		}
	}
}

func (kv *kv) do(ctx context.Context, op Op) (OpResponse, error) {
	var err error
	switch op.t {
	// TODO: handle other ops
	case tRange:
		var resp *pb.RangeResponse
		resp, err = kv.remote.Range(ctx, op.toRangeRequest(), grpc.FailFast(false))
		if err == nil {
			return OpResponse{get: (*GetResponse)(resp)}, nil
		}
	case tPut:
		var resp *pb.PutResponse
		r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID), PrevKv: op.prevKV, IgnoreValue: op.ignoreValue}
		resp, err = kv.remote.Put(ctx, r)
		if err == nil {
			return OpResponse{put: (*PutResponse)(resp)}, nil
		}
	case tDeleteRange:
		var resp *pb.DeleteRangeResponse
		r := &pb.DeleteRangeRequest{Key: op.key, RangeEnd: op.end, PrevKv: op.prevKV}
		resp, err = kv.remote.DeleteRange(ctx, r)
		if err == nil {
			return OpResponse{del: (*DeleteResponse)(resp)}, nil
		}
	default:
		panic("Unknown op")
	}
	return OpResponse{}, err
}
<commit_msg>kv.go: Fixed []byte to string conversion syntax in comment<commit_after>// Copyright 2015 The etcd 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 clientv3

import (
	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
)

type (
	CompactResponse pb.CompactionResponse
	PutResponse     pb.PutResponse
	GetResponse     pb.RangeResponse
	DeleteResponse  pb.DeleteRangeResponse
	TxnResponse     pb.TxnResponse
)

type KV interface {
	// Put puts a key-value pair into etcd.
	// Note that key,value can be plain bytes array and string is
	// an immutable representation of that bytes array.
	// To get a string of bytes, do string([]byte{0x10, 0x20}).
	Put(ctx context.Context, key, val string, opts ...OpOption) (*PutResponse, error)

	// Get retrieves keys.
	// By default, Get will return the value for "key", if any.
	// When passed WithRange(end), Get will return the keys in the range [key, end).
	// When passed WithFromKey(), Get returns keys greater than or equal to key.
	// When passed WithRev(rev) with rev > 0, Get retrieves keys at the given revision;
	// if the required revision is compacted, the request will fail with ErrCompacted .
	// When passed WithLimit(limit), the number of returned keys is bounded by limit.
	// When passed WithSort(), the keys will be sorted.
	Get(ctx context.Context, key string, opts ...OpOption) (*GetResponse, error)

	// Delete deletes a key, or optionally using WithRange(end), [key, end).
	Delete(ctx context.Context, key string, opts ...OpOption) (*DeleteResponse, error)

	// Compact compacts etcd KV history before the given rev.
	Compact(ctx context.Context, rev int64, opts ...CompactOption) (*CompactResponse, error)

	// Do applies a single Op on KV without a transaction.
	// Do is useful when declaring operations to be issued at a later time
	// whereas Get/Put/Delete are for better suited for when the operation
	// should be immediately issued at time of declaration.

	// Do applies a single Op on KV without a transaction.
	// Do is useful when creating arbitrary operations to be issued at a
	// later time; the user can range over the operations, calling Do to
	// execute them. Get/Put/Delete, on the other hand, are best suited
	// for when the operation should be issued at the time of declaration.
	Do(ctx context.Context, op Op) (OpResponse, error)

	// Txn creates a transaction.
	Txn(ctx context.Context) Txn
}

type OpResponse struct {
	put *PutResponse
	get *GetResponse
	del *DeleteResponse
}

func (op OpResponse) Put() *PutResponse    { return op.put }
func (op OpResponse) Get() *GetResponse    { return op.get }
func (op OpResponse) Del() *DeleteResponse { return op.del }

type kv struct {
	remote pb.KVClient
}

func NewKV(c *Client) KV {
	return &kv{remote: RetryKVClient(c)}
}

func NewKVFromKVClient(remote pb.KVClient) KV {
	return &kv{remote: remote}
}

func (kv *kv) Put(ctx context.Context, key, val string, opts ...OpOption) (*PutResponse, error) {
	r, err := kv.Do(ctx, OpPut(key, val, opts...))
	return r.put, toErr(ctx, err)
}

func (kv *kv) Get(ctx context.Context, key string, opts ...OpOption) (*GetResponse, error) {
	r, err := kv.Do(ctx, OpGet(key, opts...))
	return r.get, toErr(ctx, err)
}

func (kv *kv) Delete(ctx context.Context, key string, opts ...OpOption) (*DeleteResponse, error) {
	r, err := kv.Do(ctx, OpDelete(key, opts...))
	return r.del, toErr(ctx, err)
}

func (kv *kv) Compact(ctx context.Context, rev int64, opts ...CompactOption) (*CompactResponse, error) {
	resp, err := kv.remote.Compact(ctx, OpCompact(rev, opts...).toRequest())
	if err != nil {
		return nil, toErr(ctx, err)
	}
	return (*CompactResponse)(resp), err
}

func (kv *kv) Txn(ctx context.Context) Txn {
	return &txn{
		kv:  kv,
		ctx: ctx,
	}
}

func (kv *kv) Do(ctx context.Context, op Op) (OpResponse, error) {
	for {
		resp, err := kv.do(ctx, op)
		if err == nil {
			return resp, nil
		}

		if isHaltErr(ctx, err) {
			return resp, toErr(ctx, err)
		}
		// do not retry on modifications
		if op.isWrite() {
			return resp, toErr(ctx, err)
		}
	}
}

func (kv *kv) do(ctx context.Context, op Op) (OpResponse, error) {
	var err error
	switch op.t {
	// TODO: handle other ops
	case tRange:
		var resp *pb.RangeResponse
		resp, err = kv.remote.Range(ctx, op.toRangeRequest(), grpc.FailFast(false))
		if err == nil {
			return OpResponse{get: (*GetResponse)(resp)}, nil
		}
	case tPut:
		var resp *pb.PutResponse
		r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID), PrevKv: op.prevKV, IgnoreValue: op.ignoreValue}
		resp, err = kv.remote.Put(ctx, r)
		if err == nil {
			return OpResponse{put: (*PutResponse)(resp)}, nil
		}
	case tDeleteRange:
		var resp *pb.DeleteRangeResponse
		r := &pb.DeleteRangeRequest{Key: op.key, RangeEnd: op.end, PrevKv: op.prevKV}
		resp, err = kv.remote.DeleteRange(ctx, r)
		if err == nil {
			return OpResponse{del: (*DeleteResponse)(resp)}, nil
		}
	default:
		panic("Unknown op")
	}
	return OpResponse{}, err
}
<|endoftext|>


<commit_before>package cloud

import (
	"bytes"
	"encoding/json"
	"fmt"
	"path/filepath"

	bosherr "github.com/cloudfoundry/bosh-agent/errors"
	boshlog "github.com/cloudfoundry/bosh-agent/logger"
	boshsys "github.com/cloudfoundry/bosh-agent/system"

	bmstemcell "github.com/cloudfoundry/bosh-micro-cli/stemcell"
	bmvm "github.com/cloudfoundry/bosh-micro-cli/vm"
)

type CmdContext struct {
	DirectorUUID string `json:"director_uuid"`
}

type CmdInput struct {
	Method    string        `json:"method"`
	Arguments []interface{} `json:"arguments"`
	Context   CmdContext    `json:"context"`
}

type CmdError struct {
	Type      string `json:"type"`
	Message   string `json:"message"`
	OkToRetry bool   `json:"ok_to_retry"`
}

func (e CmdError) String() string {
	bytes, err := json.Marshal(e)
	if err != nil {
		panic(fmt.Sprintf("Error stringifying CmdError %#v: %s", e, err.Error()))
	}
	return fmt.Sprintf("CmdError%s", string(bytes))
}

type CmdOutput struct {
	Result interface{} `json:"result"`
	Error  *CmdError   `json:"error,omitempty"`
	Log    string      `json:"log"`
}

type CPIJob struct {
	JobPath      string
	JobsPath     string
	PackagesPath string
}

func (j CPIJob) String() string {
	return fmt.Sprintf(
		"CPIJob{JobPath:'%s', JobsPath:'%s', PackagesPath:'%s'}",
		j.JobPath,
		j.JobsPath,
		j.PackagesPath,
	)
}

type Cloud interface {
	bmstemcell.Infrastructure
	bmvm.Infrastructure
}

type cloud struct {
	fs             boshsys.FileSystem
	cmdRunner      boshsys.CmdRunner
	cpiJob         CPIJob
	deploymentUUID string
	logger         boshlog.Logger
	logTag         string
}

func NewCloud(
	fs boshsys.FileSystem,
	cmdRunner boshsys.CmdRunner,
	cpiJob CPIJob,
	deploymentUUID string,
	logger boshlog.Logger,
) Cloud {
	return cloud{
		fs:             fs,
		cmdRunner:      cmdRunner,
		cpiJob:         cpiJob,
		deploymentUUID: deploymentUUID,
		logger:         logger,
		logTag:         "cloud",
	}
}

func (c cloud) String() string {
	return fmt.Sprintf(
		"Cloud{cpiJob:%s, deploymentUUID:'%s', logTag:'%s'}",
		c.cpiJob,
		c.deploymentUUID,
		c.logTag,
	)
}

func (c cloud) CreateStemcell(stemcell bmstemcell.Stemcell) (cid bmstemcell.CID, err error) {
	cloudProperties, err := stemcell.CloudProperties()
	if err != nil {
		return "", bosherr.WrapError(err, "Building stemcell CloudProperties")
	}

	cmdOutput, err := c.execCPICmd("create_stemcell", stemcell.ImagePath, cloudProperties)
	if err != nil {
		return cid, err
	}

	// for create_stemcell, the result is a string of the stemcell cid
	cidString, ok := cmdOutput.Result.(string)
	if !ok {
		return cid, bosherr.New("Unexpected external CPI command result: '%#v'", cmdOutput.Result)
	}
	return bmstemcell.CID(cidString), nil
}

func (c cloud) CreateVM(
	stemcellCID bmstemcell.CID,
	cloudProperties map[string]interface{},
	networksSpec map[string]interface{},
	env map[string]interface{},
) (bmvm.CID, error) {
	diskLocality := []interface{}{} // not used with bosh-micro-cli
	cmdOutput, err := c.execCPICmd(
		"create_vm",
		c.deploymentUUID,
		stemcellCID,
		cloudProperties,
		networksSpec,
		diskLocality,
		env,
	)
	if err != nil {
		return "", err
	}

	// for create_vm, the result is a string of the vm cid
	cidString, ok := cmdOutput.Result.(string)
	if !ok {
		return "", bosherr.New("Unexpected external CPI command result: '%#v'", cmdOutput.Result)
	}
	return bmvm.CID(cidString), nil
}

func (c cloud) cpiExecutablePath() string {
	return filepath.Join(c.cpiJob.JobPath, "bin", "cpi")
}

func (c cloud) execCPICmd(method string, args ...interface{}) (CmdOutput, error) {
	cmdInput := CmdInput{
		Method:    method,
		Arguments: args,
		Context: CmdContext{
			DirectorUUID: c.deploymentUUID,
		},
	}
	inputBytes, err := json.Marshal(cmdInput)
	if err != nil {
		return CmdOutput{}, bosherr.WrapError(err, "Marshalling external CPI command input %#v", cmdInput)
	}

	cmdPath := c.cpiExecutablePath()
	cmd := boshsys.Command{
		Name: cmdPath,
		Env: map[string]string{
			"BOSH_PACKAGES_DIR": c.cpiJob.PackagesPath,
			"BOSH_JOBS_DIR":     c.cpiJob.JobsPath,
			"PATH":              "/usr/local/bin:/usr/bin:/bin",
		},
		UseIsolatedEnv: true,
		Stdin:          bytes.NewReader(inputBytes),
	}
	stdout, stderr, exitCode, err := c.cmdRunner.RunComplexCommand(cmd)
	c.logger.Debug(c.logTag, "Exit Code %d when executing external CPI command '%s'\nSTDIN: '%s'\nSTDOUT: '%s'\nSTDERR: '%s'", exitCode, cmdPath, string(inputBytes), stdout, stderr)
	if err != nil {
		return CmdOutput{}, bosherr.WrapError(err, "Executing external CPI command: '%s'", cmdPath)
	}

	cmdOutput := CmdOutput{}
	err = json.Unmarshal([]byte(stdout), &cmdOutput)
	if err != nil {
		return CmdOutput{}, bosherr.WrapError(err, "Unmarshalling external CPI command output: '%s'", stdout)
	}

	c.logger.Debug(c.logTag, cmdOutput.Log)

	if cmdOutput.Error != nil {
		return cmdOutput, bosherr.New("External CPI command for method `%s' returned an error: %s", method, cmdOutput.Error)
	}

	return cmdOutput, err
}
<commit_msg>Log CPI stderr in addition to stdout<commit_after>package cloud

import (
	"bytes"
	"encoding/json"
	"fmt"
	"path/filepath"

	bosherr "github.com/cloudfoundry/bosh-agent/errors"
	boshlog "github.com/cloudfoundry/bosh-agent/logger"
	boshsys "github.com/cloudfoundry/bosh-agent/system"

	bmstemcell "github.com/cloudfoundry/bosh-micro-cli/stemcell"
	bmvm "github.com/cloudfoundry/bosh-micro-cli/vm"
)

type CmdContext struct {
	DirectorUUID string `json:"director_uuid"`
}

type CmdInput struct {
	Method    string        `json:"method"`
	Arguments []interface{} `json:"arguments"`
	Context   CmdContext    `json:"context"`
}

type CmdError struct {
	Type      string `json:"type"`
	Message   string `json:"message"`
	OkToRetry bool   `json:"ok_to_retry"`
}

func (e CmdError) String() string {
	bytes, err := json.Marshal(e)
	if err != nil {
		panic(fmt.Sprintf("Error stringifying CmdError %#v: %s", e, err.Error()))
	}
	return fmt.Sprintf("CmdError%s", string(bytes))
}

type CmdOutput struct {
	Result interface{} `json:"result"`
	Error  *CmdError   `json:"error,omitempty"`
	Log    string      `json:"log"`
}

type CPIJob struct {
	JobPath      string
	JobsPath     string
	PackagesPath string
}

func (j CPIJob) String() string {
	return fmt.Sprintf(
		"CPIJob{JobPath:'%s', JobsPath:'%s', PackagesPath:'%s'}",
		j.JobPath,
		j.JobsPath,
		j.PackagesPath,
	)
}

type Cloud interface {
	bmstemcell.Infrastructure
	bmvm.Infrastructure
}

type cloud struct {
	fs             boshsys.FileSystem
	cmdRunner      boshsys.CmdRunner
	cpiJob         CPIJob
	deploymentUUID string
	logger         boshlog.Logger
	logTag         string
}

func NewCloud(
	fs boshsys.FileSystem,
	cmdRunner boshsys.CmdRunner,
	cpiJob CPIJob,
	deploymentUUID string,
	logger boshlog.Logger,
) Cloud {
	return cloud{
		fs:             fs,
		cmdRunner:      cmdRunner,
		cpiJob:         cpiJob,
		deploymentUUID: deploymentUUID,
		logger:         logger,
		logTag:         "cloud",
	}
}

func (c cloud) String() string {
	return fmt.Sprintf(
		"Cloud{cpiJob:%s, deploymentUUID:'%s', logTag:'%s'}",
		c.cpiJob,
		c.deploymentUUID,
		c.logTag,
	)
}

func (c cloud) CreateStemcell(stemcell bmstemcell.Stemcell) (cid bmstemcell.CID, err error) {
	cloudProperties, err := stemcell.CloudProperties()
	if err != nil {
		return "", bosherr.WrapError(err, "Building stemcell CloudProperties")
	}

	cmdOutput, err := c.execCPICmd("create_stemcell", stemcell.ImagePath, cloudProperties)
	if err != nil {
		return cid, err
	}

	// for create_stemcell, the result is a string of the stemcell cid
	cidString, ok := cmdOutput.Result.(string)
	if !ok {
		return cid, bosherr.New("Unexpected external CPI command result: '%#v'", cmdOutput.Result)
	}
	return bmstemcell.CID(cidString), nil
}

func (c cloud) CreateVM(
	stemcellCID bmstemcell.CID,
	cloudProperties map[string]interface{},
	networksSpec map[string]interface{},
	env map[string]interface{},
) (bmvm.CID, error) {
	diskLocality := []interface{}{} // not used with bosh-micro-cli
	cmdOutput, err := c.execCPICmd(
		"create_vm",
		c.deploymentUUID,
		stemcellCID,
		cloudProperties,
		networksSpec,
		diskLocality,
		env,
	)
	if err != nil {
		return "", err
	}

	// for create_vm, the result is a string of the vm cid
	cidString, ok := cmdOutput.Result.(string)
	if !ok {
		return "", bosherr.New("Unexpected external CPI command result: '%#v'", cmdOutput.Result)
	}
	return bmvm.CID(cidString), nil
}

func (c cloud) cpiExecutablePath() string {
	return filepath.Join(c.cpiJob.JobPath, "bin", "cpi")
}

func (c cloud) execCPICmd(method string, args ...interface{}) (CmdOutput, error) {
	cmdInput := CmdInput{
		Method:    method,
		Arguments: args,
		Context: CmdContext{
			DirectorUUID: c.deploymentUUID,
		},
	}
	inputBytes, err := json.Marshal(cmdInput)
	if err != nil {
		return CmdOutput{}, bosherr.WrapError(err, "Marshalling external CPI command input %#v", cmdInput)
	}

	cmdPath := c.cpiExecutablePath()
	cmd := boshsys.Command{
		Name: cmdPath,
		Env: map[string]string{
			"BOSH_PACKAGES_DIR": c.cpiJob.PackagesPath,
			"BOSH_JOBS_DIR":     c.cpiJob.JobsPath,
			"PATH":              "/usr/local/bin:/usr/bin:/bin",
		},
		UseIsolatedEnv: true,
		Stdin:          bytes.NewReader(inputBytes),
	}
	stdout, stderr, exitCode, err := c.cmdRunner.RunComplexCommand(cmd)
	c.logger.Debug(c.logTag, "Exit Code %d when executing external CPI command '%s'\nSTDIN: '%s'\nSTDOUT: '%s'\nSTDERR: '%s'", exitCode, cmdPath, string(inputBytes), stdout, stderr)
	if err != nil {
		return CmdOutput{}, bosherr.WrapError(err, "Executing external CPI command: '%s'", cmdPath)
	}

	cmdOutput := CmdOutput{}
	err = json.Unmarshal([]byte(stdout), &cmdOutput)
	if err != nil {
		return CmdOutput{}, bosherr.WrapError(err, "Unmarshalling external CPI command output: STDOUT: '%s', STDERR: '%s'", stdout, stderr)
	}

	c.logger.Debug(c.logTag, cmdOutput.Log)

	if cmdOutput.Error != nil {
		return cmdOutput, bosherr.New("External CPI command for method `%s' returned an error: %s", method, cmdOutput.Error)
	}

	return cmdOutput, err
}
<|endoftext|>


<commit_before>/*
 * Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
 *
 * 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 cmd

import (
	"crypto/x509"
	"time"

	humanize "github.com/dustin/go-humanize"
	"github.com/fatih/color"
	"github.com/minio/minio/pkg/objcache"
)

// Global constants for Minio.
const (
	minGoVersion = ">= 1.7" // Minio requires at least Go v1.7
)

// minio configuration related constants.
const (
	globalMinioConfigVersion      = "9"
	globalMinioConfigDir          = ".minio"
	globalMinioCertsDir           = "certs"
	globalMinioCertsCADir         = "CAs"
	globalMinioCertFile           = "public.crt"
	globalMinioKeyFile            = "private.key"
	globalMinioConfigFile         = "config.json"
	globalMinioCertExpireWarnDays = time.Hour * 24 * 30 // 30 days.
	// Add new global values here.
)

var (
	globalQuiet    = false // Quiet flag set via command line.
	globalIsDistXL = false // "Is Distributed?" flag.

	// Add new global flags here.

	// Maximum cache size.
	globalMaxCacheSize = uint64(maxCacheSize)
	// Cache expiry.
	globalCacheExpiry = objcache.DefaultExpiry
	// Minio local server address (in `host:port` format)
	globalMinioAddr = ""
	// Minio default port, can be changed through command line.
	globalMinioPort = "9000"
	// Holds the host that was passed using --address
	globalMinioHost = ""
	// Peer communication struct
	globalS3Peers = s3Peers{}

	// CA root certificates, a nil value means system certs pool will be used
	globalRootCAs *x509.CertPool

	// Add new variable global values here.
)

var (
	// Limit fields size (except file) to 1Mib since Policy document
	// can reach that size according to https://aws.amazon.com/articles/1434
	maxFormFieldSize = int64(1 * humanize.MiByte)
)

var (
	// The maximum allowed difference between the request generation time and the server processing time
	globalMaxSkewTime = 15 * time.Minute

	// Keeps the connection active by waiting for following amount of time.
	// Primarily used in ListenBucketNotification.
	globalSNSConnAlive = 5 * time.Second
)

// global colors.
var (
	colorRed   = color.New(color.FgRed).SprintFunc()
	colorBold  = color.New(color.Bold).SprintFunc()
	colorBlue  = color.New(color.FgBlue).SprintfFunc()
	colorGreen = color.New(color.FgGreen).SprintfFunc()
)
<commit_msg>globals: make read only variables as constants. (#3326)<commit_after>/*
 * Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
 *
 * 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 cmd

import (
	"crypto/x509"
	"time"

	humanize "github.com/dustin/go-humanize"
	"github.com/fatih/color"
	"github.com/minio/minio/pkg/objcache"
)

// Global constants for Minio.
const (
	minGoVersion = ">= 1.7" // Minio requires at least Go v1.7
)

// minio configuration related constants.
const (
	globalMinioConfigVersion      = "9"
	globalMinioConfigDir          = ".minio"
	globalMinioCertsDir           = "certs"
	globalMinioCertsCADir         = "CAs"
	globalMinioCertFile           = "public.crt"
	globalMinioKeyFile            = "private.key"
	globalMinioConfigFile         = "config.json"
	globalMinioCertExpireWarnDays = time.Hour * 24 * 30 // 30 days.
	// Add new global values here.
)

const (
	// Limit fields size (except file) to 1Mib since Policy document
	// can reach that size according to https://aws.amazon.com/articles/1434
	maxFormFieldSize = int64(1 * humanize.MiByte)

	// The maximum allowed difference between the request generation time and the server processing time
	globalMaxSkewTime = 15 * time.Minute
)

var (
	globalQuiet    = false // Quiet flag set via command line.
	globalIsDistXL = false // "Is Distributed?" flag.

	// Add new global flags here.

	// Maximum cache size.
	globalMaxCacheSize = uint64(maxCacheSize)
	// Cache expiry.
	globalCacheExpiry = objcache.DefaultExpiry
	// Minio local server address (in `host:port` format)
	globalMinioAddr = ""
	// Minio default port, can be changed through command line.
	globalMinioPort = "9000"
	// Holds the host that was passed using --address
	globalMinioHost = ""
	// Peer communication struct
	globalS3Peers = s3Peers{}

	// CA root certificates, a nil value means system certs pool will be used
	globalRootCAs *x509.CertPool

	// Add new variable global values here.
)

var (
	// Keeps the connection active by waiting for following amount of time.
	// Primarily used in ListenBucketNotification.
	globalSNSConnAlive = 5 * time.Second
)

// global colors.
var (
	colorRed   = color.New(color.FgRed).SprintFunc()
	colorBold  = color.New(color.Bold).SprintFunc()
	colorBlue  = color.New(color.FgBlue).SprintfFunc()
	colorGreen = color.New(color.FgGreen).SprintfFunc()
)
<|endoftext|>