diff --git a/builtin/providers/kubernetes/provider.go b/builtin/providers/kubernetes/provider.go index 5e57a5258..5420572c4 100644 --- a/builtin/providers/kubernetes/provider.go +++ b/builtin/providers/kubernetes/provider.go @@ -87,6 +87,7 @@ func Provider() terraform.ResourceProvider { ResourcesMap: map[string]*schema.Resource{ "kubernetes_config_map": resourceKubernetesConfigMap(), + "kubernetes_limit_range": resourceKubernetesLimitRange(), "kubernetes_namespace": resourceKubernetesNamespace(), "kubernetes_persistent_volume": resourceKubernetesPersistentVolume(), "kubernetes_persistent_volume_claim": resourceKubernetesPersistentVolumeClaim(), diff --git a/builtin/providers/kubernetes/resource_kubernetes_limit_range.go b/builtin/providers/kubernetes/resource_kubernetes_limit_range.go new file mode 100644 index 000000000..e6ca5adf4 --- /dev/null +++ b/builtin/providers/kubernetes/resource_kubernetes_limit_range.go @@ -0,0 +1,188 @@ +package kubernetes + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform/helper/schema" + pkgApi "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + api "k8s.io/kubernetes/pkg/api/v1" + kubernetes "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5" +) + +func resourceKubernetesLimitRange() *schema.Resource { + return &schema.Resource{ + Create: resourceKubernetesLimitRangeCreate, + Read: resourceKubernetesLimitRangeRead, + Exists: resourceKubernetesLimitRangeExists, + Update: resourceKubernetesLimitRangeUpdate, + Delete: resourceKubernetesLimitRangeDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "metadata": namespacedMetadataSchema("limit range", true), + "spec": { + Type: schema.TypeList, + Description: "Spec defines the limits enforced. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status", + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "limit": { + Type: schema.TypeList, + Description: "Limits is the list of objects that are enforced.", + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "default": { + Type: schema.TypeMap, + Description: "Default resource requirement limit value by resource name if resource limit is omitted.", + Optional: true, + }, + "default_request": { + Type: schema.TypeMap, + Description: "The default resource requirement request value by resource name if resource request is omitted.", + Optional: true, + Computed: true, + }, + "max": { + Type: schema.TypeMap, + Description: "Max usage constraints on this kind by resource name.", + Optional: true, + }, + "max_limit_request_ratio": { + Type: schema.TypeMap, + Description: "The named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource.", + Optional: true, + }, + "min": { + Type: schema.TypeMap, + Description: "Min usage constraints on this kind by resource name.", + Optional: true, + }, + "type": { + Type: schema.TypeString, + Description: "Type of resource that this limit applies to.", + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func resourceKubernetesLimitRangeCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*kubernetes.Clientset) + + metadata := expandMetadata(d.Get("metadata").([]interface{})) + spec, err := expandLimitRangeSpec(d.Get("spec").([]interface{}), d.IsNewResource()) + if err != nil { + return err + } + limitRange := api.LimitRange{ + ObjectMeta: metadata, + Spec: spec, + } + log.Printf("[INFO] Creating new limit range: %#v", limitRange) + out, err := conn.CoreV1().LimitRanges(metadata.Namespace).Create(&limitRange) + if err != nil { + return fmt.Errorf("Failed to create limit range: %s", err) + } + log.Printf("[INFO] Submitted new limit range: %#v", out) + d.SetId(buildId(out.ObjectMeta)) + + return resourceKubernetesLimitRangeRead(d, meta) +} + +func resourceKubernetesLimitRangeRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*kubernetes.Clientset) + + namespace, name := idParts(d.Id()) + log.Printf("[INFO] Reading limit range %s", name) + limitRange, err := conn.CoreV1().LimitRanges(namespace).Get(name) + if err != nil { + log.Printf("[DEBUG] Received error: %#v", err) + return err + } + log.Printf("[INFO] Received limit range: %#v", limitRange) + + err = d.Set("metadata", flattenMetadata(limitRange.ObjectMeta)) + if err != nil { + return err + } + err = d.Set("spec", flattenLimitRangeSpec(limitRange.Spec)) + if err != nil { + return err + } + + return nil +} + +func resourceKubernetesLimitRangeUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*kubernetes.Clientset) + + namespace, name := idParts(d.Id()) + + ops := patchMetadata("metadata.0.", "/metadata/", d) + if d.HasChange("spec") { + spec, err := expandLimitRangeSpec(d.Get("spec").([]interface{}), d.IsNewResource()) + if err != nil { + return err + } + ops = append(ops, &ReplaceOperation{ + Path: "/spec", + Value: spec, + }) + } + data, err := ops.MarshalJSON() + if err != nil { + return fmt.Errorf("Failed to marshal update operations: %s", err) + } + log.Printf("[INFO] Updating limit range %q: %v", name, string(data)) + out, err := conn.CoreV1().LimitRanges(namespace).Patch(name, pkgApi.JSONPatchType, data) + if err != nil { + return fmt.Errorf("Failed to update limit range: %s", err) + } + log.Printf("[INFO] Submitted updated limit range: %#v", out) + d.SetId(buildId(out.ObjectMeta)) + + return resourceKubernetesLimitRangeRead(d, meta) +} + +func resourceKubernetesLimitRangeDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*kubernetes.Clientset) + + namespace, name := idParts(d.Id()) + log.Printf("[INFO] Deleting limit range: %#v", name) + err := conn.CoreV1().LimitRanges(namespace).Delete(name, &api.DeleteOptions{}) + if err != nil { + return err + } + + log.Printf("[INFO] Limit range %s deleted", name) + + d.SetId("") + return nil +} + +func resourceKubernetesLimitRangeExists(d *schema.ResourceData, meta interface{}) (bool, error) { + conn := meta.(*kubernetes.Clientset) + + namespace, name := idParts(d.Id()) + log.Printf("[INFO] Checking limit range %s", name) + _, err := conn.CoreV1().LimitRanges(namespace).Get(name) + 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 +} diff --git a/builtin/providers/kubernetes/resource_kubernetes_limit_range_test.go b/builtin/providers/kubernetes/resource_kubernetes_limit_range_test.go new file mode 100644 index 000000000..c8eaa705d --- /dev/null +++ b/builtin/providers/kubernetes/resource_kubernetes_limit_range_test.go @@ -0,0 +1,475 @@ +package kubernetes + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + api "k8s.io/kubernetes/pkg/api/v1" + kubernetes "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5" +) + +func TestAccKubernetesLimitRange_basic(t *testing.T) { + var conf api.LimitRange + name := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "kubernetes_limit_range.test", + Providers: testAccProviders, + CheckDestroy: testAccCheckKubernetesLimitRangeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKubernetesLimitRangeConfig_basic(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesLimitRangeExists("kubernetes_limit_range.test", &conf), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.annotations.%", "1"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.annotations.TestAnnotationOne", "one"), + testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{"TestAnnotationOne": "one"}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.labels.%", "3"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.labels.TestLabelOne", "one"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.labels.TestLabelThree", "three"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.labels.TestLabelFour", "four"), + testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{"TestLabelOne": "one", "TestLabelThree": "three", "TestLabelFour": "four"}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.name", name), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.#", "1"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default.%", "2"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default.cpu", "200m"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default.memory", "512M"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default_request.%", "2"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default_request.cpu", "100m"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default_request.memory", "256M"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.type", "Container"), + ), + }, + { + Config: testAccKubernetesLimitRangeConfig_metaModified(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesLimitRangeExists("kubernetes_limit_range.test", &conf), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.annotations.%", "2"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.annotations.TestAnnotationOne", "one"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.annotations.TestAnnotationTwo", "two"), + testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{"TestAnnotationOne": "one", "TestAnnotationTwo": "two"}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.labels.%", "3"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.labels.TestLabelOne", "one"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.labels.TestLabelTwo", "two"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.labels.TestLabelThree", "three"), + testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{"TestLabelOne": "one", "TestLabelTwo": "two", "TestLabelThree": "three"}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.name", name), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.#", "1"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default.%", "2"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default.cpu", "200m"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default.memory", "512M"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default_request.%", "2"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default_request.cpu", "100m"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default_request.memory", "256M"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.type", "Container"), + ), + }, + { + Config: testAccKubernetesLimitRangeConfig_specModified(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesLimitRangeExists("kubernetes_limit_range.test", &conf), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.annotations.%", "0"), + testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.labels.%", "0"), + testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.name", name), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.#", "1"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default.%", "2"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default.cpu", "200m"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default.memory", "1024M"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default_request.%", "2"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default_request.cpu", "100m"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default_request.memory", "256M"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.max.%", "1"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.max.cpu", "500m"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.min.%", "2"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.min.cpu", "10m"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.min.memory", "10M"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.type", "Container"), + ), + }, + }, + }) +} + +func TestAccKubernetesLimitRange_generatedName(t *testing.T) { + var conf api.LimitRange + prefix := "tf-acc-test-" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "kubernetes_limit_range.test", + Providers: testAccProviders, + CheckDestroy: testAccCheckKubernetesLimitRangeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKubernetesLimitRangeConfig_generatedName(prefix), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesLimitRangeExists("kubernetes_limit_range.test", &conf), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.annotations.%", "0"), + testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.labels.%", "0"), + testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.generate_name", prefix), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.#", "1"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.type", "Pod"), + ), + }, + }, + }) +} + +func TestAccKubernetesLimitRange_typeChange(t *testing.T) { + var conf api.LimitRange + name := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "kubernetes_limit_range.test", + Providers: testAccProviders, + CheckDestroy: testAccCheckKubernetesLimitRangeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKubernetesLimitRangeConfig_typeChange(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesLimitRangeExists("kubernetes_limit_range.test", &conf), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.annotations.%", "0"), + testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.labels.%", "0"), + testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.name", name), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.#", "1"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default.%", "2"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default.cpu", "200m"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default.memory", "1024M"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.type", "Container"), + ), + }, + { + Config: testAccKubernetesLimitRangeConfig_typeChangeModified(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesLimitRangeExists("kubernetes_limit_range.test", &conf), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.annotations.%", "0"), + testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.labels.%", "0"), + testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.name", name), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.#", "1"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.min.%", "2"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.min.cpu", "200m"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.min.memory", "1024M"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.type", "Pod"), + ), + }, + }, + }) +} + +func TestAccKubernetesLimitRange_multipleLimits(t *testing.T) { + var conf api.LimitRange + name := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "kubernetes_limit_range.test", + Providers: testAccProviders, + CheckDestroy: testAccCheckKubernetesLimitRangeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKubernetesLimitRangeConfig_multipleLimits(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesLimitRangeExists("kubernetes_limit_range.test", &conf), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.annotations.%", "0"), + testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.labels.%", "0"), + testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.name", name), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.#", "3"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.max.%", "2"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.max.cpu", "200m"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.max.memory", "1024M"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.type", "Pod"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.1.min.%", "1"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.1.min.storage", "24M"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.1.type", "PersistentVolumeClaim"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.2.default.%", "2"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.2.default.cpu", "50m"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.2.default.memory", "24M"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.2.type", "Container"), + ), + }, + }, + }) +} + +func TestAccKubernetesLimitRange_importBasic(t *testing.T) { + resourceName := "kubernetes_limit_range.test" + name := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckKubernetesLimitRangeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKubernetesLimitRangeConfig_basic(name), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckKubernetesLimitRangeDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*kubernetes.Clientset) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "kubernetes_limit_range" { + continue + } + namespace, name := idParts(rs.Primary.ID) + resp, err := conn.CoreV1().LimitRanges(namespace).Get(name) + if err == nil { + if resp.Namespace == namespace && resp.Name == name { + return fmt.Errorf("Limit Range still exists: %s", rs.Primary.ID) + } + } + } + + return nil +} + +func testAccCheckKubernetesLimitRangeExists(n string, obj *api.LimitRange) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + conn := testAccProvider.Meta().(*kubernetes.Clientset) + namespace, name := idParts(rs.Primary.ID) + out, err := conn.CoreV1().LimitRanges(namespace).Get(name) + if err != nil { + return err + } + + *obj = *out + return nil + } +} + +func testAccKubernetesLimitRangeConfig_basic(name string) string { + return fmt.Sprintf(` +resource "kubernetes_limit_range" "test" { + metadata { + annotations { + TestAnnotationOne = "one" + } + labels { + TestLabelOne = "one" + TestLabelThree = "three" + TestLabelFour = "four" + } + name = "%s" + } + spec { + limit { + type = "Container" + + default { + cpu = "200m" + memory = "512M" + } + + default_request { + cpu = "100m" + memory = "256M" + } + } + } +} +`, name) +} + +func testAccKubernetesLimitRangeConfig_metaModified(name string) string { + return fmt.Sprintf(` +resource "kubernetes_limit_range" "test" { + metadata { + annotations { + TestAnnotationOne = "one" + TestAnnotationTwo = "two" + } + labels { + TestLabelOne = "one" + TestLabelTwo = "two" + TestLabelThree = "three" + } + name = "%s" + } + spec { + limit { + type = "Container" + + default { + cpu = "200m" + memory = "512M" + } + + default_request { + cpu = "100m" + memory = "256M" + } + } + } +} +`, name) +} + +func testAccKubernetesLimitRangeConfig_specModified(name string) string { + return fmt.Sprintf(` +resource "kubernetes_limit_range" "test" { + metadata { + name = "%s" + } + spec { + limit { + type = "Container" + + default { + cpu = "200m" + memory = "1024M" + } + + max { + cpu = "500m" + } + + min { + cpu = "10m" + memory = "10M" + } + } + } +} +`, name) +} + +func testAccKubernetesLimitRangeConfig_generatedName(prefix string) string { + return fmt.Sprintf(` +resource "kubernetes_limit_range" "test" { + metadata { + generate_name = "%s" + } + spec { + limit { + type = "Pod" + } + } +} +`, prefix) +} + +func testAccKubernetesLimitRangeConfig_typeChange(name string) string { + return fmt.Sprintf(` +resource "kubernetes_limit_range" "test" { + metadata { + name = "%s" + } + spec { + limit { + type = "Container" + default { + cpu = "200m" + memory = "1024M" + } + } + } +} +`, name) +} + +func testAccKubernetesLimitRangeConfig_typeChangeModified(name string) string { + return fmt.Sprintf(` +resource "kubernetes_limit_range" "test" { + metadata { + name = "%s" + } + spec { + limit { + type = "Pod" + min { + cpu = "200m" + memory = "1024M" + } + } + } +} +`, name) +} + +func testAccKubernetesLimitRangeConfig_multipleLimits(name string) string { + return fmt.Sprintf(` +resource "kubernetes_limit_range" "test" { + metadata { + name = "%s" + } + spec { + limit { + type = "Pod" + max { + cpu = "200m" + memory = "1024M" + } + } + limit { + type = "PersistentVolumeClaim" + min { + storage = "24M" + } + } + limit { + type = "Container" + default { + cpu = "50m" + memory = "24M" + } + } + } +} +`, name) +} diff --git a/builtin/providers/kubernetes/structures.go b/builtin/providers/kubernetes/structures.go index 992f22b63..d02a9ea5d 100644 --- a/builtin/providers/kubernetes/structures.go +++ b/builtin/providers/kubernetes/structures.go @@ -279,3 +279,97 @@ func resourceListEquals(x, y api.ResourceList) bool { } return true } + +func expandLimitRangeSpec(s []interface{}, isNew bool) (api.LimitRangeSpec, error) { + out := api.LimitRangeSpec{} + if len(s) < 1 || s[0] == nil { + return out, nil + } + m := s[0].(map[string]interface{}) + + if limits, ok := m["limit"].([]interface{}); ok { + newLimits := make([]api.LimitRangeItem, len(limits), len(limits)) + + for i, l := range limits { + lrItem := api.LimitRangeItem{} + limit := l.(map[string]interface{}) + + if v, ok := limit["type"]; ok { + lrItem.Type = api.LimitType(v.(string)) + } + + // defaultRequest is forbidden for Pod limits, even though it's set & returned by API + // this is how we avoid sending it back + if v, ok := limit["default_request"]; ok { + drm := v.(map[string]interface{}) + if lrItem.Type == api.LimitTypePod && len(drm) > 0 { + if isNew { + return out, fmt.Errorf("limit.%d.default_request cannot be set for Pod limit", i) + } + } else { + el, err := expandMapToResourceList(drm) + if err != nil { + return out, err + } + lrItem.DefaultRequest = el + } + } + + if v, ok := limit["default"]; ok { + el, err := expandMapToResourceList(v.(map[string]interface{})) + if err != nil { + return out, err + } + lrItem.Default = el + } + if v, ok := limit["max"]; ok { + el, err := expandMapToResourceList(v.(map[string]interface{})) + if err != nil { + return out, err + } + lrItem.Max = el + } + if v, ok := limit["max_limit_request_ratio"]; ok { + el, err := expandMapToResourceList(v.(map[string]interface{})) + if err != nil { + return out, err + } + lrItem.MaxLimitRequestRatio = el + } + if v, ok := limit["min"]; ok { + el, err := expandMapToResourceList(v.(map[string]interface{})) + if err != nil { + return out, err + } + lrItem.Min = el + } + + newLimits[i] = lrItem + } + + out.Limits = newLimits + } + + return out, nil +} + +func flattenLimitRangeSpec(in api.LimitRangeSpec) []interface{} { + out := make([]interface{}, 1) + limits := make([]interface{}, len(in.Limits), len(in.Limits)) + + for i, l := range in.Limits { + m := make(map[string]interface{}, 0) + m["default"] = flattenResourceList(l.Default) + m["default_request"] = flattenResourceList(l.DefaultRequest) + m["max"] = flattenResourceList(l.Max) + m["max_limit_request_ratio"] = flattenResourceList(l.MaxLimitRequestRatio) + m["min"] = flattenResourceList(l.Min) + m["type"] = string(l.Type) + + limits[i] = m + } + out[0] = map[string]interface{}{ + "limit": limits, + } + return out +} diff --git a/website/source/docs/providers/kubernetes/r/limit_range.html.markdown b/website/source/docs/providers/kubernetes/r/limit_range.html.markdown new file mode 100644 index 000000000..732e8cef0 --- /dev/null +++ b/website/source/docs/providers/kubernetes/r/limit_range.html.markdown @@ -0,0 +1,97 @@ +--- +layout: "kubernetes" +page_title: "Kubernetes: kubernetes_limit_range" +sidebar_current: "docs-kubernetes-resource-limit-range" +description: |- + Limit Range sets resource usage limits (e.g. memory, cpu, storage) for supported kinds of resources in a namespace. +--- + +# kubernetes_limit_range + +Limit Range sets resource usage limits (e.g. memory, cpu, storage) for supported kinds of resources in a namespace. + +Read more in [the official docs](https://kubernetes.io/docs/tasks/configure-pod-container/apply-resource-quota-limit/#applying-default-resource-requests-and-limits). + + +## Example Usage + +```hcl +resource "kubernetes_limit_range" "example" { + metadata { + name = "terraform-example" + } + spec { + limit { + type = "Pod" + max { + cpu = "200m" + memory = "1024M" + } + } + limit { + type = "PersistentVolumeClaim" + min { + storage = "24M" + } + } + limit { + type = "Container" + default { + cpu = "50m" + memory = "24M" + } + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `metadata` - (Required) Standard limit range's metadata. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#metadata +* `spec` - (Optional) Spec defines the limits enforced. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status + +## Nested Blocks + +### `spec` + +#### Arguments + +* `limit` - (Optional) The list of limits that are enforced. + +### `limit` + +#### Arguments + +* `default` - (Optional) Default resource requirement limit value by resource name if resource limit is omitted. +* `default_request` - (Optional) The default resource requirement request value by resource name if resource request is omitted. +* `max` - (Optional) Max usage constraints on this kind by resource name. +* `max_limit_request_ratio` - (Optional) The named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource. +* `min` - (Optional) Min usage constraints on this kind by resource name. +* `type` - (Optional) Type of resource that this limit applies to. e.g. `Pod`, `Container` or `PersistentVolumeClaim` + +### `metadata` + +#### Arguments + +* `annotations` - (Optional) An unstructured key value map stored with the limit range that may be used to store arbitrary metadata. More info: http://kubernetes.io/docs/user-guide/annotations +* `generate_name` - (Optional) Prefix, used by the server, to generate a unique name ONLY IF the `name` field has not been provided. This value will also be combined with a unique suffix. Read more: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#idempotency +* `labels` - (Optional) Map of string keys and values that can be used to organize and categorize (scope and select) the limit range. May match selectors of replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels +* `name` - (Optional) Name of the limit range, must be unique. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names +* `namespace` - (Optional) Namespace defines the space within which name of the limit range must be unique. + +#### Attributes + +* `generation` - A sequence number representing a specific generation of the desired state. +* `resource_version` - An opaque value that represents the internal version of this limit range that can be used by clients to determine when limit range has changed. Read more: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#concurrency-control-and-consistency +* `self_link` - A URL representing this limit range. +* `uid` - The unique in time and space value for this limit range. More info: http://kubernetes.io/docs/user-guide/identifiers#uids + +## Import + +Limit Range can be imported using its name, e.g. + +``` +$ terraform import kubernetes_limit_range.example terraform-example +``` diff --git a/website/source/layouts/kubernetes.erb b/website/source/layouts/kubernetes.erb index 96a8fbaee..d7912b7ab 100644 --- a/website/source/layouts/kubernetes.erb +++ b/website/source/layouts/kubernetes.erb @@ -16,6 +16,9 @@