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