github.com/koding/terraform@v0.6.4-0.20170608090606-5d7e0339779d/builtin/providers/kubernetes/resource_kubernetes_service.go (about)

     1  package kubernetes
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"github.com/hashicorp/terraform/helper/schema"
     8  	"k8s.io/apimachinery/pkg/api/errors"
     9  	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    10  	pkgApi "k8s.io/apimachinery/pkg/types"
    11  	api "k8s.io/kubernetes/pkg/api/v1"
    12  	kubernetes "k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
    13  )
    14  
    15  func resourceKubernetesService() *schema.Resource {
    16  	return &schema.Resource{
    17  		Create: resourceKubernetesServiceCreate,
    18  		Read:   resourceKubernetesServiceRead,
    19  		Exists: resourceKubernetesServiceExists,
    20  		Update: resourceKubernetesServiceUpdate,
    21  		Delete: resourceKubernetesServiceDelete,
    22  		Importer: &schema.ResourceImporter{
    23  			State: schema.ImportStatePassthrough,
    24  		},
    25  
    26  		Schema: map[string]*schema.Schema{
    27  			"metadata": namespacedMetadataSchema("service", true),
    28  			"spec": {
    29  				Type:        schema.TypeList,
    30  				Description: "Spec defines the behavior of a service. http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status",
    31  				Required:    true,
    32  				MaxItems:    1,
    33  				Elem: &schema.Resource{
    34  					Schema: map[string]*schema.Schema{
    35  						"cluster_ip": {
    36  							Type:        schema.TypeString,
    37  							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",
    38  							Optional:    true,
    39  							ForceNew:    true,
    40  							Computed:    true,
    41  						},
    42  						"external_ips": {
    43  							Type:        schema.TypeSet,
    44  							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.",
    45  							Optional:    true,
    46  							Elem:        &schema.Schema{Type: schema.TypeString},
    47  							Set:         schema.HashString,
    48  						},
    49  						"external_name": {
    50  							Type:        schema.TypeString,
    51  							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`.",
    52  							Optional:    true,
    53  						},
    54  						"load_balancer_ip": {
    55  							Type:        schema.TypeString,
    56  							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.",
    57  							Optional:    true,
    58  						},
    59  						"load_balancer_source_ranges": {
    60  							Type:        schema.TypeSet,
    61  							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",
    62  							Optional:    true,
    63  							Elem:        &schema.Schema{Type: schema.TypeString},
    64  							Set:         schema.HashString,
    65  						},
    66  						"port": {
    67  							Type:        schema.TypeList,
    68  							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",
    69  							Required:    true,
    70  							MinItems:    1,
    71  							Elem: &schema.Resource{
    72  								Schema: map[string]*schema.Schema{
    73  									"name": {
    74  										Type:        schema.TypeString,
    75  										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.",
    76  										Optional:    true,
    77  									},
    78  									"node_port": {
    79  										Type:        schema.TypeInt,
    80  										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",
    81  										Computed:    true,
    82  										Optional:    true,
    83  									},
    84  									"port": {
    85  										Type:        schema.TypeInt,
    86  										Description: "The port that will be exposed by this service.",
    87  										Required:    true,
    88  									},
    89  									"protocol": {
    90  										Type:        schema.TypeString,
    91  										Description: "The IP protocol for this port. Supports `TCP` and `UDP`. Default is `TCP`.",
    92  										Optional:    true,
    93  										Default:     "TCP",
    94  									},
    95  									"target_port": {
    96  										Type:        schema.TypeInt,
    97  										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",
    98  										Required:    true,
    99  									},
   100  								},
   101  							},
   102  						},
   103  						"selector": {
   104  							Type:        schema.TypeMap,
   105  							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",
   106  							Optional:    true,
   107  						},
   108  						"session_affinity": {
   109  							Type:        schema.TypeString,
   110  							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",
   111  							Optional:    true,
   112  							Default:     "None",
   113  						},
   114  						"type": {
   115  							Type:        schema.TypeString,
   116  							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",
   117  							Optional:    true,
   118  							Default:     "ClusterIP",
   119  						},
   120  					},
   121  				},
   122  			},
   123  		},
   124  	}
   125  }
   126  
   127  func resourceKubernetesServiceCreate(d *schema.ResourceData, meta interface{}) error {
   128  	conn := meta.(*kubernetes.Clientset)
   129  
   130  	metadata := expandMetadata(d.Get("metadata").([]interface{}))
   131  	svc := api.Service{
   132  		ObjectMeta: metadata,
   133  		Spec:       expandServiceSpec(d.Get("spec").([]interface{})),
   134  	}
   135  	log.Printf("[INFO] Creating new service: %#v", svc)
   136  	out, err := conn.CoreV1().Services(metadata.Namespace).Create(&svc)
   137  	if err != nil {
   138  		return err
   139  	}
   140  	log.Printf("[INFO] Submitted new service: %#v", out)
   141  	d.SetId(buildId(out.ObjectMeta))
   142  
   143  	return resourceKubernetesServiceRead(d, meta)
   144  }
   145  
   146  func resourceKubernetesServiceRead(d *schema.ResourceData, meta interface{}) error {
   147  	conn := meta.(*kubernetes.Clientset)
   148  
   149  	namespace, name := idParts(d.Id())
   150  	log.Printf("[INFO] Reading service %s", name)
   151  	svc, err := conn.CoreV1().Services(namespace).Get(name, meta_v1.GetOptions{})
   152  	if err != nil {
   153  		log.Printf("[DEBUG] Received error: %#v", err)
   154  		return err
   155  	}
   156  	log.Printf("[INFO] Received service: %#v", svc)
   157  	err = d.Set("metadata", flattenMetadata(svc.ObjectMeta))
   158  	if err != nil {
   159  		return err
   160  	}
   161  
   162  	flattened := flattenServiceSpec(svc.Spec)
   163  	log.Printf("[DEBUG] Flattened service spec: %#v", flattened)
   164  	err = d.Set("spec", flattened)
   165  	if err != nil {
   166  		return err
   167  	}
   168  
   169  	return nil
   170  }
   171  
   172  func resourceKubernetesServiceUpdate(d *schema.ResourceData, meta interface{}) error {
   173  	conn := meta.(*kubernetes.Clientset)
   174  
   175  	namespace, name := idParts(d.Id())
   176  
   177  	ops := patchMetadata("metadata.0.", "/metadata/", d)
   178  	if d.HasChange("spec") {
   179  		diffOps := patchServiceSpec("spec.0.", "/spec/", d)
   180  		ops = append(ops, diffOps...)
   181  	}
   182  	data, err := ops.MarshalJSON()
   183  	if err != nil {
   184  		return fmt.Errorf("Failed to marshal update operations: %s", err)
   185  	}
   186  	log.Printf("[INFO] Updating service %q: %v", name, string(data))
   187  	out, err := conn.CoreV1().Services(namespace).Patch(name, pkgApi.JSONPatchType, data)
   188  	if err != nil {
   189  		return fmt.Errorf("Failed to update service: %s", err)
   190  	}
   191  	log.Printf("[INFO] Submitted updated service: %#v", out)
   192  	d.SetId(buildId(out.ObjectMeta))
   193  
   194  	return resourceKubernetesServiceRead(d, meta)
   195  }
   196  
   197  func resourceKubernetesServiceDelete(d *schema.ResourceData, meta interface{}) error {
   198  	conn := meta.(*kubernetes.Clientset)
   199  
   200  	namespace, name := idParts(d.Id())
   201  	log.Printf("[INFO] Deleting service: %#v", name)
   202  	err := conn.CoreV1().Services(namespace).Delete(name, &meta_v1.DeleteOptions{})
   203  	if err != nil {
   204  		return err
   205  	}
   206  
   207  	log.Printf("[INFO] Service %s deleted", name)
   208  
   209  	d.SetId("")
   210  	return nil
   211  }
   212  
   213  func resourceKubernetesServiceExists(d *schema.ResourceData, meta interface{}) (bool, error) {
   214  	conn := meta.(*kubernetes.Clientset)
   215  
   216  	namespace, name := idParts(d.Id())
   217  	log.Printf("[INFO] Checking service %s", name)
   218  	_, err := conn.CoreV1().Services(namespace).Get(name, meta_v1.GetOptions{})
   219  	if err != nil {
   220  		if statusErr, ok := err.(*errors.StatusError); ok && statusErr.ErrStatus.Code == 404 {
   221  			return false, nil
   222  		}
   223  		log.Printf("[DEBUG] Received error: %#v", err)
   224  	}
   225  	return true, err
   226  }