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 }