provider/kubernetes: Add support for resource_quota (#13914)
This commit is contained in:
parent
7c4e2c198a
commit
296a3b2381
|
@ -90,6 +90,7 @@ func Provider() terraform.ResourceProvider {
|
||||||
"kubernetes_namespace": resourceKubernetesNamespace(),
|
"kubernetes_namespace": resourceKubernetesNamespace(),
|
||||||
"kubernetes_persistent_volume": resourceKubernetesPersistentVolume(),
|
"kubernetes_persistent_volume": resourceKubernetesPersistentVolume(),
|
||||||
"kubernetes_persistent_volume_claim": resourceKubernetesPersistentVolumeClaim(),
|
"kubernetes_persistent_volume_claim": resourceKubernetesPersistentVolumeClaim(),
|
||||||
|
"kubernetes_resource_quota": resourceKubernetesResourceQuota(),
|
||||||
"kubernetes_secret": resourceKubernetesSecret(),
|
"kubernetes_secret": resourceKubernetesSecret(),
|
||||||
},
|
},
|
||||||
ConfigureFunc: providerConfigure,
|
ConfigureFunc: providerConfigure,
|
||||||
|
|
|
@ -0,0 +1,211 @@
|
||||||
|
package kubernetes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"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 resourceKubernetesResourceQuota() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceKubernetesResourceQuotaCreate,
|
||||||
|
Read: resourceKubernetesResourceQuotaRead,
|
||||||
|
Exists: resourceKubernetesResourceQuotaExists,
|
||||||
|
Update: resourceKubernetesResourceQuotaUpdate,
|
||||||
|
Delete: resourceKubernetesResourceQuotaDelete,
|
||||||
|
Importer: &schema.ResourceImporter{
|
||||||
|
State: schema.ImportStatePassthrough,
|
||||||
|
},
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"metadata": namespacedMetadataSchema("resource quota", true),
|
||||||
|
"spec": {
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Description: "Spec defines the desired quota. 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{
|
||||||
|
"hard": {
|
||||||
|
Type: schema.TypeMap,
|
||||||
|
Description: "The set of desired hard limits for each named resource. More info: http://releases.k8s.io/HEAD/docs/design/admission_control_resource_quota.md#admissioncontrol-plugin-resourcequota",
|
||||||
|
Optional: true,
|
||||||
|
Elem: schema.TypeString,
|
||||||
|
ValidateFunc: validateResourceList,
|
||||||
|
},
|
||||||
|
"scopes": {
|
||||||
|
Type: schema.TypeSet,
|
||||||
|
Description: "A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects.",
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
|
Set: schema.HashString,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceKubernetesResourceQuotaCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
conn := meta.(*kubernetes.Clientset)
|
||||||
|
|
||||||
|
metadata := expandMetadata(d.Get("metadata").([]interface{}))
|
||||||
|
spec, err := expandResourceQuotaSpec(d.Get("spec").([]interface{}))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resQuota := api.ResourceQuota{
|
||||||
|
ObjectMeta: metadata,
|
||||||
|
Spec: spec,
|
||||||
|
}
|
||||||
|
log.Printf("[INFO] Creating new resource quota: %#v", resQuota)
|
||||||
|
out, err := conn.CoreV1().ResourceQuotas(metadata.Namespace).Create(&resQuota)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to create resource quota: %s", err)
|
||||||
|
}
|
||||||
|
log.Printf("[INFO] Submitted new resource quota: %#v", out)
|
||||||
|
d.SetId(buildId(out.ObjectMeta))
|
||||||
|
|
||||||
|
err = resource.Retry(1*time.Minute, func() *resource.RetryError {
|
||||||
|
quota, err := conn.CoreV1().ResourceQuotas(out.Namespace).Get(out.Name)
|
||||||
|
if err != nil {
|
||||||
|
return resource.NonRetryableError(err)
|
||||||
|
}
|
||||||
|
if resourceListEquals(spec.Hard, quota.Status.Hard) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("Quotas don't match after creation.\nExpected: %#v\nGiven: %#v",
|
||||||
|
spec.Hard, quota.Status.Hard)
|
||||||
|
return resource.RetryableError(err)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceKubernetesResourceQuotaRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceKubernetesResourceQuotaRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
conn := meta.(*kubernetes.Clientset)
|
||||||
|
|
||||||
|
namespace, name := idParts(d.Id())
|
||||||
|
log.Printf("[INFO] Reading resource quota %s", name)
|
||||||
|
resQuota, err := conn.CoreV1().ResourceQuotas(namespace).Get(name)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[DEBUG] Received error: %#v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Printf("[INFO] Received resource quota: %#v", resQuota)
|
||||||
|
|
||||||
|
// This is to work around K8S bug
|
||||||
|
// See https://github.com/kubernetes/kubernetes/issues/44539
|
||||||
|
if resQuota.ObjectMeta.GenerateName == "" {
|
||||||
|
if v, ok := d.GetOk("metadata.0.generate_name"); ok {
|
||||||
|
resQuota.ObjectMeta.GenerateName = v.(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.Set("metadata", flattenMetadata(resQuota.ObjectMeta))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = d.Set("spec", flattenResourceQuotaSpec(resQuota.Spec))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceKubernetesResourceQuotaUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
conn := meta.(*kubernetes.Clientset)
|
||||||
|
|
||||||
|
namespace, name := idParts(d.Id())
|
||||||
|
|
||||||
|
ops := patchMetadata("metadata.0.", "/metadata/", d)
|
||||||
|
var spec api.ResourceQuotaSpec
|
||||||
|
waitForChangedSpec := false
|
||||||
|
if d.HasChange("spec") {
|
||||||
|
var err error
|
||||||
|
spec, err = expandResourceQuotaSpec(d.Get("spec").([]interface{}))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ops = append(ops, &ReplaceOperation{
|
||||||
|
Path: "/spec",
|
||||||
|
Value: spec,
|
||||||
|
})
|
||||||
|
waitForChangedSpec = true
|
||||||
|
}
|
||||||
|
data, err := ops.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to marshal update operations: %s", err)
|
||||||
|
}
|
||||||
|
log.Printf("[INFO] Updating resource quota %q: %v", name, string(data))
|
||||||
|
out, err := conn.CoreV1().ResourceQuotas(namespace).Patch(name, pkgApi.JSONPatchType, data)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to update resource quota: %s", err)
|
||||||
|
}
|
||||||
|
log.Printf("[INFO] Submitted updated resource quota: %#v", out)
|
||||||
|
d.SetId(buildId(out.ObjectMeta))
|
||||||
|
|
||||||
|
if waitForChangedSpec {
|
||||||
|
err = resource.Retry(1*time.Minute, func() *resource.RetryError {
|
||||||
|
quota, err := conn.CoreV1().ResourceQuotas(namespace).Get(name)
|
||||||
|
if err != nil {
|
||||||
|
return resource.NonRetryableError(err)
|
||||||
|
}
|
||||||
|
if resourceListEquals(spec.Hard, quota.Status.Hard) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("Quotas don't match after update.\nExpected: %#v\nGiven: %#v",
|
||||||
|
spec.Hard, quota.Status.Hard)
|
||||||
|
return resource.RetryableError(err)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceKubernetesResourceQuotaRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceKubernetesResourceQuotaDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
conn := meta.(*kubernetes.Clientset)
|
||||||
|
|
||||||
|
namespace, name := idParts(d.Id())
|
||||||
|
log.Printf("[INFO] Deleting resource quota: %#v", name)
|
||||||
|
err := conn.CoreV1().ResourceQuotas(namespace).Delete(name, &api.DeleteOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[INFO] Resource quota %s deleted", name)
|
||||||
|
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceKubernetesResourceQuotaExists(d *schema.ResourceData, meta interface{}) (bool, error) {
|
||||||
|
conn := meta.(*kubernetes.Clientset)
|
||||||
|
|
||||||
|
namespace, name := idParts(d.Id())
|
||||||
|
log.Printf("[INFO] Checking resource quota %s", name)
|
||||||
|
_, err := conn.CoreV1().ResourceQuotas(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
|
||||||
|
}
|
|
@ -0,0 +1,352 @@
|
||||||
|
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 TestAccKubernetesResourceQuota_basic(t *testing.T) {
|
||||||
|
var conf api.ResourceQuota
|
||||||
|
name := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(10))
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
IDRefreshName: "kubernetes_resource_quota.test",
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckKubernetesResourceQuotaDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: testAccKubernetesResourceQuotaConfig_basic(name),
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
testAccCheckKubernetesResourceQuotaExists("kubernetes_resource_quota.test", &conf),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.annotations.%", "1"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.annotations.TestAnnotationOne", "one"),
|
||||||
|
testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{"TestAnnotationOne": "one"}),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.labels.%", "3"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.labels.TestLabelOne", "one"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.labels.TestLabelThree", "three"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.labels.TestLabelFour", "four"),
|
||||||
|
testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{"TestLabelOne": "one", "TestLabelThree": "three", "TestLabelFour": "four"}),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.name", name),
|
||||||
|
resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.generation"),
|
||||||
|
resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.resource_version"),
|
||||||
|
resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.self_link"),
|
||||||
|
resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.uid"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.%", "3"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.limits.cpu", "2"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.limits.memory", "2Gi"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.pods", "4"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: testAccKubernetesResourceQuotaConfig_metaModified(name),
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
testAccCheckKubernetesResourceQuotaExists("kubernetes_resource_quota.test", &conf),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.annotations.%", "2"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.annotations.TestAnnotationOne", "one"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.annotations.TestAnnotationTwo", "two"),
|
||||||
|
testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{"TestAnnotationOne": "one", "TestAnnotationTwo": "two"}),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.labels.%", "3"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.labels.TestLabelOne", "one"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.labels.TestLabelTwo", "two"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.labels.TestLabelThree", "three"),
|
||||||
|
testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{"TestLabelOne": "one", "TestLabelTwo": "two", "TestLabelThree": "three"}),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.name", name),
|
||||||
|
resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.generation"),
|
||||||
|
resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.resource_version"),
|
||||||
|
resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.self_link"),
|
||||||
|
resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.uid"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.%", "3"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.limits.cpu", "2"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.limits.memory", "2Gi"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.pods", "4"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: testAccKubernetesResourceQuotaConfig_specModified(name),
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
testAccCheckKubernetesResourceQuotaExists("kubernetes_resource_quota.test", &conf),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.annotations.%", "0"),
|
||||||
|
testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{}),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.labels.%", "0"),
|
||||||
|
testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{}),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.name", name),
|
||||||
|
resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.generation"),
|
||||||
|
resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.resource_version"),
|
||||||
|
resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.self_link"),
|
||||||
|
resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.uid"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.%", "4"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.limits.cpu", "4"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.requests.cpu", "1"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.limits.memory", "4Gi"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.pods", "10"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccKubernetesResourceQuota_generatedName(t *testing.T) {
|
||||||
|
var conf api.ResourceQuota
|
||||||
|
prefix := "tf-acc-test-"
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
IDRefreshName: "kubernetes_resource_quota.test",
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckKubernetesResourceQuotaDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: testAccKubernetesResourceQuotaConfig_generatedName(prefix),
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
testAccCheckKubernetesResourceQuotaExists("kubernetes_resource_quota.test", &conf),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.annotations.%", "0"),
|
||||||
|
testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{}),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.labels.%", "0"),
|
||||||
|
testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{}),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.generate_name", prefix),
|
||||||
|
resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.generation"),
|
||||||
|
resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.resource_version"),
|
||||||
|
resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.self_link"),
|
||||||
|
resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.uid"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.%", "1"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.pods", "10"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.scopes.#", "0"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccKubernetesResourceQuota_withScopes(t *testing.T) {
|
||||||
|
var conf api.ResourceQuota
|
||||||
|
name := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(10))
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
IDRefreshName: "kubernetes_resource_quota.test",
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckKubernetesResourceQuotaDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: testAccKubernetesResourceQuotaConfig_withScopes(name),
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
testAccCheckKubernetesResourceQuotaExists("kubernetes_resource_quota.test", &conf),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.annotations.%", "0"),
|
||||||
|
testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{}),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.labels.%", "0"),
|
||||||
|
testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{}),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.name", name),
|
||||||
|
resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.generation"),
|
||||||
|
resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.resource_version"),
|
||||||
|
resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.self_link"),
|
||||||
|
resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.uid"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.%", "1"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.pods", "10"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.scopes.#", "1"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.scopes.193563370", "BestEffort"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: testAccKubernetesResourceQuotaConfig_withScopesModified(name),
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
testAccCheckKubernetesResourceQuotaExists("kubernetes_resource_quota.test", &conf),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.annotations.%", "0"),
|
||||||
|
testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{}),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.labels.%", "0"),
|
||||||
|
testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{}),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.name", name),
|
||||||
|
resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.generation"),
|
||||||
|
resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.resource_version"),
|
||||||
|
resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.self_link"),
|
||||||
|
resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.uid"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.%", "1"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.pods", "10"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.scopes.#", "1"),
|
||||||
|
resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.scopes.3022121741", "NotBestEffort"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccKubernetesResourceQuota_importBasic(t *testing.T) {
|
||||||
|
resourceName := "kubernetes_resource_quota.test"
|
||||||
|
name := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(10))
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckKubernetesResourceQuotaDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: testAccKubernetesResourceQuotaConfig_basic(name),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ResourceName: resourceName,
|
||||||
|
ImportState: true,
|
||||||
|
ImportStateVerify: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckKubernetesResourceQuotaDestroy(s *terraform.State) error {
|
||||||
|
conn := testAccProvider.Meta().(*kubernetes.Clientset)
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "kubernetes_resource_quota" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
namespace, name := idParts(rs.Primary.ID)
|
||||||
|
resp, err := conn.CoreV1().ResourceQuotas(namespace).Get(name)
|
||||||
|
if err == nil {
|
||||||
|
if resp.Namespace == namespace && resp.Name == name {
|
||||||
|
return fmt.Errorf("Resource Quota still exists: %s", rs.Primary.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckKubernetesResourceQuotaExists(n string, obj *api.ResourceQuota) 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().ResourceQuotas(namespace).Get(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*obj = *out
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccKubernetesResourceQuotaConfig_basic(name string) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
resource "kubernetes_resource_quota" "test" {
|
||||||
|
metadata {
|
||||||
|
annotations {
|
||||||
|
TestAnnotationOne = "one"
|
||||||
|
}
|
||||||
|
labels {
|
||||||
|
TestLabelOne = "one"
|
||||||
|
TestLabelThree = "three"
|
||||||
|
TestLabelFour = "four"
|
||||||
|
}
|
||||||
|
name = "%s"
|
||||||
|
}
|
||||||
|
spec {
|
||||||
|
hard {
|
||||||
|
"limits.cpu" = 2
|
||||||
|
"limits.memory" = "2Gi"
|
||||||
|
pods = 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccKubernetesResourceQuotaConfig_metaModified(name string) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
resource "kubernetes_resource_quota" "test" {
|
||||||
|
metadata {
|
||||||
|
annotations {
|
||||||
|
TestAnnotationOne = "one"
|
||||||
|
TestAnnotationTwo = "two"
|
||||||
|
}
|
||||||
|
labels {
|
||||||
|
TestLabelOne = "one"
|
||||||
|
TestLabelTwo = "two"
|
||||||
|
TestLabelThree = "three"
|
||||||
|
}
|
||||||
|
name = "%s"
|
||||||
|
}
|
||||||
|
spec {
|
||||||
|
hard {
|
||||||
|
"limits.cpu" = 2
|
||||||
|
"limits.memory" = "2Gi"
|
||||||
|
pods = 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccKubernetesResourceQuotaConfig_specModified(name string) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
resource "kubernetes_resource_quota" "test" {
|
||||||
|
metadata {
|
||||||
|
name = "%s"
|
||||||
|
}
|
||||||
|
spec {
|
||||||
|
hard {
|
||||||
|
"limits.cpu" = 4
|
||||||
|
"requests.cpu" = 1
|
||||||
|
"limits.memory" = "4Gi"
|
||||||
|
pods = 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccKubernetesResourceQuotaConfig_generatedName(prefix string) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
resource "kubernetes_resource_quota" "test" {
|
||||||
|
metadata {
|
||||||
|
generate_name = "%s"
|
||||||
|
}
|
||||||
|
spec {
|
||||||
|
hard {
|
||||||
|
pods = 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccKubernetesResourceQuotaConfig_withScopes(name string) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
resource "kubernetes_resource_quota" "test" {
|
||||||
|
metadata {
|
||||||
|
name = "%s"
|
||||||
|
}
|
||||||
|
spec {
|
||||||
|
hard {
|
||||||
|
pods = 10
|
||||||
|
}
|
||||||
|
scopes = ["BestEffort"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccKubernetesResourceQuotaConfig_withScopesModified(name string) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
resource "kubernetes_resource_quota" "test" {
|
||||||
|
metadata {
|
||||||
|
name = "%s"
|
||||||
|
}
|
||||||
|
spec {
|
||||||
|
hard {
|
||||||
|
pods = 10
|
||||||
|
}
|
||||||
|
scopes = ["NotBestEffort"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, name)
|
||||||
|
}
|
|
@ -163,12 +163,22 @@ func flattenResourceList(l api.ResourceList) map[string]string {
|
||||||
|
|
||||||
func expandMapToResourceList(m map[string]interface{}) (api.ResourceList, error) {
|
func expandMapToResourceList(m map[string]interface{}) (api.ResourceList, error) {
|
||||||
out := make(map[api.ResourceName]resource.Quantity)
|
out := make(map[api.ResourceName]resource.Quantity)
|
||||||
for stringKey, v := range m {
|
for stringKey, origValue := range m {
|
||||||
key := api.ResourceName(stringKey)
|
key := api.ResourceName(stringKey)
|
||||||
value, err := resource.ParseQuantity(v.(string))
|
var value resource.Quantity
|
||||||
|
|
||||||
|
if v, ok := origValue.(int); ok {
|
||||||
|
q := resource.NewQuantity(int64(v), resource.DecimalExponent)
|
||||||
|
value = *q
|
||||||
|
} else if v, ok := origValue.(string); ok {
|
||||||
|
var err error
|
||||||
|
value, err = resource.ParseQuantity(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return out, fmt.Errorf("Unexpected value type: %#v", origValue)
|
||||||
|
}
|
||||||
|
|
||||||
out[key] = value
|
out[key] = value
|
||||||
}
|
}
|
||||||
|
@ -191,6 +201,55 @@ func expandPersistentVolumeAccessModes(s []interface{}) []api.PersistentVolumeAc
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func flattenResourceQuotaSpec(in api.ResourceQuotaSpec) []interface{} {
|
||||||
|
out := make([]interface{}, 1)
|
||||||
|
|
||||||
|
m := make(map[string]interface{}, 0)
|
||||||
|
m["hard"] = flattenResourceList(in.Hard)
|
||||||
|
m["scopes"] = flattenResourceQuotaScopes(in.Scopes)
|
||||||
|
|
||||||
|
out[0] = m
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func expandResourceQuotaSpec(s []interface{}) (api.ResourceQuotaSpec, error) {
|
||||||
|
out := api.ResourceQuotaSpec{}
|
||||||
|
if len(s) < 1 {
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
m := s[0].(map[string]interface{})
|
||||||
|
|
||||||
|
if v, ok := m["hard"]; ok {
|
||||||
|
list, err := expandMapToResourceList(v.(map[string]interface{}))
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
out.Hard = list
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := m["scopes"]; ok {
|
||||||
|
out.Scopes = expandResourceQuotaScopes(v.(*schema.Set).List())
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func flattenResourceQuotaScopes(in []api.ResourceQuotaScope) *schema.Set {
|
||||||
|
out := make([]string, len(in), len(in))
|
||||||
|
for i, scope := range in {
|
||||||
|
out[i] = string(scope)
|
||||||
|
}
|
||||||
|
return newStringSet(schema.HashString, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func expandResourceQuotaScopes(s []interface{}) []api.ResourceQuotaScope {
|
||||||
|
out := make([]api.ResourceQuotaScope, len(s), len(s))
|
||||||
|
for i, scope := range s {
|
||||||
|
out[i] = api.ResourceQuotaScope(scope.(string))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
func newStringSet(f schema.SchemaSetFunc, in []string) *schema.Set {
|
func newStringSet(f schema.SchemaSetFunc, in []string) *schema.Set {
|
||||||
var out = make([]interface{}, len(in), len(in))
|
var out = make([]interface{}, len(in), len(in))
|
||||||
for i, v := range in {
|
for i, v := range in {
|
||||||
|
@ -198,3 +257,25 @@ func newStringSet(f schema.SchemaSetFunc, in []string) *schema.Set {
|
||||||
}
|
}
|
||||||
return schema.NewSet(f, out)
|
return schema.NewSet(f, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resourceListEquals(x, y api.ResourceList) bool {
|
||||||
|
for k, v := range x {
|
||||||
|
yValue, ok := y[k]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v.Cmp(yValue) != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k, v := range y {
|
||||||
|
xValue, ok := x[k]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v.Cmp(xValue) != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
|
@ -62,12 +62,21 @@ func validateLabels(value interface{}, key string) (ws []string, es []error) {
|
||||||
|
|
||||||
func validateResourceList(value interface{}, key string) (ws []string, es []error) {
|
func validateResourceList(value interface{}, key string) (ws []string, es []error) {
|
||||||
m := value.(map[string]interface{})
|
m := value.(map[string]interface{})
|
||||||
for k, v := range m {
|
for k, value := range m {
|
||||||
val := v.(string)
|
if _, ok := value.(int); ok {
|
||||||
_, err := resource.ParseQuantity(val)
|
continue
|
||||||
if err != nil {
|
|
||||||
es = append(es, fmt.Errorf("%s.%s (%q): %s", key, k, val, err))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v, ok := value.(string); ok {
|
||||||
|
_, err := resource.ParseQuantity(v)
|
||||||
|
if err != nil {
|
||||||
|
es = append(es, fmt.Errorf("%s.%s (%q): %s", key, k, v, err))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err := "Value can be either string or int"
|
||||||
|
es = append(es, fmt.Errorf("%s.%s (%#v): %s", key, k, value, err))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
---
|
||||||
|
layout: "kubernetes"
|
||||||
|
page_title: "Kubernetes: kubernetes_resource_quota"
|
||||||
|
sidebar_current: "docs-kubernetes-resource-resource-quota"
|
||||||
|
description: |-
|
||||||
|
A resource quota provides constraints that limit aggregate resource consumption per namespace. It can limit the quantity of objects that can be created in a namespace by type, as well as the total amount of compute resources that may be consumed by resources in that project.
|
||||||
|
---
|
||||||
|
|
||||||
|
# kubernetes_resource_quota
|
||||||
|
|
||||||
|
A resource quota provides constraints that limit aggregate resource consumption per namespace. It can limit the quantity of objects that can be created in a namespace by type, as well as the total amount of compute resources that may be consumed by resources in that project.
|
||||||
|
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
resource "kubernetes_resource_quota" "example" {
|
||||||
|
metadata {
|
||||||
|
name = "terraform-example"
|
||||||
|
}
|
||||||
|
spec {
|
||||||
|
hard {
|
||||||
|
pods = 10
|
||||||
|
}
|
||||||
|
scopes = ["BestEffort"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `metadata` - (Required) Standard resource quota's metadata. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#metadata
|
||||||
|
* `spec` - (Optional) Spec defines the desired quota. http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status
|
||||||
|
|
||||||
|
## Nested Blocks
|
||||||
|
|
||||||
|
### `metadata`
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
* `annotations` - (Optional) An unstructured key value map stored with the resource quota that may be used to store arbitrary metadata. More info: http://kubernetes.io/docs/user-guide/annotations
|
||||||
|
* `labels` - (Optional) Map of string keys and values that can be used to organize and categorize (scope and select) the resource quota. May match selectors of replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels
|
||||||
|
* `name` - (Optional) Name of the resource quota, 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 resource quota 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 resource quota that can be used by clients to determine when resource quota 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 resource quota.
|
||||||
|
* `uid` - The unique in time and space value for this resource quota. More info: http://kubernetes.io/docs/user-guide/identifiers#uids
|
||||||
|
|
||||||
|
### `spec`
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
* `hard` - (Optional) The set of desired hard limits for each named resource. More info: http://releases.k8s.io/HEAD/docs/design/admission_control_resource_quota.md#admissioncontrol-plugin-resourcequota
|
||||||
|
* `scopes` - (Optional) A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects.
|
||||||
|
|
||||||
|
## Import
|
||||||
|
|
||||||
|
Resource Quota can be imported using its name, e.g.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ terraform import kubernetes_resource_quota.example terraform-example
|
||||||
|
```
|
|
@ -25,6 +25,9 @@
|
||||||
<li<%= sidebar_current("docs-kubernetes-resource-persistent-volume-claim") %>>
|
<li<%= sidebar_current("docs-kubernetes-resource-persistent-volume-claim") %>>
|
||||||
<a href="/docs/providers/kubernetes/r/persistent_volume_claim.html">kubernetes_persistent_volume_claim</a>
|
<a href="/docs/providers/kubernetes/r/persistent_volume_claim.html">kubernetes_persistent_volume_claim</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li<%= sidebar_current("docs-kubernetes-resource-resource-quota") %>>
|
||||||
|
<a href="/docs/providers/kubernetes/r/resource_quota.html">kubernetes_resource_quota</a>
|
||||||
|
</li>
|
||||||
<li<%= sidebar_current("docs-kubernetes-resource-secret") %>>
|
<li<%= sidebar_current("docs-kubernetes-resource-secret") %>>
|
||||||
<a href="/docs/providers/kubernetes/r/secret.html">kubernetes_secret</a>
|
<a href="/docs/providers/kubernetes/r/secret.html">kubernetes_secret</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
Loading…
Reference in New Issue