diff --git a/GNUmakefile b/GNUmakefile index b455697f0f..660de6637c 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -8,6 +8,7 @@ PKG_NAME := kubernetes OS_ARCH := $(shell go env GOOS)_$(shell go env GOARCH) TF_PROV_DOCS := $(PWD)/kubernetes/test-infra/tfproviderdocs EXT_PROV_DIR := $(PWD)/kubernetes/test-infra/external-providers +EXT_PROV_BIN := /tmp/.terraform.d/localhost/test/kubernetes/9.9.9/$(OS_ARCH)/terraform-provider-kubernetes_9.9.9_$(OS_ARCH) ifneq ($(PWD),$(PROVIDER_DIR)) $(error "Makefile must be run from the provider directory") @@ -15,7 +16,7 @@ endif default: build -all: build depscheck fmtcheck test testacc test-compile tests-lint tests-lint-fix tools vet website-lint website-lint-fix +all: build depscheck fmtcheck test test-update testacc test-compile tests-lint tests-lint-fix tools vet website-lint website-lint-fix build: fmtcheck go install @@ -43,12 +44,11 @@ test: fmtcheck echo $(TEST) | \ xargs -t -n4 go test $(TESTARGS) -timeout=30s -parallel=4 -testacc: fmtcheck +testacc: fmtcheck vet rm -rf $(EXT_PROV_DIR)/.terraform $(EXT_PROV_DIR)/.terraform.lock.hcl || true mkdir $(EXT_PROV_DIR)/.terraform mkdir -p /tmp/.terraform.d/localhost/test/kubernetes/9.9.9/$(OS_ARCH) || true - go clean -cache - go build -o /tmp/.terraform.d/localhost/test/kubernetes/9.9.9/$(OS_ARCH)/terraform-provider-kubernetes_9.9.9_$(OS_ARCH) + ls $(EXT_PROV_BIN) || go build -o $(EXT_PROV_BIN) cd $(EXT_PROV_DIR) && TF_CLI_CONFIG_FILE=$(EXT_PROV_DIR)/.terraformrc TF_PLUGIN_CACHE_DIR=$(EXT_PROV_DIR)/.terraform terraform init -upgrade TF_CLI_CONFIG_FILE=$(EXT_PROV_DIR)/.terraformrc TF_PLUGIN_CACHE_DIR=$(EXT_PROV_DIR)/.terraform TF_ACC=1 go test $(TEST) -v $(TESTARGS) -timeout 120m @@ -73,6 +73,15 @@ tests-lint-fix: tools @find ./kubernetes -name "*_test.go" -exec sed -i ':a;N;$$!ba;s/fmt.Sprintf(`\n/fmt.Sprintf(`/g' '{}' \; # remove newlines for terrafmt @terrafmt fmt -f ./kubernetes --pattern '*_test.go' +test-update: fmtcheck vet + rm -rf $(EXT_PROV_DIR)/.terraform $(EXT_PROV_DIR)/.terraform.lock.hcl || true + mkdir $(EXT_PROV_DIR)/.terraform + mkdir -p /tmp/.terraform.d/localhost/test/kubernetes/9.9.9/$(OS_ARCH) || true + go clean -cache + go build -o /tmp/.terraform.d/localhost/test/kubernetes/9.9.9/$(OS_ARCH)/terraform-provider-kubernetes_9.9.9_$(OS_ARCH) + cd $(EXT_PROV_DIR) && TF_CLI_CONFIG_FILE=$(EXT_PROV_DIR)/.terraformrc TF_PLUGIN_CACHE_DIR=$(EXT_PROV_DIR)/.terraform terraform init -upgrade + TF_CLI_CONFIG_FILE=$(EXT_PROV_DIR)/.terraformrc TF_PLUGIN_CACHE_DIR=$(EXT_PROV_DIR)/.terraform TF_ACC=1 go test $(TEST) -v -run 'regression' $(TESTARGS) + tools: go install github.com/bflad/tfproviderdocs go install github.com/client9/misspell/cmd/misspell diff --git a/README.md b/README.md index 70e54e0584..4288ace5b4 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,7 @@ Please note: We take Terraform's security and our users' trust very seriously. I ## Requirements - [Terraform](https://www.terraform.io/downloads.html) 0.12.x - - Note that version 0.11.x currently works, but is [deprecated](https://www.hashicorp.com/blog/deprecating-terraform-0-11-support-in-terraform-providers/) -- [Go](https://golang.org/doc/install) 1.14.x (to build the provider plugin) +- [Go](https://golang.org/doc/install) 1.15.x (to build the provider plugin) ## Kubernetes Alpha Provider diff --git a/kubernetes/data_source_kubernetes_pod.go b/kubernetes/data_source_kubernetes_pod.go index 5b640e1565..d74a3dcb4a 100644 --- a/kubernetes/data_source_kubernetes_pod.go +++ b/kubernetes/data_source_kubernetes_pod.go @@ -10,7 +10,7 @@ import ( ) func dataSourceKubernetesPod() *schema.Resource { - podSpecFields := podSpecFields(false, false, false) + podSpecFields := podSpecFields(false, false) // Setting this default to false prevents a perpetual diff caused by volume_mounts // being mutated on the server side as Kubernetes automatically adds a mount // for the service account token diff --git a/kubernetes/provider_test.go b/kubernetes/provider_test.go index d07876c0fe..d73e7b6a99 100644 --- a/kubernetes/provider_test.go +++ b/kubernetes/provider_test.go @@ -16,7 +16,15 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -var testAccProviders map[string]*schema.Provider +// Global constants for testing images (reduces the number of docker pulls). +const ( + nginxImageVersion = "nginx:1.19.4" + nginxImageVersion1 = "nginx:1.19.3" + busyboxImageVersion = "busybox:1.32.0" + busyboxImageVersion1 = "busybox:1.31" + alpineImageVersion = "alpine:3.12.1" +) + var testAccProvider *schema.Provider var testAccExternalProviders map[string]resource.ExternalProvider var testAccProviderFactories = map[string]func() (*schema.Provider, error){ @@ -27,9 +35,6 @@ var testAccProviderFactories = map[string]func() (*schema.Provider, error){ func init() { testAccProvider = Provider() - testAccProviders = map[string]*schema.Provider{ - "kubernetes": testAccProvider, - } testAccProviderFactories = map[string]func() (*schema.Provider, error){ "kubernetes": func() (*schema.Provider, error) { return Provider(), nil diff --git a/kubernetes/resource_kubernetes_affinity_test.go b/kubernetes/resource_kubernetes_affinity_test.go index f93ea5d7b5..94eda1cb81 100644 --- a/kubernetes/resource_kubernetes_affinity_test.go +++ b/kubernetes/resource_kubernetes_affinity_test.go @@ -12,7 +12,7 @@ import ( func TestAccKubernetesPod_with_node_affinity_with_required_during_scheduling_ignored_during_execution(t *testing.T) { var conf api.Pod podName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := "nginx:1.7.9" + imageName := nginxImageVersion keyName := "spec.0.affinity.0.node_affinity.0.required_during_scheduling_ignored_during_execution" resource.Test(t, resource.TestCase{ @@ -45,7 +45,7 @@ func TestAccKubernetesPod_with_node_affinity_with_required_during_scheduling_ign func TestAccKubernetesPod_with_node_affinity_with_preferred_during_scheduling_ignored_during_execution(t *testing.T) { var conf api.Pod podName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := "nginx:1.7.9" + imageName := nginxImageVersion keyName := "spec.0.affinity.0.node_affinity.0.preferred_during_scheduling_ignored_during_execution" resource.Test(t, resource.TestCase{ @@ -82,7 +82,7 @@ func TestAccKubernetesPod_with_node_affinity_with_preferred_during_scheduling_ig func TestAccKubernetesPod_with_pod_affinity_with_required_during_scheduling_ignored_during_execution(t *testing.T) { var conf api.Pod podName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := "nginx:1.7.9" + imageName := nginxImageVersion keyName := "spec.0.affinity.0.pod_affinity.0.required_during_scheduling_ignored_during_execution" resource.Test(t, resource.TestCase{ @@ -116,7 +116,7 @@ func TestAccKubernetesPod_with_pod_affinity_with_required_during_scheduling_igno func TestAccKubernetesPod_with_pod_affinity_with_preferred_during_scheduling_ignored_during_execution(t *testing.T) { var conf api.Pod podName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := "nginx:1.7.9" + imageName := nginxImageVersion keyName := "spec.0.affinity.0.pod_affinity.0.preferred_during_scheduling_ignored_during_execution" resource.Test(t, resource.TestCase{ @@ -153,7 +153,7 @@ func TestAccKubernetesPod_with_pod_affinity_with_preferred_during_scheduling_ign func TestAccKubernetesPod_with_pod_anti_affinity_with_required_during_scheduling_ignored_during_execution(t *testing.T) { var conf api.Pod podName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := "nginx:1.7.9" + imageName := nginxImageVersion keyName := "spec.0.affinity.0.pod_anti_affinity.0.required_during_scheduling_ignored_during_execution" resource.Test(t, resource.TestCase{ @@ -187,7 +187,7 @@ func TestAccKubernetesPod_with_pod_anti_affinity_with_required_during_scheduling func TestAccKubernetesPod_with_pod_anti_affinity_with_preferred_during_scheduling_ignored_during_execution(t *testing.T) { var conf api.Pod podName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := "nginx:1.7.9" + imageName := nginxImageVersion keyName := "spec.0.affinity.0.pod_anti_affinity.0.preferred_during_scheduling_ignored_during_execution" resource.Test(t, resource.TestCase{ diff --git a/kubernetes/resource_kubernetes_cron_job_test.go b/kubernetes/resource_kubernetes_cron_job_test.go index fbd3f8cb3c..9019f4c23c 100644 --- a/kubernetes/resource_kubernetes_cron_job_test.go +++ b/kubernetes/resource_kubernetes_cron_job_test.go @@ -15,6 +15,7 @@ import ( func TestAccKubernetesCronJob_basic(t *testing.T) { var conf v1beta1.CronJob name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + imageName := alpineImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -23,7 +24,7 @@ func TestAccKubernetesCronJob_basic(t *testing.T) { CheckDestroy: testAccCheckKubernetesCronJobDestroy, Steps: []resource.TestStep{ { - Config: testAccKubernetesCronJobConfig_basic(name), + Config: testAccKubernetesCronJobConfig_basic(name, imageName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesCronJobExists("kubernetes_cron_job.test", &conf), resource.TestCheckResourceAttr("kubernetes_cron_job.test", "metadata.0.name", name), @@ -42,11 +43,11 @@ func TestAccKubernetesCronJob_basic(t *testing.T) { resource.TestCheckResourceAttr("kubernetes_cron_job.test", "spec.0.job_template.0.spec.0.parallelism", "1"), resource.TestCheckResourceAttr("kubernetes_cron_job.test", "spec.0.job_template.0.spec.0.backoff_limit", "2"), resource.TestCheckResourceAttr("kubernetes_cron_job.test", "spec.0.job_template.0.spec.0.template.0.spec.0.container.0.name", "hello"), - resource.TestCheckResourceAttr("kubernetes_cron_job.test", "spec.0.job_template.0.spec.0.template.0.spec.0.container.0.image", "alpine"), + resource.TestCheckResourceAttr("kubernetes_cron_job.test", "spec.0.job_template.0.spec.0.template.0.spec.0.container.0.image", imageName), ), }, { - Config: testAccKubernetesCronJobConfig_modified(name), + Config: testAccKubernetesCronJobConfig_modified(name, imageName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesCronJobExists("kubernetes_cron_job.test", &conf), resource.TestCheckResourceAttr("kubernetes_cron_job.test", "metadata.0.name", name), @@ -75,6 +76,7 @@ func TestAccKubernetesCronJob_basic(t *testing.T) { func TestAccKubernetesCronJob_extra(t *testing.T) { var conf v1beta1.CronJob name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + imageName := alpineImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -83,7 +85,7 @@ func TestAccKubernetesCronJob_extra(t *testing.T) { CheckDestroy: testAccCheckKubernetesCronJobDestroy, Steps: []resource.TestStep{ { - Config: testAccKubernetesCronJobConfig_extra(name), + Config: testAccKubernetesCronJobConfig_extra(name, imageName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesCronJobExists("kubernetes_cron_job.test", &conf), resource.TestCheckResourceAttr("kubernetes_cron_job.test", "metadata.0.name", name), @@ -96,7 +98,7 @@ func TestAccKubernetesCronJob_extra(t *testing.T) { ), }, { - Config: testAccKubernetesCronJobConfig_extraModified(name), + Config: testAccKubernetesCronJobConfig_extraModified(name, imageName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesCronJobExists("kubernetes_cron_job.test", &conf), resource.TestCheckResourceAttr("kubernetes_cron_job.test", "metadata.0.name", name), @@ -169,7 +171,7 @@ func testAccCheckKubernetesCronJobExists(n string, obj *v1beta1.CronJob) resourc } } -func testAccKubernetesCronJobConfig_basic(name string) string { +func testAccKubernetesCronJobConfig_basic(name, imageName string) string { return fmt.Sprintf(`resource "kubernetes_cron_job" "test" { metadata { name = "%s" @@ -190,7 +192,7 @@ func testAccKubernetesCronJobConfig_basic(name string) string { spec { container { name = "hello" - image = "alpine" + image = "%s" command = ["echo", "'hello'"] } } @@ -198,10 +200,10 @@ func testAccKubernetesCronJobConfig_basic(name string) string { } } } -}`, name) +}`, name, imageName) } -func testAccKubernetesCronJobConfig_modified(name string) string { +func testAccKubernetesCronJobConfig_modified(name, imageName string) string { return fmt.Sprintf(`resource "kubernetes_cron_job" "test" { metadata { name = "%s" @@ -221,7 +223,7 @@ func testAccKubernetesCronJobConfig_modified(name string) string { spec { container { name = "hello" - image = "alpine" + image = "%s" command = ["echo", "'hello'"] } } @@ -229,10 +231,10 @@ func testAccKubernetesCronJobConfig_modified(name string) string { } } } -}`, name) +}`, name, imageName) } -func testAccKubernetesCronJobConfig_extra(name string) string { +func testAccKubernetesCronJobConfig_extra(name, imageName string) string { return fmt.Sprintf(`resource "kubernetes_cron_job" "test" { metadata { name = "%s" @@ -252,7 +254,7 @@ func testAccKubernetesCronJobConfig_extra(name string) string { spec { container { name = "hello" - image = "alpine" + image = "%s" command = ["echo", "'hello'"] } } @@ -260,10 +262,10 @@ func testAccKubernetesCronJobConfig_extra(name string) string { } } } -}`, name) +}`, name, imageName) } -func testAccKubernetesCronJobConfig_extraModified(name string) string { +func testAccKubernetesCronJobConfig_extraModified(name, imageName string) string { return fmt.Sprintf(`resource "kubernetes_cron_job" "test" { metadata { name = "%s" @@ -283,7 +285,7 @@ func testAccKubernetesCronJobConfig_extraModified(name string) string { spec { container { name = "hello" - image = "alpine" + image = "%s" command = ["echo", "'hello'"] } } @@ -291,5 +293,5 @@ func testAccKubernetesCronJobConfig_extraModified(name string) string { } } } -}`, name) +}`, name, imageName) } diff --git a/kubernetes/resource_kubernetes_daemonset_test.go b/kubernetes/resource_kubernetes_daemonset_test.go index 43a237fa19..45aafa62ca 100644 --- a/kubernetes/resource_kubernetes_daemonset_test.go +++ b/kubernetes/resource_kubernetes_daemonset_test.go @@ -16,6 +16,7 @@ import ( func TestAccKubernetesDaemonSet_minimal(t *testing.T) { var conf appsv1.DaemonSet name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -25,11 +26,11 @@ func TestAccKubernetesDaemonSet_minimal(t *testing.T) { CheckDestroy: testAccCheckKubernetesDaemonSetDestroy, Steps: []resource.TestStep{ { - Config: testAccKubernetesDaemonSetConfig_minimal(name), + Config: testAccKubernetesDaemonSetConfig_minimal(name, imageName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesDaemonSetExists("kubernetes_daemonset.test", &conf), resource.TestCheckResourceAttr("kubernetes_daemonset.test", "metadata.0.name", name), - resource.TestCheckResourceAttr("kubernetes_daemonset.test", "spec.0.template.0.spec.0.container.0.image", "nginx:1.7.8"), + resource.TestCheckResourceAttr("kubernetes_daemonset.test", "spec.0.template.0.spec.0.container.0.image", imageName), resource.TestCheckResourceAttr("kubernetes_daemonset.test", "spec.0.template.0.spec.0.container.0.name", "tf-acc-test"), ), }, @@ -41,6 +42,8 @@ func TestAccKubernetesDaemonSet_basic(t *testing.T) { var conf appsv1.DaemonSet name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) resourceName := "kubernetes_daemonset.test" + imageName := nginxImageVersion + imageName1 := nginxImageVersion1 resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -50,7 +53,7 @@ func TestAccKubernetesDaemonSet_basic(t *testing.T) { CheckDestroy: testAccCheckKubernetesDaemonSetDestroy, Steps: []resource.TestStep{ { - Config: testAccKubernetesDaemonSetConfig_basic(name), + Config: testAccKubernetesDaemonSetConfig_basic(name, imageName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesDaemonSetExists(resourceName, &conf), resource.TestCheckResourceAttr("kubernetes_daemonset.test", "metadata.0.annotations.%", "2"), @@ -67,7 +70,7 @@ func TestAccKubernetesDaemonSet_basic(t *testing.T) { resource.TestCheckResourceAttrSet("kubernetes_daemonset.test", "metadata.0.resource_version"), resource.TestCheckResourceAttrSet("kubernetes_daemonset.test", "metadata.0.self_link"), resource.TestCheckResourceAttrSet("kubernetes_daemonset.test", "metadata.0.uid"), - resource.TestCheckResourceAttr("kubernetes_daemonset.test", "spec.0.template.0.spec.0.container.0.image", "nginx:1.7.8"), + resource.TestCheckResourceAttr("kubernetes_daemonset.test", "spec.0.template.0.spec.0.container.0.image", imageName), resource.TestCheckResourceAttr("kubernetes_daemonset.test", "spec.0.template.0.spec.0.container.0.name", "tf-acc-test"), resource.TestCheckResourceAttr("kubernetes_daemonset.test", "spec.0.strategy.0.type", "RollingUpdate"), resource.TestCheckResourceAttr("kubernetes_daemonset.test", "spec.0.strategy.0.rolling_update.0.max_unavailable", "1"), @@ -81,7 +84,7 @@ func TestAccKubernetesDaemonSet_basic(t *testing.T) { ImportStateVerifyIgnore: []string{"metadata.0.resource_version", "wait_for_rollout"}, }, { - Config: testAccKubernetesDaemonSetConfig_modified(name), + Config: testAccKubernetesDaemonSetConfig_modified(name, imageName1), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesDaemonSetExists(resourceName, &conf), resource.TestCheckResourceAttr("kubernetes_daemonset.test", "metadata.0.annotations.%", "2"), @@ -97,7 +100,7 @@ func TestAccKubernetesDaemonSet_basic(t *testing.T) { resource.TestCheckResourceAttrSet("kubernetes_daemonset.test", "metadata.0.resource_version"), resource.TestCheckResourceAttrSet("kubernetes_daemonset.test", "metadata.0.self_link"), resource.TestCheckResourceAttrSet("kubernetes_daemonset.test", "metadata.0.uid"), - resource.TestCheckResourceAttr("kubernetes_daemonset.test", "spec.0.template.0.spec.0.container.0.image", "nginx:1.7.9"), + resource.TestCheckResourceAttr("kubernetes_daemonset.test", "spec.0.template.0.spec.0.container.0.image", imageName1), resource.TestCheckResourceAttr("kubernetes_daemonset.test", "spec.0.template.0.spec.0.container.0.name", "tf-acc-test"), resource.TestCheckResourceAttr("kubernetes_daemonset.test", "spec.0.template.0.spec.0.dns_config.#", "1"), resource.TestCheckResourceAttr("kubernetes_daemonset.test", "spec.0.template.0.spec.0.dns_config.0.nameservers.#", "3"), @@ -123,7 +126,7 @@ func TestAccKubernetesDaemonSet_with_template_metadata(t *testing.T) { var conf appsv1.DaemonSet depName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := "nginx:1.7.9" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -163,6 +166,7 @@ func TestAccKubernetesDaemonSet_with_template_metadata(t *testing.T) { func TestAccKubernetesDaemonSet_initContainer(t *testing.T) { var conf appsv1.DaemonSet name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -172,10 +176,10 @@ func TestAccKubernetesDaemonSet_initContainer(t *testing.T) { CheckDestroy: testAccCheckKubernetesDaemonSetDestroy, Steps: []resource.TestStep{ { - Config: testAccKubernetesDaemonSetWithInitContainer(name, "nginx:1.7.8"), + Config: testAccKubernetesDaemonSetWithInitContainer(name, imageName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesDaemonSetExists("kubernetes_daemonset.test", &conf), - resource.TestCheckResourceAttr("kubernetes_daemonset.test", "spec.0.template.0.spec.0.init_container.0.image", "alpine"), + resource.TestCheckResourceAttr("kubernetes_daemonset.test", "spec.0.template.0.spec.0.init_container.0.image", imageName), ), }, }, @@ -193,7 +197,7 @@ func TestAccKubernetesDaemonSet_noTopLevelLabels(t *testing.T) { CheckDestroy: testAccCheckKubernetesDaemonSetDestroy, Steps: []resource.TestStep{ { - Config: testAccKubernetesDaemonSetWithNoTopLevelLabels(name, "nginx:1.7.8"), + Config: testAccKubernetesDaemonSetWithNoTopLevelLabels(name, nginxImageVersion1), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesDaemonSetExists("kubernetes_daemonset.test", &conf), resource.TestCheckResourceAttr("kubernetes_daemonset.test", "metadata.0.labels.%", "0"), @@ -266,6 +270,7 @@ func TestAccKubernetesDaemonSet_with_tolerations_unset_toleration_seconds(t *tes func TestAccKubernetesDaemonSet_regression(t *testing.T) { var conf1, conf2 appsv1.DaemonSet name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -275,7 +280,7 @@ func TestAccKubernetesDaemonSet_regression(t *testing.T) { CheckDestroy: testAccCheckKubernetesDaemonSetDestroy, Steps: []resource.TestStep{ { - Config: requiredProviders() + testAccKubernetesDaemonSetConfig_regression("kubernetes-released", name), + Config: requiredProviders() + testAccKubernetesDaemonSetConfig_regression("kubernetes-released", name, imageName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesDaemonSetExists("kubernetes_daemonset.test", &conf1), resource.TestCheckResourceAttr("kubernetes_daemonset.test", "metadata.0.annotations.%", "2"), @@ -290,14 +295,14 @@ func TestAccKubernetesDaemonSet_regression(t *testing.T) { resource.TestCheckResourceAttrSet("kubernetes_daemonset.test", "metadata.0.resource_version"), resource.TestCheckResourceAttrSet("kubernetes_daemonset.test", "metadata.0.self_link"), resource.TestCheckResourceAttrSet("kubernetes_daemonset.test", "metadata.0.uid"), - resource.TestCheckResourceAttr("kubernetes_daemonset.test", "spec.0.template.0.spec.0.container.0.image", "nginx:1.7.8"), + resource.TestCheckResourceAttr("kubernetes_daemonset.test", "spec.0.template.0.spec.0.container.0.image", imageName), resource.TestCheckResourceAttr("kubernetes_daemonset.test", "spec.0.template.0.spec.0.container.0.name", "tf-acc-test"), resource.TestCheckResourceAttr("kubernetes_daemonset.test", "spec.0.strategy.0.type", "RollingUpdate"), resource.TestCheckResourceAttr("kubernetes_daemonset.test", "spec.0.strategy.0.rolling_update.0.max_unavailable", "1"), ), }, { - Config: requiredProviders() + testAccKubernetesDaemonSetConfig_regression("kubernetes-local", name), + Config: requiredProviders() + testAccKubernetesDaemonSetConfig_regression("kubernetes-local", name, imageName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesDaemonSetExists("kubernetes_daemonset.test", &conf2), resource.TestCheckResourceAttr("kubernetes_daemonset.test", "metadata.0.annotations.%", "2"), @@ -312,7 +317,7 @@ func TestAccKubernetesDaemonSet_regression(t *testing.T) { resource.TestCheckResourceAttrSet("kubernetes_daemonset.test", "metadata.0.resource_version"), resource.TestCheckResourceAttrSet("kubernetes_daemonset.test", "metadata.0.self_link"), resource.TestCheckResourceAttrSet("kubernetes_daemonset.test", "metadata.0.uid"), - resource.TestCheckResourceAttr("kubernetes_daemonset.test", "spec.0.template.0.spec.0.container.0.image", "nginx:1.7.8"), + resource.TestCheckResourceAttr("kubernetes_daemonset.test", "spec.0.template.0.spec.0.container.0.image", imageName), resource.TestCheckResourceAttr("kubernetes_daemonset.test", "spec.0.template.0.spec.0.container.0.name", "tf-acc-test"), resource.TestCheckResourceAttr("kubernetes_daemonset.test", "spec.0.strategy.0.type", "RollingUpdate"), resource.TestCheckResourceAttr("kubernetes_daemonset.test", "spec.0.strategy.0.rolling_update.0.max_unavailable", "1"), @@ -394,7 +399,7 @@ func testAccCheckKubernetesDaemonSetExists(n string, obj *appsv1.DaemonSet) reso } } -func testAccKubernetesDaemonSetConfig_minimal(name string) string { +func testAccKubernetesDaemonSetConfig_minimal(name, imageName string) string { return fmt.Sprintf(`resource "kubernetes_daemonset" "test" { metadata { name = "%s" @@ -416,17 +421,17 @@ func testAccKubernetesDaemonSetConfig_minimal(name string) string { spec { container { - image = "nginx:1.7.8" + image = "%s" name = "tf-acc-test" } } } } } -`, name) +`, name, imageName) } -func testAccKubernetesDaemonSetConfig_basic(name string) string { +func testAccKubernetesDaemonSetConfig_basic(name, imageName string) string { return fmt.Sprintf(`resource "kubernetes_daemonset" "test" { metadata { annotations = { @@ -463,17 +468,17 @@ func testAccKubernetesDaemonSetConfig_basic(name string) string { spec { container { - image = "nginx:1.7.8" + image = "%s" name = "tf-acc-test" } } } } } -`, name) +`, name, imageName) } -func testAccKubernetesDaemonSetConfig_modified(name string) string { +func testAccKubernetesDaemonSetConfig_modified(name, imageName string) string { return fmt.Sprintf(`resource "kubernetes_daemonset" "test" { metadata { annotations = { @@ -509,7 +514,7 @@ func testAccKubernetesDaemonSetConfig_modified(name string) string { spec { container { - image = "nginx:1.7.9" + image = "%s" name = "tf-acc-test" } @@ -532,7 +537,7 @@ func testAccKubernetesDaemonSetConfig_modified(name string) string { } } } -`, name) +`, name, imageName) } func testAccKubernetesDaemonSetConfigWithTemplateMetadata(depName, imageName string) string { @@ -648,7 +653,7 @@ func testAccKubernetesDaemonSetWithInitContainer(depName, imageName string) stri spec { init_container { name = "hello" - image = "alpine" + image = "%s" command = ["echo", "'hello'"] } @@ -660,7 +665,7 @@ func testAccKubernetesDaemonSetWithInitContainer(depName, imageName string) stri } } } -`, depName, imageName) +`, depName, imageName, imageName) } func testAccKubernetesDaemonSetWithNoTopLevelLabels(depName, imageName string) string { @@ -748,7 +753,7 @@ func testAccKubernetesDaemonSetConfigWithTolerations(rcName, imageName string, t `, rcName, operator, valueString, tolerationDuration, imageName) } -func testAccKubernetesDaemonSetConfig_regression(provider, name string) string { +func testAccKubernetesDaemonSetConfig_regression(provider, name, imageName string) string { return fmt.Sprintf(`resource "kubernetes_daemonset" "test" { provider = %s metadata { @@ -786,12 +791,12 @@ func testAccKubernetesDaemonSetConfig_regression(provider, name string) string { spec { container { - image = "nginx:1.7.8" + image = "%s" name = "tf-acc-test" } } } } } -`, provider, name) +`, provider, name, imageName) } diff --git a/kubernetes/resource_kubernetes_deployment.go b/kubernetes/resource_kubernetes_deployment.go index 6b42aed1dc..b1642e91bf 100644 --- a/kubernetes/resource_kubernetes_deployment.go +++ b/kubernetes/resource_kubernetes_deployment.go @@ -195,7 +195,7 @@ func resourceKubernetesDeploymentSchemaV1() map[string]*schema.Schema { Required: true, MaxItems: 1, Elem: &schema.Resource{ - Schema: podSpecFields(true, false, false), + Schema: podSpecFields(true, false), }, }, }, diff --git a/kubernetes/resource_kubernetes_deployment_test.go b/kubernetes/resource_kubernetes_deployment_test.go index 5bf08a13e5..623718df84 100644 --- a/kubernetes/resource_kubernetes_deployment_test.go +++ b/kubernetes/resource_kubernetes_deployment_test.go @@ -14,13 +14,38 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -const ( - defaultNginxImage = "nginx:1.19" -) +func TestAccKubernetesDeployment_minimal(t *testing.T) { + var conf appsv1.Deployment + name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + imageName := nginxImageVersion + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "kubernetes_deployment.test", + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckKubernetesDeploymentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKubernetesDeploymentConfig_minimal(name, imageName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesDeploymentExists("kubernetes_deployment.test", &conf), + resource.TestCheckResourceAttrSet("kubernetes_deployment.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_deployment.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_deployment.test", "metadata.0.uid"), + ), + }, + { + Config: testAccKubernetesDeploymentConfig_minimal(name, imageName), + PlanOnly: true, + }, + }, + }) +} func TestAccKubernetesDeployment_basic(t *testing.T) { var conf appsv1.Deployment name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -29,7 +54,7 @@ func TestAccKubernetesDeployment_basic(t *testing.T) { CheckDestroy: testAccCheckKubernetesDeploymentDestroy, Steps: []resource.TestStep{ { - Config: testAccKubernetesDeploymentConfig_basic(name), + Config: testAccKubernetesDeploymentConfig_basic(name, imageName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesDeploymentExists("kubernetes_deployment.test", &conf), resource.TestCheckResourceAttr("kubernetes_deployment.test", "metadata.0.annotations.%", "2"), @@ -46,7 +71,7 @@ func TestAccKubernetesDeployment_basic(t *testing.T) { resource.TestCheckResourceAttrSet("kubernetes_deployment.test", "metadata.0.resource_version"), resource.TestCheckResourceAttrSet("kubernetes_deployment.test", "metadata.0.self_link"), resource.TestCheckResourceAttrSet("kubernetes_deployment.test", "metadata.0.uid"), - resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.container.0.image", defaultNginxImage), + resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.container.0.image", imageName), resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.container.0.name", "tf-acc-test"), resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.strategy.0.type", "RollingUpdate"), resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.strategy.0.rolling_update.0.max_surge", "25%"), @@ -58,10 +83,12 @@ func TestAccKubernetesDeployment_basic(t *testing.T) { }) } -func TestAccKubernetesDeployment_initContainer(t *testing.T) { - var conf appsv1.Deployment +func TestAccKubernetesDeployment_initContainerForceNew(t *testing.T) { + var conf1, conf2 appsv1.Deployment name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - resourceName := "kubernetes_deployment.test" + imageName := busyboxImageVersion + imageName1 := busyboxImageVersion1 + initCommand := "until nslookup init-service.default.svc.cluster.local; do echo waiting for init-service; sleep 2; done" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -70,18 +97,85 @@ func TestAccKubernetesDeployment_initContainer(t *testing.T) { CheckDestroy: testAccCheckKubernetesDeploymentDestroy, Steps: []resource.TestStep{ { - Config: testAccKubernetesDeploymentConfig_initContainer(name), + Config: testAccKubernetesDeploymentConfig_initContainer( + name, imageName, imageName1, "64Mi", "testvar", + "initcontainer2", initCommand, "IfNotPresent"), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckKubernetesDeploymentExists(resourceName, &conf), - resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.init_container.0.image", "busybox"), - resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.init_container.0.name", "init-service"), + testAccCheckKubernetesDeploymentExists("kubernetes_deployment.test", &conf1), + resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.init_container.0.image", imageName), + resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.init_container.0.resources.0.requests.memory", "64Mi"), + resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.init_container.0.env.2.value", "testvar"), + resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.init_container.1.name", "initcontainer2"), + resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.init_container.1.image", imageName1), + resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.init_container.1.command.2", initCommand), + resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.init_container.1.image_pull_policy", "IfNotPresent"), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"metadata.0.resource_version", "wait_for_rollout"}, + { // Test for non-empty plans. No modification. + Config: testAccKubernetesDeploymentConfig_initContainer( + name, imageName, imageName1, "64Mi", "testvar", + "initcontainer2", initCommand, "IfNotPresent"), + PlanOnly: true, + }, + { // Modify resources.limits.memory. + Config: testAccKubernetesDeploymentConfig_initContainer( + name, imageName, imageName1, "80Mi", "testvar", + "initcontainer2", initCommand, "IfNotPresent"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesDeploymentExists("kubernetes_deployment.test", &conf2), + resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.init_container.0.resources.0.requests.memory", "80Mi"), + testAccCheckKubernetesDeploymentForceNew(&conf1, &conf2, false), + ), + }, + { // Modify name of environment variable. + Config: testAccKubernetesDeploymentConfig_initContainer( + name, imageName, imageName1, "64Mi", "testvar", + "initcontainer2", initCommand, "IfNotPresent"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesDeploymentExists("kubernetes_deployment.test", &conf2), + resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.init_container.0.env.2.value", "testvar"), + testAccCheckKubernetesDeploymentForceNew(&conf1, &conf2, false), + ), + }, + { // Modify init_container's command. + Config: testAccKubernetesDeploymentConfig_initContainer( + name, imageName, imageName1, "64Mi", "testvar", + "initcontainer2", "echo done", "IfNotPresent"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesDeploymentExists("kubernetes_deployment.test", &conf2), + resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.init_container.1.command.2", "echo done"), + testAccCheckKubernetesDeploymentForceNew(&conf1, &conf2, false), + ), + }, + { // Modify init_container's image_pull_policy. + Config: testAccKubernetesDeploymentConfig_initContainer( + name, imageName, imageName1, "64Mi", "testvar", + "initcontainer2", "echo done", "Never"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesDeploymentExists("kubernetes_deployment.test", &conf2), + resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.init_container.1.image_pull_policy", "Never"), + testAccCheckKubernetesDeploymentForceNew(&conf1, &conf2, false), + ), + }, + { // Modify init_container's image + Config: testAccKubernetesDeploymentConfig_initContainer( + name, imageName, imageName, "64Mi", "testvar", + "initcontainer2", "echo done", "Never"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesDeploymentExists("kubernetes_deployment.test", &conf2), + resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.init_container.1.image", imageName), + testAccCheckKubernetesDeploymentForceNew(&conf1, &conf2, false), + ), + }, + { // Modify init_container's name. + Config: testAccKubernetesDeploymentConfig_initContainer( + name, imageName, imageName, "64Mi", "testvar", + "initcontainertwo", "echo done", "Never"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesDeploymentExists("kubernetes_deployment.test", &conf2), + resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.init_container.1.name", "initcontainertwo"), + testAccCheckKubernetesDeploymentForceNew(&conf1, &conf2, false), + ), }, }, }) @@ -90,7 +184,7 @@ func TestAccKubernetesDeployment_initContainer(t *testing.T) { func TestAccKubernetesDeployment_generatedName(t *testing.T) { var conf appsv1.Deployment prefix := "tf-acc-test-gen-" - resourceName := "kubernetes_deployment.test" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -99,7 +193,7 @@ func TestAccKubernetesDeployment_generatedName(t *testing.T) { CheckDestroy: testAccCheckKubernetesDeploymentDestroy, Steps: []resource.TestStep{ { - Config: testAccKubernetesDeploymentConfig_generatedName(prefix), + Config: testAccKubernetesDeploymentConfig_generatedName(prefix, imageName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesDeploymentExists("kubernetes_deployment.test", &conf), resource.TestCheckResourceAttr("kubernetes_deployment.test", "metadata.0.annotations.%", "0"), @@ -115,7 +209,7 @@ func TestAccKubernetesDeployment_generatedName(t *testing.T) { ), }, { - ResourceName: resourceName, + ResourceName: "kubernetes_deployment.test", ImportState: true, ImportStateVerify: true, ImportStateVerifyIgnore: []string{"metadata.0.resource_version", "wait_for_rollout"}, @@ -456,7 +550,7 @@ func TestAccKubernetesDeployment_with_volume_mount(t *testing.T) { deploymentName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) secretName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := defaultNginxImage + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -482,7 +576,7 @@ func TestAccKubernetesDeployment_with_volume_mount(t *testing.T) { func TestAccKubernetesDeployment_ForceNew(t *testing.T) { var conf1, conf2 appsv1.Deployment - imageName := defaultNginxImage + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -544,7 +638,7 @@ func TestAccKubernetesDeployment_with_resource_requirements(t *testing.T) { var conf appsv1.Deployment deploymentName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := defaultNginxImage + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -572,7 +666,7 @@ func TestAccKubernetesDeployment_with_empty_dir_volume(t *testing.T) { var conf appsv1.Deployment deploymentName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := defaultNginxImage + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -608,7 +702,7 @@ func TestAccKubernetesDeployment_with_empty_dir_volume(t *testing.T) { func TestAccKubernetesDeploymentUpdate_basic(t *testing.T) { var conf1, conf2 appsv1.Deployment - + imageName := nginxImageVersion deploymentName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) resource.Test(t, resource.TestCase{ @@ -617,7 +711,7 @@ func TestAccKubernetesDeploymentUpdate_basic(t *testing.T) { CheckDestroy: testAccCheckKubernetesDeploymentDestroy, Steps: []resource.TestStep{ { - Config: testAccKubernetesDeploymentConfig_basic(deploymentName), + Config: testAccKubernetesDeploymentConfig_basic(deploymentName, imageName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesDeploymentExists("kubernetes_deployment.test", &conf1), // Not to be changed @@ -627,11 +721,11 @@ func TestAccKubernetesDeploymentUpdate_basic(t *testing.T) { // To be added resource.TestCheckNoResourceAttr("kubernetes_deployment.test", "metadata.0.annotations.Different"), // To be changed - resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.container.0.image", defaultNginxImage), + resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.container.0.image", imageName), ), }, { - Config: testAccKubernetesDeploymentConfig_modified(deploymentName), + Config: testAccKubernetesDeploymentConfig_modified(deploymentName, imageName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesDeploymentExists("kubernetes_deployment.test", &conf2), // Unchanged @@ -641,7 +735,7 @@ func TestAccKubernetesDeploymentUpdate_basic(t *testing.T) { // Added resource.TestCheckResourceAttr("kubernetes_deployment.test", "metadata.0.annotations.Different", "1234"), // Changed - resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.container.0.image", defaultNginxImage), + resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.container.0.image", nginxImageVersion), testAccCheckKubernetesDeploymentForceNew(&conf1, &conf2, false), ), }, @@ -653,7 +747,7 @@ func TestAccKubernetesDeployment_with_deployment_strategy_rollingupdate(t *testi var conf appsv1.Deployment deploymentName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := defaultNginxImage + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -679,7 +773,7 @@ func TestAccKubernetesDeployment_with_share_process_namespace(t *testing.T) { var conf appsv1.Deployment deploymentName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := defaultNginxImage + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -699,6 +793,7 @@ func TestAccKubernetesDeployment_with_share_process_namespace(t *testing.T) { func TestAccKubernetesDeployment_no_rollout_wait(t *testing.T) { deploymentName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -706,7 +801,7 @@ func TestAccKubernetesDeployment_no_rollout_wait(t *testing.T) { CheckDestroy: testAccCheckKubernetesDeploymentDestroy, Steps: []resource.TestStep{ { - Config: testAccKubernetesDeploymentConfigWithWaitForRolloutFalse(deploymentName), + Config: testAccKubernetesDeploymentConfigWithWaitForRolloutFalse(deploymentName, imageName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesDeploymentRollingOut("kubernetes_deployment.test"), resource.TestCheckResourceAttr("kubernetes_deployment.test", "wait_for_rollout", "false"), @@ -720,7 +815,7 @@ func TestAccKubernetesDeployment_with_deployment_strategy_rollingupdate_max_surg var conf appsv1.Deployment deploymentName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := defaultNginxImage + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -746,7 +841,7 @@ func TestAccKubernetesDeployment_with_deployment_strategy_rollingupdate_max_surg var conf appsv1.Deployment deploymentName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := defaultNginxImage + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -772,7 +867,7 @@ func TestAccKubernetesDeployment_with_deployment_strategy_rollingupdate_max_surg var conf appsv1.Deployment deploymentName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := defaultNginxImage + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -798,7 +893,7 @@ func TestAccKubernetesDeployment_with_deployment_strategy_rollingupdate_max_surg var conf appsv1.Deployment deploymentName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := defaultNginxImage + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -824,7 +919,7 @@ func TestAccKubernetesDeployment_with_deployment_strategy_rollingupdate_max_surg var conf appsv1.Deployment deploymentName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := defaultNginxImage + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -850,7 +945,7 @@ func TestAccKubernetesDeployment_with_deployment_strategy_recreate(t *testing.T) var conf appsv1.Deployment deploymentName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := defaultNginxImage + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -874,7 +969,7 @@ func TestAccKubernetesDeployment_with_host_aliases(t *testing.T) { var conf appsv1.Deployment deploymentName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := defaultNginxImage + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -901,6 +996,7 @@ func TestAccKubernetesDeployment_with_host_aliases(t *testing.T) { func TestAccKubernetesDeployment_regression(t *testing.T) { var conf1, conf2 appsv1.Deployment name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -910,7 +1006,7 @@ func TestAccKubernetesDeployment_regression(t *testing.T) { CheckDestroy: testAccCheckKubernetesDeploymentDestroy, Steps: []resource.TestStep{ { - Config: requiredProviders() + testAccKubernetesDeploymentConfig_regression("kubernetes-released", name), + Config: requiredProviders() + testAccKubernetesDeploymentConfigReleased("kubernetes-released", name, imageName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesDeploymentExists("kubernetes_deployment.test", &conf1), resource.TestCheckResourceAttr("kubernetes_deployment.test", "metadata.0.annotations.%", "2"), @@ -927,7 +1023,7 @@ func TestAccKubernetesDeployment_regression(t *testing.T) { resource.TestCheckResourceAttrSet("kubernetes_deployment.test", "metadata.0.resource_version"), resource.TestCheckResourceAttrSet("kubernetes_deployment.test", "metadata.0.self_link"), resource.TestCheckResourceAttrSet("kubernetes_deployment.test", "metadata.0.uid"), - resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.container.0.image", defaultNginxImage), + resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.container.0.image", imageName), resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.container.0.name", "containername"), resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.strategy.0.type", "RollingUpdate"), resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.strategy.0.rolling_update.0.max_surge", "25%"), @@ -936,7 +1032,11 @@ func TestAccKubernetesDeployment_regression(t *testing.T) { ), }, { - Config: requiredProviders() + testAccKubernetesDeploymentConfig_regression("kubernetes-local", name), + PlanOnly: true, + Config: requiredProviders() + testAccKubernetesDeploymentConfigLocal("kubernetes-local", name, imageName), + }, + { + Config: requiredProviders() + testAccKubernetesDeploymentConfigLocal("kubernetes-local", name, imageName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesDeploymentExists("kubernetes_deployment.test", &conf2), resource.TestCheckResourceAttr("kubernetes_deployment.test", "metadata.0.annotations.%", "2"), @@ -953,7 +1053,7 @@ func TestAccKubernetesDeployment_regression(t *testing.T) { resource.TestCheckResourceAttrSet("kubernetes_deployment.test", "metadata.0.resource_version"), resource.TestCheckResourceAttrSet("kubernetes_deployment.test", "metadata.0.self_link"), resource.TestCheckResourceAttrSet("kubernetes_deployment.test", "metadata.0.uid"), - resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.container.0.image", defaultNginxImage), + resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.container.0.image", imageName), resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.template.0.spec.0.container.0.name", "containername"), resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.strategy.0.type", "RollingUpdate"), resource.TestCheckResourceAttr("kubernetes_deployment.test", "spec.0.strategy.0.rolling_update.0.max_surge", "25%"), @@ -969,7 +1069,7 @@ func TestAccKubernetesDeployment_regression(t *testing.T) { func TestAccKubernetesDeployment_with_resource_field_selector(t *testing.T) { var conf appsv1.Deployment rcName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := "nginx:1.7.9" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -1036,7 +1136,7 @@ func TestAccKubernetesDeployment_config_with_automount_service_account_token(t * var confDeployment appsv1.Deployment deploymentName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := defaultNginxImage + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -1135,7 +1235,37 @@ func testAccCheckKubernetesDeploymentRollingOut(n string) resource.TestCheckFunc } } -func testAccKubernetesDeploymentConfig_basic(name string) string { +func testAccKubernetesDeploymentConfig_minimal(name, imageName string) string { + return fmt.Sprintf(`resource "kubernetes_deployment" "test" { + metadata { + name = "%s" + } + spec { + replicas = 2 + selector { + match_labels = { + TestLabelOne = "one" + } + } + template { + metadata { + labels = { + TestLabelOne = "one" + } + } + spec { + container { + image = "%s" + name = "tf-acc-test" + } + } + } + } +} +`, name, imageName) +} + +func testAccKubernetesDeploymentConfig_basic(name, imageName string) string { return fmt.Sprintf(`resource "kubernetes_deployment" "test" { metadata { annotations = { @@ -1174,7 +1304,7 @@ func testAccKubernetesDeploymentConfig_basic(name string) string { spec { container { - image = %q + image = "%s" name = "tf-acc-test" port { @@ -1200,29 +1330,25 @@ func testAccKubernetesDeploymentConfig_basic(name string) string { } } } -`, name, defaultNginxImage) +`, name, imageName) } -func testAccKubernetesDeploymentConfig_initContainer(name string) string { +func testAccKubernetesDeploymentConfig_initContainer(name, imageName, imageName1, memory, envName, initName, initCommand, pullPolicy string) string { return fmt.Sprintf(`resource "kubernetes_deployment" "test" { metadata { annotations = { TestAnnotationOne = "one" TestAnnotationTwo = "two" } - labels = { TestLabelOne = "one" TestLabelTwo = "two" TestLabelThree = "three" } - name = "%s" } - spec { - replicas = 5 - + replicas = 2 selector { match_labels = { TestLabelOne = "one" @@ -1230,7 +1356,6 @@ func testAccKubernetesDeploymentConfig_initContainer(name string) string { TestLabelThree = "three" } } - template { metadata { labels = { @@ -1239,32 +1364,74 @@ func testAccKubernetesDeploymentConfig_initContainer(name string) string { TestLabelThree = "three" } } - spec { container { - name = "busybox" - image = "busybox" - command = ["sh", "-c", "echo The app is running! && sleep 3600"] - - resources { - requests = { - memory = "64Mi" - cpu = "50m" - } - } + name = "regularcontainer" + image = "%s" + command = ["sleep"] + args = ["120s"] } - init_container { - name = "init-service" - image = "busybox" - command = ["sh", "-c", "until nslookup init-service.default.svc.cluster.local; do echo waiting for init-service; sleep 2; done"] - + name = "initcontainer1" + image = "%s" + image_pull_policy = "IfNotPresent" + command = [ + "sh", + "-c", + "printenv SECRETENV CONFIGENV LIMITS_CPU CUSTOM", + ] + env { + name = "SECRETENV" + value_from { + secret_key_ref { + name = "test" + key = "SECRETENV" + } + } + } resources { requests = { - memory = "64Mi" + memory = "%s" cpu = "50m" } + limits = { + memory = "100Mi" + cpu = "100m" + } + } + env { + name = "LIMITS_CPU" + value_from { + resource_field_ref { + container_name = "initcontainer1" + resource = "requests.cpu" + divisor = "1m" + } + } + } + env_from { + config_map_ref { + name = kubernetes_config_map.test.metadata.0.name + } + prefix = "CONFIG" + } + port { + container_port = 80 } + env { + name = "CUSTOM" + value = "%s" + } + } + init_container { + name = "%s" + image = "%s" + image_pull_policy = "%s" + command = [ + "sh", + "-c", + "%s", + ] } } } @@ -1275,7 +1442,6 @@ resource "kubernetes_service" "test" { metadata { name = "init-service" } - spec { port { port = 8080 @@ -1283,10 +1449,29 @@ resource "kubernetes_service" "test" { } } } -`, name) + +resource "kubernetes_secret" "test" { + metadata { + name = "test" + } + data = { + "SECRETENV" = "asdf1234" + } } -func testAccKubernetesDeploymentConfig_modified(name string) string { + +resource "kubernetes_config_map" "test" { + metadata { + name = "test" + } + data = { + "ENV" = "somedata" + } +} +`, name, imageName, imageName, memory, envName, initName, imageName1, pullPolicy, initCommand) +} + +func testAccKubernetesDeploymentConfig_modified(name, imageName string) string { return fmt.Sprintf(`resource "kubernetes_deployment" "test" { metadata { annotations = { @@ -1329,10 +1514,10 @@ func testAccKubernetesDeploymentConfig_modified(name string) string { } } } -`, name, defaultNginxImage) +`, name, nginxImageVersion) } -func testAccKubernetesDeploymentConfig_generatedName(prefix string) string { +func testAccKubernetesDeploymentConfig_generatedName(prefix, imageName string) string { return fmt.Sprintf(`resource "kubernetes_deployment" "test" { metadata { labels = { @@ -1371,7 +1556,7 @@ func testAccKubernetesDeploymentConfig_generatedName(prefix string) string { } } } -`, prefix, defaultNginxImage) +`, prefix, nginxImageVersion) } func testAccKubernetesDeploymentConfigWithSecurityContext(deploymentName, imageName string) string { @@ -1867,7 +2052,7 @@ func testAccKubernetesDeploymentConfigWithContainerSecurityContextRunAsGroup(dep container { name = "container2" - image = "busybox" + image = "%s" command = ["sh", "-c", "echo The app is running! && sleep 3600"] security_context { run_as_group = 200 @@ -1878,7 +2063,7 @@ func testAccKubernetesDeploymentConfigWithContainerSecurityContextRunAsGroup(dep } } } -`, deploymentName, imageName) +`, deploymentName, imageName, imageName) } func testAccKubernetesDeploymentConfigWithVolumeMounts(secretName, deploymentName, imageName string) string { @@ -2315,7 +2500,7 @@ func testAccKubernetesDeploymentConfigWithAutomountServiceAccountToken(deploymen `, deploymentName, imageName) } -func testAccKubernetesDeploymentConfigWithWaitForRolloutFalse(deploymentName string) string { +func testAccKubernetesDeploymentConfigWithWaitForRolloutFalse(deploymentName, imageName string) string { return fmt.Sprintf(`resource "kubernetes_deployment" "test" { metadata { name = %q @@ -2354,12 +2539,103 @@ func testAccKubernetesDeploymentConfigWithWaitForRolloutFalse(deploymentName str } wait_for_rollout = false } -`, deploymentName, defaultNginxImage) +`, deploymentName, nginxImageVersion) +} + +func testAccKubernetesDeploymentConfigReleased(provider, name, imageName string) string { + return fmt.Sprintf(`resource "kubernetes_deployment" "test" { + provider = %s + metadata { + annotations = { + TestAnnotationOne = "one" + TestAnnotationTwo = "two" + } + + labels = { + TestLabelOne = "one" + TestLabelTwo = "two" + TestLabelThree = "three" + } + + name = "%s" + } + + spec { + replicas = 5 + + selector { + match_labels = { + TestLabelOne = "one" + TestLabelTwo = "two" + TestLabelThree = "three" + } + } + + template { + metadata { + labels = { + TestLabelOne = "one" + TestLabelTwo = "two" + TestLabelThree = "three" + } + } + + spec { + container { + image = %q + name = "containername" + + port { + container_port = 80 + } + + readiness_probe { + initial_delay_seconds = 5 + http_get { + path = "/" + port = 80 + } + } + + resources { + requests { + memory = "64Mi" + cpu = "50m" + } + limits { + memory = "100Mi" + cpu = "100m" + } + } + env { + name = "LIMITS_CPU" + value_from { + resource_field_ref { + container_name = "containername" + resource = "requests.cpu" + } + } + } + env { + name = "LIMITS_MEM" + value_from { + resource_field_ref { + container_name = "containername" + resource = "requests.memory" + } + } + } + } + } + } + } +} +`, provider, name, imageName) } -func testAccKubernetesDeploymentConfig_regression(provider, name string) string { +func testAccKubernetesDeploymentConfigLocal(provider, name, imageName string) string { return fmt.Sprintf(`resource "kubernetes_deployment" "test" { - provider = "%s" + provider = %s metadata { annotations = { TestAnnotationOne = "one" @@ -2441,7 +2717,7 @@ func testAccKubernetesDeploymentConfig_regression(provider, name string) string } } } -`, provider, name, defaultNginxImage) +`, provider, name, imageName) } func testAccKubernetesDeploymentConfig_ForceNew(secretName, label, deploymentName, imageName string) string { diff --git a/kubernetes/resource_kubernetes_job.go b/kubernetes/resource_kubernetes_job.go index ff2fd1f344..02868f94b7 100644 --- a/kubernetes/resource_kubernetes_job.go +++ b/kubernetes/resource_kubernetes_job.go @@ -52,7 +52,7 @@ func resourceKubernetesJobSchemaV1() map[string]*schema.Schema { Description: "Spec of the job owned by the cluster", Required: true, MaxItems: 1, - ForceNew: true, + ForceNew: false, Elem: &schema.Resource{ Schema: jobSpecFields(), }, @@ -121,6 +121,14 @@ func resourceKubernetesJobUpdate(ctx context.Context, d *schema.ResourceData, me ops := patchMetadata("metadata.0.", "/metadata/", d) + if d.HasChange("spec") { + specOps, err := patchJobSpec("/spec", "spec.0.", d) + if err != nil { + return diag.FromErr(err) + } + ops = append(ops, specOps...) + } + data, err := ops.MarshalJSON() if err != nil { return diag.Errorf("Failed to marshal update operations: %s", err) diff --git a/kubernetes/resource_kubernetes_job_test.go b/kubernetes/resource_kubernetes_job_test.go index cf582bbc10..2775389006 100644 --- a/kubernetes/resource_kubernetes_job_test.go +++ b/kubernetes/resource_kubernetes_job_test.go @@ -27,7 +27,7 @@ func TestAccKubernetesJob_wait_for_completion(t *testing.T) { CheckDestroy: testAccCheckKubernetesJobDestroy, Steps: []resource.TestStep{ { - Config: testAccKubernetesJobConfig_wait_for_completion(name), + Config: testAccKubernetesJobConfig_wait_for_completion(name, busyboxImageVersion), Check: resource.ComposeAggregateTestCheckFunc( // NOTE this is to check that Terraform actually waited for the Job to complete // before considering the Job resource as created @@ -57,6 +57,7 @@ func testAccCheckJobWaited(minDuration time.Duration) func(*terraform.State) err func TestAccKubernetesJob_basic(t *testing.T) { var conf api.Job name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + imageName := alpineImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -66,7 +67,7 @@ func TestAccKubernetesJob_basic(t *testing.T) { CheckDestroy: testAccCheckKubernetesJobDestroy, Steps: []resource.TestStep{ { - Config: testAccKubernetesJobConfig_basic(name), + Config: testAccKubernetesJobConfig_basic(name, imageName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesJobExists("kubernetes_job.test", &conf), resource.TestCheckResourceAttr("kubernetes_job.test", "metadata.0.name", name), @@ -80,12 +81,12 @@ func TestAccKubernetesJob_basic(t *testing.T) { resource.TestCheckResourceAttr("kubernetes_job.test", "spec.0.completions", "10"), resource.TestCheckResourceAttr("kubernetes_job.test", "spec.0.parallelism", "2"), resource.TestCheckResourceAttr("kubernetes_job.test", "spec.0.template.0.spec.0.container.0.name", "hello"), - resource.TestCheckResourceAttr("kubernetes_job.test", "spec.0.template.0.spec.0.container.0.image", "alpine"), + resource.TestCheckResourceAttr("kubernetes_job.test", "spec.0.template.0.spec.0.container.0.image", imageName), resource.TestCheckResourceAttr("kubernetes_job.test", "wait_for_completion", "false"), ), }, { - Config: testAccKubernetesJobConfig_modified(name), + Config: testAccKubernetesJobConfig_modified(name, imageName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesJobExists("kubernetes_job.test", &conf), resource.TestCheckResourceAttr("kubernetes_job.test", "metadata.0.name", name), @@ -102,7 +103,7 @@ func TestAccKubernetesJob_basic(t *testing.T) { resource.TestCheckResourceAttr("kubernetes_job.test", "spec.0.parallelism", "1"), resource.TestCheckResourceAttr("kubernetes_job.test", "spec.0.manual_selector", "true"), resource.TestCheckResourceAttr("kubernetes_job.test", "spec.0.template.0.spec.0.container.0.name", "hello"), - resource.TestCheckResourceAttr("kubernetes_job.test", "spec.0.template.0.spec.0.container.0.image", "alpine"), + resource.TestCheckResourceAttr("kubernetes_job.test", "spec.0.template.0.spec.0.container.0.image", imageName), resource.TestCheckResourceAttr("kubernetes_job.test", "wait_for_completion", "false"), ), }, @@ -110,6 +111,86 @@ func TestAccKubernetesJob_basic(t *testing.T) { }) } +func TestAccKubernetesJob_update(t *testing.T) { + var conf1, conf2, conf3 api.Job + name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + imageName := alpineImageVersion + imageName1 := busyboxImageVersion + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "kubernetes_job.test", + IDRefreshIgnore: []string{"metadata.0.resource_version"}, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckKubernetesJobDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKubernetesJobConfig_basic(name, imageName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesJobExists("kubernetes_job.test", &conf1), + resource.TestCheckResourceAttr("kubernetes_job.test", "metadata.0.name", name), + resource.TestCheckResourceAttr("kubernetes_job.test", "spec.0.active_deadline_seconds", "120"), + resource.TestCheckResourceAttr("kubernetes_job.test", "spec.0.backoff_limit", "10"), + resource.TestCheckResourceAttr("kubernetes_job.test", "spec.0.completions", "10"), + resource.TestCheckResourceAttr("kubernetes_job.test", "spec.0.parallelism", "2"), + resource.TestCheckResourceAttr("kubernetes_job.test", "spec.0.template.0.spec.0.container.0.name", "hello"), + resource.TestCheckResourceAttr("kubernetes_job.test", "spec.0.template.0.spec.0.container.0.image", imageName), + resource.TestCheckResourceAttr("kubernetes_job.test", "wait_for_completion", "false"), + resource.TestCheckResourceAttr("kubernetes_job.test", "spec.0.manual_selector", "false"), + ), + }, + { + Config: testAccKubernetesJobConfig_updateMutableFields(name, imageName, "121", "10", "false", "2"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesJobExists("kubernetes_job.test", &conf2), + resource.TestCheckResourceAttr("kubernetes_job.test", "spec.0.active_deadline_seconds", "121"), + testAccCheckKubernetesJobForceNew(&conf1, &conf2, false), + ), + }, + { + Config: testAccKubernetesJobConfig_updateMutableFields(name, imageName, "121", "11", "false", "2"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesJobExists("kubernetes_job.test", &conf2), + resource.TestCheckResourceAttr("kubernetes_job.test", "spec.0.backoff_limit", "11"), + testAccCheckKubernetesJobForceNew(&conf1, &conf2, false), + ), + }, + { + Config: testAccKubernetesJobConfig_updateMutableFields(name, imageName, "121", "11", "true", "2"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesJobExists("kubernetes_job.test", &conf2), + resource.TestCheckResourceAttr("kubernetes_job.test", "spec.0.manual_selector", "true"), + testAccCheckKubernetesJobForceNew(&conf1, &conf2, false), + ), + }, + { + Config: testAccKubernetesJobConfig_updateMutableFields(name, imageName, "121", "11", "true", "3"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesJobExists("kubernetes_job.test", &conf2), + resource.TestCheckResourceAttr("kubernetes_job.test", "spec.0.parallelism", "3"), + testAccCheckKubernetesJobForceNew(&conf1, &conf2, false), + ), + }, + { + Config: testAccKubernetesJobConfig_updateImmutableFields(name, imageName, "12"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesJobExists("kubernetes_job.test", &conf2), + resource.TestCheckResourceAttr("kubernetes_job.test", "spec.0.completions", "12"), + testAccCheckKubernetesJobForceNew(&conf1, &conf2, true), + ), + }, + { + Config: testAccKubernetesJobConfig_updateImmutableFields(name, imageName1, "12"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesJobExists("kubernetes_job.test", &conf3), + resource.TestCheckResourceAttr("kubernetes_job.test", "spec.0.template.0.spec.0.container.0.image", imageName1), + testAccCheckKubernetesJobForceNew(&conf2, &conf3, true), + ), + }, + }, + }) +} + func TestAccKubernetesJob_ttl_seconds_after_finished(t *testing.T) { var conf api.Job name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) @@ -119,28 +200,36 @@ func TestAccKubernetesJob_ttl_seconds_after_finished(t *testing.T) { IDRefreshName: "kubernetes_job.test", IDRefreshIgnore: []string{ "spec.0.selector.0.match_expressions.#", - "spec.0.selector.0.match_labels.%", - "spec.0.template.0.spec.0.container.0.resources.0.limits.#", - "spec.0.template.0.spec.0.container.0.resources.0.requests.#", }, ProviderFactories: testAccProviderFactories, CheckDestroy: testAccCheckKubernetesJobDestroy, Steps: []resource.TestStep{ { - Config: testAccKubernetesJobConfig_ttl_seconds_after_finished(name), + Config: testAccKubernetesJobConfig_ttl_seconds_after_finished(name, busyboxImageVersion), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesJobExists("kubernetes_job.test", &conf), - // FIXME uncomment this check when the TTLSecondsAfterFinished feature gate defaults to true - // resource.TestCheckResourceAttr("kubernetes_job.test", "spec.0.ttl_seconds_after_finished", "60"), + resource.TestCheckResourceAttr("kubernetes_job.test", "spec.0.ttl_seconds_after_finished", "60"), ), - - // FIXME remove this when TTLSecondsAfterFinished defaults to true - ExpectNonEmptyPlan: true, }, }, }) } +func testAccCheckKubernetesJobForceNew(old, new *api.Job, wantNew bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + if wantNew { + if old.ObjectMeta.UID == new.ObjectMeta.UID { + return fmt.Errorf("Expecting new resource for Job %s", old.ObjectMeta.UID) + } + } else { + if old.ObjectMeta.UID != new.ObjectMeta.UID { + return fmt.Errorf("Expecting Job UIDs to be the same: expected %s got %s", old.ObjectMeta.UID, new.ObjectMeta.UID) + } + } + return nil + } +} + func testAccCheckKubernetesJobDestroy(s *terraform.State) error { conn, err := testAccProvider.Meta().(KubeClientsets).MainClientset() @@ -198,7 +287,7 @@ func testAccCheckKubernetesJobExists(n string, obj *api.Job) resource.TestCheckF } } -func testAccKubernetesJobConfig_basic(name string) string { +func testAccKubernetesJobConfig_basic(name, imageName string) string { return fmt.Sprintf(`resource "kubernetes_job" "test" { metadata { name = "%s" @@ -213,7 +302,57 @@ func testAccKubernetesJobConfig_basic(name string) string { spec { container { name = "hello" - image = "alpine" + image = "%s" + command = ["echo", "'hello'"] + } + } + } + } + + wait_for_completion = false +}`, name, imageName) +} + +func testAccKubernetesJobConfig_updateMutableFields(name, imageName, activeDeadlineSeconds, backoffLimit, manualSelector, parallelism string) string { + return fmt.Sprintf(`resource "kubernetes_job" "test" { + metadata { + name = "%s" + } + spec { + active_deadline_seconds = %s + backoff_limit = %s + completions = 10 + manual_selector = %s + parallelism = %s + template { + metadata {} + spec { + container { + name = "hello" + image = "%s" + command = ["echo", "'hello'"] + } + } + } + } + + wait_for_completion = false +}`, name, activeDeadlineSeconds, backoffLimit, manualSelector, parallelism, imageName) +} + +func testAccKubernetesJobConfig_updateImmutableFields(name, imageName, completions string) string { + return fmt.Sprintf(`resource "kubernetes_job" "test" { + metadata { + name = "%s" + } + spec { + completions = %s + template { + metadata {} + spec { + container { + name = "newname" + image = "%s" command = ["echo", "'hello'"] } } @@ -221,10 +360,10 @@ func testAccKubernetesJobConfig_basic(name string) string { } wait_for_completion = false -}`, name) +}`, name, completions, imageName) } -func testAccKubernetesJobConfig_ttl_seconds_after_finished(name string) string { +func testAccKubernetesJobConfig_ttl_seconds_after_finished(name, imageName string) string { return fmt.Sprintf(`resource "kubernetes_job" "test" { metadata { name = "%s" @@ -239,16 +378,16 @@ func testAccKubernetesJobConfig_ttl_seconds_after_finished(name string) string { spec { container { name = "hello" - image = "alpine" + image = "%s" command = ["echo", "'hello'"] } } } } -}`, name) +}`, name, imageName) } -func testAccKubernetesJobConfig_wait_for_completion(name string) string { +func testAccKubernetesJobConfig_wait_for_completion(name, imageName string) string { return fmt.Sprintf(`resource "kubernetes_job" "test" { metadata { name = "%s" @@ -261,7 +400,7 @@ func testAccKubernetesJobConfig_wait_for_completion(name string) string { spec { container { name = "wait-test" - image = "busybox" + image = "%s" command = ["sleep", "10"] } } @@ -271,10 +410,10 @@ func testAccKubernetesJobConfig_wait_for_completion(name string) string { timeouts { create = "1m" } -}`, name) +}`, name, imageName) } -func testAccKubernetesJobConfig_modified(name string) string { +func testAccKubernetesJobConfig_modified(name, imageName string) string { return fmt.Sprintf(`resource "kubernetes_job" "test" { metadata { name = "%s" @@ -298,12 +437,12 @@ func testAccKubernetesJobConfig_modified(name string) string { spec { container { name = "hello" - image = "alpine" + image = "%s" command = ["echo", "'hello'"] } } } } wait_for_completion = false -}`, name) +}`, name, imageName) } diff --git a/kubernetes/resource_kubernetes_persistent_volume_claim_test.go b/kubernetes/resource_kubernetes_persistent_volume_claim_test.go index d357f66636..9b87ebd47d 100644 --- a/kubernetes/resource_kubernetes_persistent_volume_claim_test.go +++ b/kubernetes/resource_kubernetes_persistent_volume_claim_test.go @@ -433,6 +433,7 @@ func TestAccKubernetesPersistentVolumeClaim_googleCloud_storageClass(t *testing. func TestAccKubernetesPersistentVolumeClaim_expansionGoogleCloud(t *testing.T) { var conf1, conf2 api.PersistentVolumeClaim name := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(10)) + imageName := alpineImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); skipIfNotRunningInGke(t) }, @@ -442,7 +443,7 @@ func TestAccKubernetesPersistentVolumeClaim_expansionGoogleCloud(t *testing.T) { CheckDestroy: testAccCheckKubernetesPersistentVolumeClaimDestroy, Steps: []resource.TestStep{ { // GKE specific check -- initial create. - Config: testAccKubernetesPersistentVolumeClaimConfig_updateStorageGKE(name, "1Gi"), + Config: testAccKubernetesPersistentVolumeClaimConfig_updateStorageGKE(name, "1Gi", imageName), SkipFunc: func() (bool, error) { isInGke, err := isRunningInGke() return !isInGke, err @@ -454,7 +455,7 @@ func TestAccKubernetesPersistentVolumeClaim_expansionGoogleCloud(t *testing.T) { ), }, { // GKE specific check -- Update -- storage is increased in place. - Config: testAccKubernetesPersistentVolumeClaimConfig_updateStorageGKE(name, "2Gi"), + Config: testAccKubernetesPersistentVolumeClaimConfig_updateStorageGKE(name, "2Gi", imageName), SkipFunc: func() (bool, error) { isInGke, err := isRunningInGke() return !isInGke, err @@ -748,7 +749,7 @@ resource "kubernetes_persistent_volume_claim" "test" { `, name, requests, limits) } -func testAccKubernetesPersistentVolumeClaimConfig_updateStorageGKE(name, requests string) string { +func testAccKubernetesPersistentVolumeClaimConfig_updateStorageGKE(name, requests, imageName string) string { return fmt.Sprintf(`resource "kubernetes_storage_class" "test" { metadata { name = "test" @@ -778,7 +779,7 @@ resource "kubernetes_pod" "main" { spec { container { name = "default" - image = "alpine:latest" + image = "%s" command = ["sleep", "3600s"] volume_mount { mount_path = "/etc/test" @@ -793,7 +794,7 @@ resource "kubernetes_pod" "main" { } } } -`, name, requests) +`, name, requests, imageName) } func testAccKubernetesPersistentVolumeClaimConfig_metaModified(name string) string { diff --git a/kubernetes/resource_kubernetes_pod.go b/kubernetes/resource_kubernetes_pod.go index 4de4fcde7d..c7f0b6549b 100644 --- a/kubernetes/resource_kubernetes_pod.go +++ b/kubernetes/resource_kubernetes_pod.go @@ -50,7 +50,7 @@ func resourceKubernetesPodSchemaV1() map[string]*schema.Schema { Required: true, MaxItems: 1, Elem: &schema.Resource{ - Schema: podSpecFields(false, false, false), + Schema: podSpecFields(false, false), }, }, } diff --git a/kubernetes/resource_kubernetes_pod_test.go b/kubernetes/resource_kubernetes_pod_test.go index dfcc873c1f..e5ac1d94bb 100644 --- a/kubernetes/resource_kubernetes_pod_test.go +++ b/kubernetes/resource_kubernetes_pod_test.go @@ -14,16 +14,38 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) +func TestAccKubernetesPod_minimal(t *testing.T) { + name := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckKubernetesPodDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKubernetesPodConfigMinimal(name, busyboxImageVersion), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet("kubernetes_pod.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_pod.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_pod.test", "metadata.0.uid"), + ), + }, + { + Config: testAccKubernetesPodConfigMinimal(name, busyboxImageVersion), + PlanOnly: true, + }, + }, + }) +} + func TestAccKubernetesPod_basic(t *testing.T) { var conf1 api.Pod - var conf2 api.Pod podName := acctest.RandomWithPrefix("tf-acc-test") secretName := acctest.RandomWithPrefix("tf-acc-test") configMapName := acctest.RandomWithPrefix("tf-acc-test") - imageName1 := "nginx:1.7.9" - imageName2 := "nginx:1.11" + imageName1 := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -66,14 +88,6 @@ func TestAccKubernetesPod_basic(t *testing.T) { ImportStateVerify: true, ImportStateVerifyIgnore: []string{"metadata.0.resource_version"}, }, - { - Config: testAccKubernetesPodConfigBasic(secretName, configMapName, podName, imageName2), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckKubernetesPodExists("kubernetes_pod.test", &conf2), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.container.0.image", imageName2), - testAccCheckKubernetesPodForceNew(&conf1, &conf2, false), - ), - }, }, }) } @@ -82,8 +96,8 @@ func TestAccKubernetesPod_initContainer_updateForcesNew(t *testing.T) { var conf1, conf2 api.Pod podName := acctest.RandomWithPrefix("tf-acc-test") - image1 := "busybox:1.27" - image2 := "busybox:1.28" + image := busyboxImageVersion + image1 := busyboxImageVersion1 resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -91,52 +105,23 @@ func TestAccKubernetesPod_initContainer_updateForcesNew(t *testing.T) { CheckDestroy: testAccCheckKubernetesPodDestroy, Steps: []resource.TestStep{ { - Config: testAccKubernetesPodConfigWithInitContainer(podName, image1), + Config: testAccKubernetesPodConfigWithInitContainer(podName, image), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesPodExists("kubernetes_pod.test", &conf1), - resource.TestCheckResourceAttr("kubernetes_pod.test", "metadata.0.annotations.%", "0"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "metadata.0.labels.%", "1"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "metadata.0.labels.app", "pod_label"), resource.TestCheckResourceAttr("kubernetes_pod.test", "metadata.0.name", podName), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.init_container.0.name", "install"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.init_container.0.image", image1), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.init_container.0.command.0", "wget"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.init_container.0.command.1", "-O"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.init_container.0.command.2", "/work-dir/index.html"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.init_container.0.command.3", "http://kubernetes.io"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.init_container.0.volume_mount.0.name", "workdir"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.init_container.0.volume_mount.0.mount_path", "/work-dir"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.dns_config.#", "1"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.dns_config.0.nameservers.#", "3"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.dns_config.0.nameservers.0", "1.1.1.1"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.dns_config.0.nameservers.1", "8.8.8.8"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.dns_config.0.nameservers.2", "9.9.9.9"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.dns_config.0.searches.#", "1"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.dns_config.0.searches.0", "kubernetes.io"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.dns_config.0.option.#", "2"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.dns_config.0.option.0.name", "ndots"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.dns_config.0.option.0.value", "1"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.dns_config.0.option.1.name", "use-vc"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.dns_config.0.option.1.value", ""), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.dns_policy", "Default"), + resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.container.0.name", "container"), + resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.init_container.0.name", "initcontainer"), + resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.init_container.0.image", image), ), }, { - Config: testAccKubernetesPodConfigWithInitContainer(podName, image2), + Config: testAccKubernetesPodConfigWithInitContainer(podName, image1), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesPodExists("kubernetes_pod.test", &conf2), - resource.TestCheckResourceAttr("kubernetes_pod.test", "metadata.0.annotations.%", "0"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "metadata.0.labels.%", "1"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "metadata.0.labels.app", "pod_label"), resource.TestCheckResourceAttr("kubernetes_pod.test", "metadata.0.name", podName), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.init_container.0.name", "install"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.init_container.0.image", image2), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.init_container.0.command.0", "wget"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.init_container.0.command.1", "-O"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.init_container.0.command.2", "/work-dir/index.html"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.init_container.0.command.3", "http://kubernetes.io"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.init_container.0.volume_mount.0.name", "workdir"), - resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.init_container.0.volume_mount.0.mount_path", "/work-dir"), + resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.container.0.name", "container"), + resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.init_container.0.name", "initcontainer"), + resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.init_container.0.image", image1), testAccCheckKubernetesPodForceNew(&conf1, &conf2, true), ), }, @@ -256,7 +241,7 @@ func TestAccKubernetesPod_with_pod_security_context(t *testing.T) { var conf api.Pod podName := acctest.RandomWithPrefix("tf-acc-test") - imageName := "nginx:1.7.9" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -282,7 +267,7 @@ func TestAccKubernetesPod_with_pod_security_context_run_as_group(t *testing.T) { var conf api.Pod podName := acctest.RandomWithPrefix("tf-acc-test") - imageName := "nginx:1.7.9" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); skipIfUnsupportedSecurityContextRunAsGroup(t) }, @@ -424,7 +409,7 @@ func TestAccKubernetesPod_with_container_security_context(t *testing.T) { var conf api.Pod podName := acctest.RandomWithPrefix("tf-acc-test") - imageName := "nginx:1.7.9" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -456,7 +441,7 @@ func TestAccKubernetesPod_with_volume_mount(t *testing.T) { podName := acctest.RandomWithPrefix("tf-acc-test") secretName := acctest.RandomWithPrefix("tf-acc-test") - imageName := "nginx:1.7.9" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -485,7 +470,7 @@ func TestAccKubernetesPod_with_cfg_map_volume_mount(t *testing.T) { podName := acctest.RandomWithPrefix("tf-acc-test") cfgMap := acctest.RandomWithPrefix("tf-acc-test") - imageName := "busybox:1.30.1" + imageName := busyboxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -522,7 +507,7 @@ func TestAccKubernetesPod_with_projected_volume(t *testing.T) { cfgMap2Name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) secretName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) podName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := "busybox:1.32" + imageName := busyboxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -564,7 +549,7 @@ func TestAccKubernetesPod_with_resource_requirements(t *testing.T) { var conf api.Pod podName := acctest.RandomWithPrefix("tf-acc-test") - imageName := "nginx:1.7.9" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -592,7 +577,7 @@ func TestAccKubernetesPod_with_empty_dir_volume(t *testing.T) { var conf api.Pod podName := acctest.RandomWithPrefix("tf-acc-test") - imageName := "nginx:1.7.9" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -618,7 +603,7 @@ func TestAccKubernetesPod_with_empty_dir_volume_with_sizeLimit(t *testing.T) { var conf api.Pod podName := acctest.RandomWithPrefix("tf-acc-test") - imageName := "nginx:1.7.9" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -646,7 +631,7 @@ func TestAccKubernetesPod_with_secret_vol_items(t *testing.T) { secretName := acctest.RandomWithPrefix("tf-acc-test") podName := acctest.RandomWithPrefix("tf-acc-test") - imageName := "nginx:1.7.9" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -671,7 +656,7 @@ func TestAccKubernetesPod_gke_with_nodeSelector(t *testing.T) { var conf api.Pod podName := acctest.RandomWithPrefix("tf-acc-test") - imageName := "nginx:1.7.9" + imageName := nginxImageVersion region := os.Getenv("GOOGLE_REGION") resource.Test(t, resource.TestCase{ @@ -698,7 +683,7 @@ func TestAccKubernetesPod_config_with_automount_service_account_token(t *testing podName := acctest.RandomWithPrefix("tf-acc-test") saName := acctest.RandomWithPrefix("tf-acc-test") - imageName := "nginx:1.7.9" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -725,7 +710,7 @@ func TestAccKubernetesPod_config_container_working_dir(t *testing.T) { var confPod api.Pod podName := acctest.RandomWithPrefix("tf-acc-test") - imageName := "nginx:1.7.9" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -756,7 +741,7 @@ func TestAccKubernetesPod_config_container_startup_probe(t *testing.T) { var confPod api.Pod podName := acctest.RandomWithPrefix("tf-acc-test") - imageName := "nginx:1.7.9" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -785,7 +770,7 @@ func TestAccKubernetesPod_termination_message_policy_default(t *testing.T) { var confPod api.Pod podName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := "nginx:1.7.9" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -809,7 +794,7 @@ func TestAccKubernetesPod_termination_message_policy_override_as_file(t *testing var confPod api.Pod podName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := "nginx:1.7.9" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -833,7 +818,7 @@ func TestAccKubernetesPod_termination_message_policy_override_as_fallback_to_log var confPod api.Pod podName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := "nginx:1.7.9" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -857,7 +842,7 @@ func TestAccKubernetesPod_enableServiceLinks(t *testing.T) { var conf1 api.Pod rName := acctest.RandomWithPrefix("tf-acc-test") - imageName := "nginx:1.7.9" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -883,13 +868,66 @@ func TestAccKubernetesPod_enableServiceLinks(t *testing.T) { }) } +func TestAccKubernetesPod_bug961EmptyBlocks(t *testing.T) { + name := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckKubernetesPodDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKubernetesPodConfigEmptyBlocks(name, busyboxImageVersion), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet("kubernetes_pod.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_pod.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_pod.test", "metadata.0.uid"), + resource.TestCheckResourceAttrSet("kubernetes_pod.test", "metadata.0.uid"), + ), + }, + { + Config: testAccKubernetesPodConfigMinimal(name, busyboxImageVersion), + PlanOnly: true, + }, + }, + }) +} + +func TestAccKubernetesPod_bug1085(t *testing.T) { + name := acctest.RandomWithPrefix("tf-acc-test") + imageName := alpineImageVersion + var conf api.Pod + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckKubernetesPodDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKubernetesPodConfigWithVolume(name, imageName, ""), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesPodExists("kubernetes_pod.test", &conf), + resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.service_account_name", "default"), + ), + }, + { + Config: testAccKubernetesPodConfigWithVolume(name, imageName, `service_account_name="test"`), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesPodExists("kubernetes_pod.test", &conf), + resource.TestCheckResourceAttr("kubernetes_pod.test", "spec.0.service_account_name", "test"), + ), + }, + }, + }) +} + func TestAccKubernetesPod_readinessGate(t *testing.T) { var conf1 api.Pod podName := acctest.RandomWithPrefix("tf-acc-test") secretName := acctest.RandomWithPrefix("tf-acc-test") configMapName := acctest.RandomWithPrefix("tf-acc-test") - imageName1 := "nginx:1.7.9" + imageName1 := nginxImageVersion1 resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -965,7 +1003,7 @@ func TestAccKubernetesPod_regression(t *testing.T) { var conf1, conf2 api.Pod name := acctest.RandomWithPrefix("tf-acc-test") - imageName := "nginx:1.7.9" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -1204,67 +1242,55 @@ resource "kubernetes_pod" "test" { `, secretName, secretName, configMapName, configMapName, podName, imageName) } -func testAccKubernetesPodConfigWithInitContainer(podName string, image string) string { +func testAccKubernetesPodConfigWithInitContainer(podName, image string) string { return fmt.Sprintf(`resource "kubernetes_pod" "test" { metadata { - labels = { - app = "pod_label" - } - name = "%s" } spec { - automount_service_account_token = false - - container { - name = "nginx" - image = "nginx" - - port { - container_port = 80 - } - - volume_mount { - name = "workdir" - mount_path = "/usr/share/nginx/html" - } - } + automount_service_account_token = false + container { + name = "container" + image = "%s" + command = ["sh", "-c", "echo The app is running! && sleep 3600"] + + resources { + requests = { + memory = "64Mi" + cpu = "50m" + } + } + } init_container { - name = "install" + name = "initcontainer" image = "%s" - command = ["wget", "-O", "/work-dir/index.html", "http://kubernetes.io"] + command = ["sh", "-c", "until nslookup init-service.default.svc.cluster.local; do echo waiting for init-service; sleep 2; done"] - volume_mount { - name = "workdir" - mount_path = "/work-dir" - } - } - - dns_config { - nameservers = ["1.1.1.1", "8.8.8.8", "9.9.9.9"] - searches = ["kubernetes.io"] - - option { - name = "ndots" - value = 1 - } - - option { - name = "use-vc" + resources { + requests = { + memory = "64Mi" + cpu = "50m" + } } } + } +} - dns_policy = "Default" +resource "kubernetes_service" "test" { + metadata { + name = "init-service" + } - volume { - name = "workdir" - empty_dir {} + spec { + port { + port = 8080 + target_port = 80 } } } -`, podName, image) +`, podName, image, image) } func testAccKubernetesPodConfigWithSecurityContext(podName, imageName string) string { @@ -2346,3 +2372,128 @@ resource "kubernetes_config_map" "test_from" { } `, provider, name, imageName) } + +func testAccKubernetesPodConfigMinimal(name, imageName string) string { + return fmt.Sprintf(`resource "kubernetes_pod" "test" { + metadata { + name = "%s" + } + spec { + container { + image = "%s" + name = "containername" + } + } +} +`, name, imageName) +} + +func testAccKubernetesPodConfigEmptyBlocks(name, imageName string) string { + return fmt.Sprintf(`resource "kubernetes_pod" "test" { + metadata { + labels = { + app = "pod_label" + } + + name = "%s" + } + + spec { + container { + image = "%s" + name = "containername" + + env {} + env_from { + config_map_ref {} + } + env_from { + secret_ref {} + } + env_from {} + } + volume { + name = "empty" + secret {} + } + volume {} + } +} +`, name, imageName) +} + +func testAccKubernetesPodConfigWithVolume(name, imageName, serviceAccount string) string { + return fmt.Sprintf(`resource "kubernetes_storage_class" "test" { + metadata { + name = "test" + } + storage_provisioner = "k8s.io/minikube-hostpath" +} + +resource "kubernetes_service_account" "test" { + metadata { + name = "test" + } +} + +resource "kubernetes_persistent_volume" "test" { + metadata { + name = "test" + } + spec { + capacity = { + storage = "1Gi" + } + access_modes = ["ReadWriteOnce"] + storage_class_name = kubernetes_storage_class.test.metadata.0.name + persistent_volume_source { + host_path { + path = "/mnt/minikube" + type = "DirectoryOrCreate" + } + } + } +} + +resource "kubernetes_persistent_volume_claim" "test" { + wait_until_bound = false + metadata { + name = "test" + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = kubernetes_storage_class.test.metadata.0.name + volume_name = kubernetes_persistent_volume.test.metadata.0.name + resources { + requests = { + storage = "1G" + } + } + } +} + +resource "kubernetes_pod" "test" { + metadata { + name = "%s" + } + spec { + %s + container { + name = "default" + image = "%s" + command = ["sleep", "3600s"] + volume_mount { + mount_path = "/etc/test" + name = "pvc" + } + } + volume { + name = "pvc" + persistent_volume_claim { + claim_name = kubernetes_persistent_volume_claim.test.metadata[0].name + } + } + } +} +`, name, serviceAccount, imageName) +} diff --git a/kubernetes/resource_kubernetes_replication_controller.go b/kubernetes/resource_kubernetes_replication_controller.go index a210915869..470f098ec0 100644 --- a/kubernetes/resource_kubernetes_replication_controller.go +++ b/kubernetes/resource_kubernetes_replication_controller.go @@ -76,40 +76,23 @@ func resourceKubernetesReplicationController() *schema.Resource { func replicationControllerTemplateFieldSpec() map[string]*schema.Schema { metadata := namespacedMetadataSchemaIsTemplate("replication controller's template", true, true) - // TODO: make this required once the legacy fields are removed - metadata.Computed = true - metadata.Required = false - metadata.Optional = true + metadata.Required = true templateFields := map[string]*schema.Schema{ "metadata": metadata, "spec": { Type: schema.TypeList, Description: "Spec of the pods managed by the replication controller", - Optional: true, // TODO: make this required once the legacy fields are removed - Computed: true, + Required: true, MaxItems: 1, Elem: &schema.Resource{ - Schema: podSpecFields(false, false, true), + Schema: podSpecFields(false, true), }, }, } - - // Merge deprecated fields and mark them conflicting with the ones to avoid complex mixed use-cases - for k, v := range podSpecFields(true, true, true) { - v.ConflictsWith = []string{"spec.0.template.0.spec", "spec.0.template.0.metadata"} - templateFields[k] = v - } - return templateFields } -func useDeprecatedSpecFields(d *schema.ResourceData) (deprecatedSpecFieldsExist bool) { - // Check which replication controller template spec fields are used - _, deprecatedSpecFieldsExist = d.GetOkExists("spec.0.template.0.container.0.name") - return -} - func resourceKubernetesReplicationControllerCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn, err := meta.(KubeClientsets).MainClientset() if err != nil { @@ -118,7 +101,7 @@ func resourceKubernetesReplicationControllerCreate(ctx context.Context, d *schem metadata := expandMetadata(d.Get("metadata").([]interface{})) - spec, err := expandReplicationControllerSpec(d.Get("spec").([]interface{}), useDeprecatedSpecFields(d)) + spec, err := expandReplicationControllerSpec(d.Get("spec").([]interface{})) if err != nil { return diag.FromErr(err) } @@ -184,7 +167,7 @@ func resourceKubernetesReplicationControllerRead(ctx context.Context, d *schema. return diag.FromErr(err) } - spec, err := flattenReplicationControllerSpec(rc.Spec, d, useDeprecatedSpecFields(d)) + spec, err := flattenReplicationControllerSpec(rc.Spec, d) if err != nil { return diag.FromErr(err) } @@ -211,7 +194,7 @@ func resourceKubernetesReplicationControllerUpdate(ctx context.Context, d *schem ops := patchMetadata("metadata.0.", "/metadata/", d) if d.HasChange("spec") { - spec, err := expandReplicationControllerSpec(d.Get("spec").([]interface{}), useDeprecatedSpecFields(d)) + spec, err := expandReplicationControllerSpec(d.Get("spec").([]interface{})) if err != nil { return diag.FromErr(err) } @@ -281,6 +264,22 @@ func resourceKubernetesReplicationControllerDelete(ctx context.Context, d *schem return diag.FromErr(err) } + // Wait for Delete to finish. Necessary for ForceNew operations. + err = resource.RetryContext(ctx, d.Timeout(schema.TimeoutDelete), func() *resource.RetryError { + _, err := conn.CoreV1().ReplicationControllers(namespace).Get(ctx, name, metav1.GetOptions{}) + if err != nil { + if statusErr, ok := err.(*errors.StatusError); ok && statusErr.ErrStatus.Code == 404 { + return nil + } + return resource.NonRetryableError(err) + } + + e := fmt.Errorf("Replication Controller (%s) still exists", d.Id()) + return resource.RetryableError(e) + }) + if err != nil { + return diag.FromErr(err) + } log.Printf("[INFO] Replication controller %s deleted", name) d.SetId("") diff --git a/kubernetes/resource_kubernetes_replication_controller_deprecated_test.go b/kubernetes/resource_kubernetes_replication_controller_deprecated_test.go deleted file mode 100644 index 9515892791..0000000000 --- a/kubernetes/resource_kubernetes_replication_controller_deprecated_test.go +++ /dev/null @@ -1,844 +0,0 @@ -package kubernetes - -import ( - "fmt" - "regexp" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - api "k8s.io/api/core/v1" -) - -func TestAccKubernetesReplicationController_deprecated_basic(t *testing.T) { - var conf api.ReplicationController - name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - resourceName := "kubernetes_replication_controller.test" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - IDRefreshName: resourceName, - ProviderFactories: testAccProviderFactories, - CheckDestroy: testAccCheckKubernetesReplicationControllerDestroy, - Steps: []resource.TestStep{ - { - Config: testAccKubernetesReplicationControllerConfig_deprecated_basic(name), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckKubernetesReplicationControllerExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "metadata.0.annotations.%", "2"), - resource.TestCheckResourceAttr(resourceName, "metadata.0.annotations.TestAnnotationOne", "one"), - resource.TestCheckResourceAttr(resourceName, "metadata.0.annotations.TestAnnotationTwo", "two"), - //testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{"TestAnnotationOne": "one", "TestAnnotationTwo": "two"}), - resource.TestCheckResourceAttr(resourceName, "metadata.0.labels.%", "3"), - resource.TestCheckResourceAttr(resourceName, "metadata.0.labels.TestLabelOne", "one"), - resource.TestCheckResourceAttr(resourceName, "metadata.0.labels.TestLabelTwo", "two"), - resource.TestCheckResourceAttr(resourceName, "metadata.0.labels.TestLabelThree", "three"), - //testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{"TestLabelOne": "one", "TestLabelTwo": "two", "TestLabelThree": "three"}), - resource.TestCheckResourceAttr(resourceName, "metadata.0.name", name), - resource.TestCheckResourceAttrSet(resourceName, "metadata.0.generation"), - resource.TestCheckResourceAttrSet(resourceName, "metadata.0.resource_version"), - resource.TestCheckResourceAttrSet(resourceName, "metadata.0.self_link"), - resource.TestCheckResourceAttrSet(resourceName, "metadata.0.uid"), - resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.container.0.image", "nginx:1.7.8"), - resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.container.0.name", "tf-acc-test"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: false, - }, - { - Config: testAccKubernetesReplicationControllerConfig_deprecated_modified(name), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckKubernetesReplicationControllerExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "metadata.0.annotations.%", "2"), - resource.TestCheckResourceAttr(resourceName, "metadata.0.annotations.TestAnnotationOne", "one"), - resource.TestCheckResourceAttr(resourceName, "metadata.0.annotations.Different", "1234"), - //testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{"TestAnnotationOne": "one", "Different": "1234"}), - resource.TestCheckResourceAttr(resourceName, "metadata.0.labels.%", "2"), - resource.TestCheckResourceAttr(resourceName, "metadata.0.labels.TestLabelOne", "one"), - resource.TestCheckResourceAttr(resourceName, "metadata.0.labels.TestLabelThree", "three"), - //testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{"TestLabelOne": "one", "TestLabelThree": "three"}), - resource.TestCheckResourceAttr(resourceName, "metadata.0.name", name), - resource.TestCheckResourceAttrSet(resourceName, "metadata.0.generation"), - resource.TestCheckResourceAttrSet(resourceName, "metadata.0.resource_version"), - resource.TestCheckResourceAttrSet(resourceName, "metadata.0.self_link"), - resource.TestCheckResourceAttrSet(resourceName, "metadata.0.uid"), - resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.container.0.image", "nginx:1.7.9"), - resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.container.0.name", "tf-acc-test"), - ), - }, - }, - }) -} - -func TestAccKubernetesReplicationController_deprecated_initContainer(t *testing.T) { - var conf api.ReplicationController - name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - IDRefreshName: "kubernetes_replication_controller.test", - ProviderFactories: testAccProviderFactories, - CheckDestroy: testAccCheckKubernetesReplicationControllerDestroy, - Steps: []resource.TestStep{ - { - Config: testAccKubernetesReplicationControllerConfig_deprecated_initContainer(name), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckKubernetesReplicationControllerExists("kubernetes_replication_controller.test", &conf), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.init_container.0.image", "busybox"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.init_container.0.name", "install"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.init_container.0.command.0", "wget"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.init_container.0.command.1", "-O"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.init_container.0.command.2", "/work-dir/index.html"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.init_container.0.command.3", "http://kubernetes.io"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.init_container.0.volume_mount.0.name", "workdir"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.init_container.0.volume_mount.0.mount_path", "/work-dir"), - ), - }, - }, - }) -} - -func TestAccKubernetesReplicationController_deprecated_generatedName(t *testing.T) { - var conf api.ReplicationController - prefix := "tf-acc-test-gen-" - resourceName := "kubernetes_replication_controller.test" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - IDRefreshName: resourceName, - ProviderFactories: testAccProviderFactories, - CheckDestroy: testAccCheckKubernetesReplicationControllerDestroy, - Steps: []resource.TestStep{ - { - Config: testAccKubernetesReplicationControllerConfig_deprecated_generatedName(prefix), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckKubernetesReplicationControllerExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "metadata.0.annotations.%", "0"), - //testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{}), - resource.TestCheckResourceAttr(resourceName, "metadata.0.labels.%", "3"), - //testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{"TestLabelOne": "one", "TestLabelTwo": "two", "TestLabelThree": "three"}), - resource.TestCheckResourceAttr(resourceName, "metadata.0.generate_name", prefix), - resource.TestMatchResourceAttr(resourceName, "metadata.0.name", regexp.MustCompile("^"+prefix)), - resource.TestCheckResourceAttrSet(resourceName, "metadata.0.generation"), - resource.TestCheckResourceAttrSet(resourceName, "metadata.0.resource_version"), - resource.TestCheckResourceAttrSet(resourceName, "metadata.0.self_link"), - resource.TestCheckResourceAttrSet(resourceName, "metadata.0.uid"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: false, - }, - }, - }) -} - -func TestAccKubernetesReplicationController_deprecated_with_security_context(t *testing.T) { - var conf api.ReplicationController - - rcName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := "nginx:1.7.9" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviderFactories, - CheckDestroy: testAccCheckKubernetesReplicationControllerDestroy, - Steps: []resource.TestStep{ - { - Config: testAccKubernetesReplicationControllerConfig_deprecated_WithSecurityContext(rcName, imageName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckKubernetesReplicationControllerExists("kubernetes_replication_controller.test", &conf), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.security_context.0.fs_group", "100"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.security_context.0.run_as_non_root", "true"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.security_context.0.run_as_user", "101"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.security_context.0.supplemental_groups.#", "1"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.security_context.0.supplemental_groups.0", "101"), - ), - }, - }, - }) -} - -func TestAccKubernetesReplicationController_deprecated_with_container_liveness_probe_using_exec(t *testing.T) { - var conf api.ReplicationController - - rcName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := "gcr.io/google_containers/busybox" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviderFactories, - CheckDestroy: testAccCheckKubernetesReplicationControllerDestroy, - Steps: []resource.TestStep{ - { - Config: testAccKubernetesReplicationControllerConfig_deprecated_WithLivenessProbeUsingExec(rcName, imageName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckKubernetesReplicationControllerExists("kubernetes_replication_controller.test", &conf), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.args.#", "3"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.liveness_probe.#", "1"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.liveness_probe.0.exec.#", "1"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.liveness_probe.0.exec.0.command.#", "2"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.liveness_probe.0.exec.0.command.0", "cat"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.liveness_probe.0.exec.0.command.1", "/tmp/healthy"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.liveness_probe.0.failure_threshold", "3"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.liveness_probe.0.initial_delay_seconds", "5"), - ), - }, - }, - }) -} - -func TestAccKubernetesReplicationController_deprecated_with_container_liveness_probe_using_http_get(t *testing.T) { - var conf api.ReplicationController - - rcName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := "gcr.io/google_containers/liveness" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviderFactories, - CheckDestroy: testAccCheckKubernetesReplicationControllerDestroy, - Steps: []resource.TestStep{ - { - Config: testAccKubernetesReplicationControllerConfig_deprecated_WithLivenessProbeUsingHTTPGet(rcName, imageName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckKubernetesReplicationControllerExists("kubernetes_replication_controller.test", &conf), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.args.#", "1"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.liveness_probe.#", "1"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.liveness_probe.0.http_get.#", "1"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.liveness_probe.0.http_get.0.path", "/healthz"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.liveness_probe.0.http_get.0.port", "8080"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.liveness_probe.0.http_get.0.http_header.#", "1"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.liveness_probe.0.http_get.0.http_header.0.name", "X-Custom-Header"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.liveness_probe.0.http_get.0.http_header.0.value", "Awesome"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.liveness_probe.0.initial_delay_seconds", "3"), - ), - }, - }, - }) -} - -func TestAccKubernetesReplicationController_deprecated_with_container_liveness_probe_using_tcp(t *testing.T) { - var conf api.ReplicationController - - rcName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := "gcr.io/google_containers/liveness" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviderFactories, - CheckDestroy: testAccCheckKubernetesReplicationControllerDestroy, - Steps: []resource.TestStep{ - { - Config: testAccKubernetesReplicationControllerConfig_deprecated_WithLivenessProbeUsingTCP(rcName, imageName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckKubernetesReplicationControllerExists("kubernetes_replication_controller.test", &conf), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.args.#", "1"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.liveness_probe.#", "1"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.liveness_probe.0.tcp_socket.#", "1"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.liveness_probe.0.tcp_socket.0.port", "8080"), - ), - }, - }, - }) -} - -func TestAccKubernetesReplicationController_deprecated_with_container_lifecycle(t *testing.T) { - var conf api.ReplicationController - - rcName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := "gcr.io/google_containers/liveness" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviderFactories, - CheckDestroy: testAccCheckKubernetesReplicationControllerDestroy, - Steps: []resource.TestStep{ - { - Config: testAccKubernetesReplicationControllerConfig_deprecated_WithLifeCycle(rcName, imageName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckKubernetesReplicationControllerExists("kubernetes_replication_controller.test", &conf), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.lifecycle.#", "1"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.lifecycle.0.post_start.#", "1"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.lifecycle.0.post_start.0.exec.#", "1"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.lifecycle.0.post_start.0.exec.0.command.#", "2"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.lifecycle.0.post_start.0.exec.0.command.0", "ls"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.lifecycle.0.post_start.0.exec.0.command.1", "-al"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.lifecycle.0.pre_stop.#", "1"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.lifecycle.0.pre_stop.0.exec.#", "1"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.lifecycle.0.pre_stop.0.exec.0.command.#", "1"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.lifecycle.0.pre_stop.0.exec.0.command.0", "date"), - ), - }, - }, - }) -} - -func TestAccKubernetesReplicationController_deprecated_with_container_security_context(t *testing.T) { - var conf api.ReplicationController - - rcName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := "nginx:1.7.9" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviderFactories, - CheckDestroy: testAccCheckKubernetesReplicationControllerDestroy, - Steps: []resource.TestStep{ - { - Config: testAccKubernetesReplicationControllerConfig_deprecated_WithContainerSecurityContext(rcName, imageName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckKubernetesReplicationControllerExists("kubernetes_replication_controller.test", &conf), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.security_context.#", "1"), - ), - }, - }, - }) -} - -func TestAccKubernetesReplicationController_deprecated_with_volume_mount(t *testing.T) { - var conf api.ReplicationController - - rcName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - secretName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - - imageName := "nginx:1.7.9" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviderFactories, - CheckDestroy: testAccCheckKubernetesReplicationControllerDestroy, - Steps: []resource.TestStep{ - { - Config: testAccKubernetesReplicationControllerConfig_deprecated_WithVolumeMounts(secretName, rcName, imageName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckKubernetesReplicationControllerExists("kubernetes_replication_controller.test", &conf), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.image", imageName), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.volume_mount.#", "1"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.volume_mount.0.mount_path", "/tmp/my_path"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.volume_mount.0.name", "db"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.volume_mount.0.read_only", "false"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.volume_mount.0.sub_path", ""), - ), - }, - }, - }) -} - -func TestAccKubernetesReplicationController_deprecated_with_resource_requirements(t *testing.T) { - var conf api.ReplicationController - - rcName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - - imageName := "nginx:1.7.9" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviderFactories, - CheckDestroy: testAccCheckKubernetesReplicationControllerDestroy, - Steps: []resource.TestStep{ - { - Config: testAccKubernetesReplicationControllerConfig_deprecated_WithResourceRequirements(rcName, imageName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckKubernetesReplicationControllerExists("kubernetes_replication_controller.test", &conf), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.image", imageName), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.resources.0.requests.0.memory", "50Mi"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.resources.0.requests.0.cpu", "250m"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.resources.0.limits.0.memory", "512Mi"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.resources.0.limits.0.cpu", "500m"), - ), - }, - }, - }) -} - -func TestAccKubernetesReplicationController_deprecated_with_empty_dir_volume(t *testing.T) { - var conf api.ReplicationController - - rcName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := "nginx:1.7.9" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviderFactories, - CheckDestroy: testAccCheckKubernetesReplicationControllerDestroy, - Steps: []resource.TestStep{ - { - Config: testAccKubernetesReplicationControllerConfig_deprecated_WithEmptyDirVolumes(rcName, imageName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckKubernetesReplicationControllerExists("kubernetes_replication_controller.test", &conf), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.image", imageName), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.volume_mount.#", "1"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.volume_mount.0.mount_path", "/cache"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.container.0.volume_mount.0.name", "cache-volume"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.volume.0.empty_dir.0.medium", "Memory"), - ), - }, - }, - }) -} - -func testAccKubernetesReplicationControllerConfig_deprecated_basic(name string) string { - return fmt.Sprintf(`resource "kubernetes_replication_controller" "test" { - metadata { - annotations = { - TestAnnotationOne = "one" - TestAnnotationTwo = "two" - } - - labels = { - TestLabelOne = "one" - TestLabelTwo = "two" - TestLabelThree = "three" - } - - name = "%s" - } - - spec { - replicas = 1000 # This is intentionally high to exercise the waiter - - selector = { - TestLabelOne = "one" - TestLabelTwo = "two" - TestLabelThree = "three" - } - - template { - container { - image = "nginx:1.7.8" - name = "tf-acc-test" - } - } - } -} -`, name) -} - -func testAccKubernetesReplicationControllerConfig_deprecated_initContainer(name string) string { - return fmt.Sprintf(`resource "kubernetes_replication_controller" "test" { - metadata { - annotations = { - TestAnnotationOne = "one" - TestAnnotationTwo = "two" - } - labels = { - TestLabelOne = "one" - TestLabelTwo = "two" - TestLabelThree = "three" - } - name = "%s" - } - spec { - replicas = 1000 # This is intentionally high to exercise the waiter - selector = { - TestLabelOne = "one" - TestLabelTwo = "two" - TestLabelThree = "three" - } - template { - container { - name = "nginx" - image = "nginx" - port { - container_port = 80 - } - volume_mount { - name = "workdir" - mount_path = "/usr/share/nginx/html" - } - } - init_container { - name = "install" - image = "busybox" - command = ["wget", "-O", "/work-dir/index.html", "http://kubernetes.io"] - volume_mount { - name = "workdir" - mount_path = "/work-dir" - } - } - dns_policy = "Default" - volume { - name = "workdir" - empty_dir {} - } - } - } -}`, name) -} - -func testAccKubernetesReplicationControllerConfig_deprecated_modified(name string) string { - return fmt.Sprintf(`resource "kubernetes_replication_controller" "test" { - metadata { - annotations = { - TestAnnotationOne = "one" - Different = "1234" - } - labels = { - TestLabelOne = "one" - TestLabelThree = "three" - } - name = "%s" - } - spec { - selector = { - TestLabelOne = "one" - TestLabelTwo = "two" - TestLabelThree = "three" - } - template { - container { - image = "nginx:1.7.9" - name = "tf-acc-test" - } - } - } -}`, name) -} - -func testAccKubernetesReplicationControllerConfig_deprecated_generatedName(prefix string) string { - return fmt.Sprintf(`resource "kubernetes_replication_controller" "test" { - metadata { - labels = { - TestLabelOne = "one" - TestLabelTwo = "two" - TestLabelThree = "three" - } - generate_name = "%s" - } - spec { - selector = { - TestLabelOne = "one" - TestLabelTwo = "two" - TestLabelThree = "three" - } - template { - container { - image = "nginx:1.7.9" - name = "tf-acc-test" - } - } - } -}`, prefix) -} - -func testAccKubernetesReplicationControllerConfig_deprecated_WithSecurityContext(rcName, imageName string) string { - return fmt.Sprintf(`resource "kubernetes_replication_controller" "test" { - metadata { - name = "%s" - labels = { - Test = "TfAcceptanceTest" - } - } - spec { - selector = { - Test = "TfAcceptanceTest" - } - template { - security_context { - fs_group = 100 - run_as_non_root = true - run_as_user = 101 - supplemental_groups = [101] - } - container { - image = "%s" - name = "containername" - } - } - } -} - `, rcName, imageName) -} - -func testAccKubernetesReplicationControllerConfig_deprecated_WithLivenessProbeUsingExec(rcName, imageName string) string { - return fmt.Sprintf(`resource "kubernetes_replication_controller" "test" { - metadata { - name = "%s" - labels = { - Test = "TfAcceptanceTest" - } - } - spec { - selector = { - Test = "TfAcceptanceTest" - } - template { - container { - image = "%s" - name = "containername" - args = ["/bin/sh", "-c", "touch /tmp/healthy; sleep 300; rm -rf /tmp/healthy; sleep 600"] - - liveness_probe { - exec { - command = ["cat", "/tmp/healthy"] - } - - initial_delay_seconds = 5 - period_seconds = 5 - } - } - } - } -} - `, rcName, imageName) -} - -func testAccKubernetesReplicationControllerConfig_deprecated_WithLivenessProbeUsingHTTPGet(rcName, imageName string) string { - return fmt.Sprintf(`resource "kubernetes_replication_controller" "test" { - metadata { - name = "%s" - labels = { - Test = "TfAcceptanceTest" - } - } - spec { - selector = { - Test = "TfAcceptanceTest" - } - template { - container { - image = "%s" - name = "containername" - args = ["/server"] - - liveness_probe { - http_get { - path = "/healthz" - port = 8080 - - http_header { - name = "X-Custom-Header" - value = "Awesome" - } - } - initial_delay_seconds = 3 - period_seconds = 3 - } - } - } - } -} - `, rcName, imageName) -} - -func testAccKubernetesReplicationControllerConfig_deprecated_WithLivenessProbeUsingTCP(rcName, imageName string) string { - return fmt.Sprintf(`resource "kubernetes_replication_controller" "test" { - metadata { - name = "%s" - labels = { - Test = "TfAcceptanceTest" - } - } - spec { - selector = { - Test = "TfAcceptanceTest" - } - template { - container { - image = "%s" - name = "containername" - args = ["/server"] - - liveness_probe { - tcp_socket { - port = 8080 - } - - initial_delay_seconds = 3 - period_seconds = 3 - } - } - } - } -} - `, rcName, imageName) -} - -func testAccKubernetesReplicationControllerConfig_deprecated_WithLifeCycle(rcName, imageName string) string { - return fmt.Sprintf(`resource "kubernetes_replication_controller" "test" { - metadata { - name = "%s" - labels = { - Test = "TfAcceptanceTest" - } - } - spec { - selector = { - Test = "TfAcceptanceTest" - } - template { - container { - image = "%s" - name = "containername" - args = ["/server"] - - lifecycle { - post_start { - exec { - command = ["ls", "-al"] - } - } - pre_stop { - exec { - command = ["date"] - } - } - } - } - } - } -} - - `, rcName, imageName) -} - -func testAccKubernetesReplicationControllerConfig_deprecated_WithContainerSecurityContext(rcName, imageName string) string { - return fmt.Sprintf(`resource "kubernetes_replication_controller" "test" { - metadata { - name = "%s" - labels = { - Test = "TfAcceptanceTest" - } - } - spec { - selector = { - Test = "TfAcceptanceTest" - } - template { - container { - image = "%s" - name = "containername" - - security_context { - privileged = true - run_as_user = 1 - se_linux_options { - level = "s0:c123,c456" - } - } - } - } - } -} - - - `, rcName, imageName) -} - -func testAccKubernetesReplicationControllerConfig_deprecated_WithVolumeMounts(secretName, rcName, imageName string) string { - return fmt.Sprintf(`resource "kubernetes_secret" "test" { - metadata { - name = "%s" - } - - data = { - one = "first" - } -} - -resource "kubernetes_replication_controller" "test" { - metadata { - name = "%s" - labels = { - Test = "TfAcceptanceTest" - } - } - - spec { - selector = { - Test = "TfAcceptanceTest" - } - template { - container { - image = "%s" - name = "containername" - volume_mount { - mount_path = "/tmp/my_path" - name = "db" - } - } - volume { - name = "db" - secret { - secret_name = "${kubernetes_secret.test.metadata.0.name}" - } - } - } - } -} - `, secretName, rcName, imageName) -} - -func testAccKubernetesReplicationControllerConfig_deprecated_WithResourceRequirements(rcName, imageName string) string { - return fmt.Sprintf(`resource "kubernetes_replication_controller" "test" { - metadata { - name = "%s" - labels = { - Test = "TfAcceptanceTest" - } - } - - spec { - selector = { - Test = "TfAcceptanceTest" - } - template { - container { - image = "%s" - name = "containername" - - resources { - limits = { - cpu = "0.5" - memory = "512Mi" - } - requests = { - cpu = "250m" - memory = "50Mi" - } - } - } - } - } -} - `, rcName, imageName) -} - -func testAccKubernetesReplicationControllerConfig_deprecated_WithEmptyDirVolumes(rcName, imageName string) string { - return fmt.Sprintf(`resource "kubernetes_replication_controller" "test" { - metadata { - name = "%s" - labels = { - Test = "TfAcceptanceTest" - } - } - - spec { - selector = { - Test = "TfAcceptanceTest" - } - template { - container { - image = "%s" - name = "containername" - volume_mount { - mount_path = "/cache" - name = "cache-volume" - } - } - volume { - name = "cache-volume" - empty_dir { - medium = "Memory" - } - } - } - } -} -`, rcName, imageName) -} diff --git a/kubernetes/resource_kubernetes_replication_controller_test.go b/kubernetes/resource_kubernetes_replication_controller_test.go index f30bd66e7b..87162aead0 100644 --- a/kubernetes/resource_kubernetes_replication_controller_test.go +++ b/kubernetes/resource_kubernetes_replication_controller_test.go @@ -13,69 +13,95 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +func TestAccKubernetesReplicationController_minimal(t *testing.T) { + name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + imageName := busyboxImageVersion + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "kubernetes_replication_controller.test", + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckKubernetesReplicationControllerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKubernetesReplicationControllerConfigMinimal(name, imageName), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet("kubernetes_replication_controller.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_replication_controller.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_replication_controller.test", "metadata.0.uid"), + ), + }, + { + Config: testAccKubernetesReplicationControllerConfigMinimal(name, imageName), + PlanOnly: true, + }, + }, + }) +} + func TestAccKubernetesReplicationController_basic(t *testing.T) { var conf api.ReplicationController name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - resourceName := "kubernetes_replication_controller.test" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - IDRefreshName: resourceName, + IDRefreshName: "kubernetes_replication_controller.test", IDRefreshIgnore: []string{"metadata.0.resource_version"}, ProviderFactories: testAccProviderFactories, CheckDestroy: testAccCheckKubernetesReplicationControllerDestroy, Steps: []resource.TestStep{ { - Config: testAccKubernetesReplicationControllerConfig_basic(name), + Config: testAccKubernetesReplicationControllerConfig_basic(name, imageName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckKubernetesReplicationControllerExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "metadata.0.annotations.%", "2"), - resource.TestCheckResourceAttr(resourceName, "metadata.0.annotations.TestAnnotationOne", "one"), - resource.TestCheckResourceAttr(resourceName, "metadata.0.annotations.TestAnnotationTwo", "two"), + testAccCheckKubernetesReplicationControllerExists("kubernetes_replication_controller.test", &conf), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "metadata.0.annotations.%", "2"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "metadata.0.annotations.TestAnnotationOne", "one"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "metadata.0.annotations.TestAnnotationTwo", "two"), //testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{"TestAnnotationOne": "one", "TestAnnotationTwo": "two"}), - resource.TestCheckResourceAttr(resourceName, "metadata.0.labels.%", "3"), - resource.TestCheckResourceAttr(resourceName, "metadata.0.labels.TestLabelOne", "one"), - resource.TestCheckResourceAttr(resourceName, "metadata.0.labels.TestLabelTwo", "two"), - resource.TestCheckResourceAttr(resourceName, "metadata.0.labels.TestLabelThree", "three"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "metadata.0.labels.%", "3"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "metadata.0.labels.TestLabelOne", "one"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "metadata.0.labels.TestLabelTwo", "two"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "metadata.0.labels.TestLabelThree", "three"), //testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{"TestLabelOne": "one", "TestLabelTwo": "two", "TestLabelThree": "three"}), - resource.TestCheckResourceAttr(resourceName, "metadata.0.name", name), - resource.TestCheckResourceAttrSet(resourceName, "metadata.0.generation"), - resource.TestCheckResourceAttrSet(resourceName, "metadata.0.resource_version"), - resource.TestCheckResourceAttrSet(resourceName, "metadata.0.self_link"), - resource.TestCheckResourceAttrSet(resourceName, "metadata.0.uid"), - resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.spec.0.container.0.image", "nginx:1.7.8"), - resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.spec.0.container.0.name", "tf-acc-test"), - resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.metadata.0.annotations.%", "1"), - resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.metadata.0.annotations.TestAnnotationFive", "five"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "metadata.0.name", name), + resource.TestCheckResourceAttrSet("kubernetes_replication_controller.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_replication_controller.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_replication_controller.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_replication_controller.test", "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.container.0.image", imageName), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.container.0.name", "tf-acc-test"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.metadata.0.annotations.%", "1"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.metadata.0.annotations.TestAnnotationFive", "five"), ), }, { - ResourceName: resourceName, + ResourceName: "kubernetes_replication_controller.test", ImportState: true, ImportStateVerify: true, ImportStateVerifyIgnore: []string{"metadata.0.resource_version"}, }, { - Config: testAccKubernetesReplicationControllerConfig_modified(name), + Config: testAccKubernetesReplicationControllerConfig_modified(name, imageName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckKubernetesReplicationControllerExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "metadata.0.annotations.%", "2"), - resource.TestCheckResourceAttr(resourceName, "metadata.0.annotations.TestAnnotationOne", "one"), - resource.TestCheckResourceAttr(resourceName, "metadata.0.annotations.Different", "1234"), + testAccCheckKubernetesReplicationControllerExists("kubernetes_replication_controller.test", &conf), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "metadata.0.annotations.%", "2"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "metadata.0.annotations.TestAnnotationOne", "one"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "metadata.0.annotations.Different", "1234"), //testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{"TestAnnotationOne": "one", "Different": "1234"}), - resource.TestCheckResourceAttr(resourceName, "metadata.0.labels.%", "2"), - resource.TestCheckResourceAttr(resourceName, "metadata.0.labels.TestLabelOne", "one"), - resource.TestCheckResourceAttr(resourceName, "metadata.0.labels.TestLabelThree", "three"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "metadata.0.labels.%", "2"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "metadata.0.labels.TestLabelOne", "one"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "metadata.0.labels.TestLabelThree", "three"), //testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{"TestLabelOne": "one", "TestLabelThree": "three"}), - resource.TestCheckResourceAttr(resourceName, "metadata.0.name", name), - resource.TestCheckResourceAttrSet(resourceName, "metadata.0.generation"), - resource.TestCheckResourceAttrSet(resourceName, "metadata.0.resource_version"), - resource.TestCheckResourceAttrSet(resourceName, "metadata.0.self_link"), - resource.TestCheckResourceAttrSet(resourceName, "metadata.0.uid"), - resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.spec.0.container.0.image", "nginx:1.7.9"), - resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.spec.0.container.0.name", "tf-acc-test"), - resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.metadata.0.annotations.%", "1"), - resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.metadata.0.annotations.TestAnnotationSix", "six"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "metadata.0.name", name), + resource.TestCheckResourceAttrSet("kubernetes_replication_controller.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_replication_controller.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_replication_controller.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_replication_controller.test", "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.container.0.image", imageName), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.container.0.name", "tf-acc-test"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.metadata.0.annotations.%", "1"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.metadata.0.annotations.TestAnnotationSix", "six"), ), }, }, @@ -83,7 +109,7 @@ func TestAccKubernetesReplicationController_basic(t *testing.T) { } func TestAccKubernetesReplicationController_initContainer(t *testing.T) { - var conf api.ReplicationController + var conf1, conf2 api.ReplicationController name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) resource.Test(t, resource.TestCase{ @@ -93,10 +119,10 @@ func TestAccKubernetesReplicationController_initContainer(t *testing.T) { CheckDestroy: testAccCheckKubernetesReplicationControllerDestroy, Steps: []resource.TestStep{ { - Config: testAccKubernetesReplicationControllerConfig_initContainer(name), + Config: testAccKubernetesReplicationControllerConfig_initContainer(name, busyboxImageVersion), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckKubernetesReplicationControllerExists("kubernetes_replication_controller.test", &conf), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.init_container.0.image", "busybox"), + testAccCheckKubernetesReplicationControllerExists("kubernetes_replication_controller.test", &conf1), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.init_container.0.image", busyboxImageVersion), resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.init_container.0.name", "install"), resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.init_container.0.command.0", "wget"), resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.init_container.0.command.1", "-O"), @@ -116,7 +142,14 @@ func TestAccKubernetesReplicationController_initContainer(t *testing.T) { resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.dns_config.0.option.0.value", "1"), resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.dns_config.0.option.1.name", "use-vc"), resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.dns_config.0.option.1.value", ""), - // resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.dns_policy", "Default"), + ), + }, + { + Config: testAccKubernetesReplicationControllerConfig_initContainer(name, busyboxImageVersion1), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesReplicationControllerExists("kubernetes_replication_controller.test", &conf2), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.init_container.0.image", busyboxImageVersion1), + testAccCheckKubernetesReplicationControllerForceNew(&conf1, &conf2, true), ), }, }, @@ -126,6 +159,7 @@ func TestAccKubernetesReplicationController_initContainer(t *testing.T) { func TestAccKubernetesReplicationController_regression(t *testing.T) { var conf1, conf2 api.ReplicationController name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + imageName := busyboxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -134,10 +168,11 @@ func TestAccKubernetesReplicationController_regression(t *testing.T) { CheckDestroy: testAccCheckKubernetesReplicationControllerDestroy, Steps: []resource.TestStep{ { - Config: requiredProviders() + testAccKubernetesReplicationControllerConfig_regression("kubernetes-released", name), + Config: requiredProviders() + testAccKubernetesReplicationControllerConfig_regression("kubernetes-released", name, imageName), + ExpectNonEmptyPlan: true, Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesReplicationControllerExists("kubernetes_replication_controller.test", &conf1), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.init_container.0.image", "busybox"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.init_container.0.image", imageName), resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.init_container.0.name", "install"), resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.init_container.0.command.0", "wget"), resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.init_container.0.command.1", "-O"), @@ -161,10 +196,10 @@ func TestAccKubernetesReplicationController_regression(t *testing.T) { ), }, { - Config: requiredProviders() + testAccKubernetesReplicationControllerConfig_regression("kubernetes-local", name), + Config: requiredProviders() + testAccKubernetesReplicationControllerConfig_regression("kubernetes-local", name, imageName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesReplicationControllerExists("kubernetes_replication_controller.test", &conf2), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.init_container.0.image", "busybox"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.init_container.0.image", imageName), resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.init_container.0.name", "install"), resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.init_container.0.command.0", "wget"), resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.init_container.0.command.1", "-O"), @@ -195,32 +230,32 @@ func TestAccKubernetesReplicationController_regression(t *testing.T) { func TestAccKubernetesReplicationController_generatedName(t *testing.T) { var conf api.ReplicationController prefix := "tf-acc-test-gen-" - resourceName := "kubernetes_replication_controller.test" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - IDRefreshName: resourceName, + IDRefreshName: "kubernetes_replication_controller.test", ProviderFactories: testAccProviderFactories, CheckDestroy: testAccCheckKubernetesReplicationControllerDestroy, Steps: []resource.TestStep{ { - Config: testAccKubernetesReplicationControllerConfig_generatedName(prefix), + Config: testAccKubernetesReplicationControllerConfig_generatedName(prefix, imageName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckKubernetesReplicationControllerExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "metadata.0.annotations.%", "0"), + testAccCheckKubernetesReplicationControllerExists("kubernetes_replication_controller.test", &conf), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "metadata.0.annotations.%", "0"), //testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{}), - resource.TestCheckResourceAttr(resourceName, "metadata.0.labels.%", "3"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "metadata.0.labels.%", "3"), //testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{"TestLabelOne": "one", "TestLabelTwo": "two", "TestLabelThree": "three"}), - resource.TestCheckResourceAttr(resourceName, "metadata.0.generate_name", prefix), - resource.TestMatchResourceAttr(resourceName, "metadata.0.name", regexp.MustCompile("^"+prefix)), - resource.TestCheckResourceAttrSet(resourceName, "metadata.0.generation"), - resource.TestCheckResourceAttrSet(resourceName, "metadata.0.resource_version"), - resource.TestCheckResourceAttrSet(resourceName, "metadata.0.self_link"), - resource.TestCheckResourceAttrSet(resourceName, "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "metadata.0.generate_name", prefix), + resource.TestMatchResourceAttr("kubernetes_replication_controller.test", "metadata.0.name", regexp.MustCompile("^"+prefix)), + resource.TestCheckResourceAttrSet("kubernetes_replication_controller.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_replication_controller.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_replication_controller.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_replication_controller.test", "metadata.0.uid"), ), }, { - ResourceName: resourceName, + ResourceName: "kubernetes_replication_controller.test", ImportState: true, ImportStateVerify: true, ImportStateVerifyIgnore: []string{"metadata.0.resource_version"}, @@ -233,7 +268,7 @@ func TestAccKubernetesReplicationController_with_security_context(t *testing.T) var conf api.ReplicationController rcName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := "nginx:1.7.9" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -259,7 +294,7 @@ func TestAccKubernetesReplicationController_with_security_context_run_as_group(t var conf api.ReplicationController rcName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := "nginx:1.7.9" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); skipIfUnsupportedSecurityContextRunAsGroup(t) }, @@ -401,7 +436,7 @@ func TestAccKubernetesReplicationController_with_container_security_context(t *t var conf api.ReplicationController rcName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := "nginx:1.7.9" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -425,7 +460,7 @@ func TestAccKubernetesReplicationController_with_volume_mount(t *testing.T) { rcName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) secretName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := "nginx:1.7.9" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -453,7 +488,7 @@ func TestAccKubernetesReplicationController_with_resource_requirements(t *testin rcName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := "nginx:1.7.9" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -465,10 +500,10 @@ func TestAccKubernetesReplicationController_with_resource_requirements(t *testin Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesReplicationControllerExists("kubernetes_replication_controller.test", &conf), resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.container.0.image", imageName), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.container.0.resources.0.requests.0.memory", "50Mi"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.container.0.resources.0.requests.0.cpu", "250m"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.container.0.resources.0.limits.0.memory", "512Mi"), - resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.container.0.resources.0.limits.0.cpu", "500m"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.container.0.resources.0.requests.memory", "50Mi"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.container.0.resources.0.requests.cpu", "250m"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.container.0.resources.0.limits.memory", "512Mi"), + resource.TestCheckResourceAttr("kubernetes_replication_controller.test", "spec.0.template.0.spec.0.container.0.resources.0.limits.cpu", "500m"), ), }, }, @@ -479,7 +514,7 @@ func TestAccKubernetesReplicationController_with_empty_dir_volume(t *testing.T) var conf api.ReplicationController rcName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - imageName := "nginx:1.7.9" + imageName := nginxImageVersion resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -573,7 +608,7 @@ func testAccCheckKubernetesReplicationControllerForceNew(old, new *api.Replicati } } -func testAccKubernetesReplicationControllerConfig_basic(name string) string { +func testAccKubernetesReplicationControllerConfig_basic(name, imageName string) string { return fmt.Sprintf(`resource "kubernetes_replication_controller" "test" { metadata { annotations = { @@ -614,17 +649,17 @@ func testAccKubernetesReplicationControllerConfig_basic(name string) string { spec { container { - image = "nginx:1.7.8" + image = "%s" name = "tf-acc-test" } } } } } -`, name) +`, name, imageName) } -func testAccKubernetesReplicationControllerConfig_initContainer(name string) string { +func testAccKubernetesReplicationControllerConfig_initContainer(name, image string) string { return fmt.Sprintf(`resource "kubernetes_replication_controller" "test" { metadata { annotations = { @@ -661,7 +696,7 @@ func testAccKubernetesReplicationControllerConfig_initContainer(name string) str spec { container { name = "nginx" - image = "nginx" + image = "%s" port { container_port = 80 @@ -675,7 +710,7 @@ func testAccKubernetesReplicationControllerConfig_initContainer(name string) str init_container { name = "install" - image = "busybox" + image = "%s" command = ["wget", "-O", "/work-dir/index.html", "http://kubernetes.io"] volume_mount { @@ -708,10 +743,10 @@ func testAccKubernetesReplicationControllerConfig_initContainer(name string) str } } } -`, name) +`, name, image, image) } -func testAccKubernetesReplicationControllerConfig_modified(name string) string { +func testAccKubernetesReplicationControllerConfig_modified(name, imageName string) string { return fmt.Sprintf(`resource "kubernetes_replication_controller" "test" { metadata { annotations = { @@ -749,17 +784,17 @@ func testAccKubernetesReplicationControllerConfig_modified(name string) string { spec { container { - image = "nginx:1.7.9" + image = "%s" name = "tf-acc-test" } } } } } -`, name) +`, name, imageName) } -func testAccKubernetesReplicationControllerConfig_generatedName(prefix string) string { +func testAccKubernetesReplicationControllerConfig_generatedName(prefix, imageName string) string { return fmt.Sprintf(`resource "kubernetes_replication_controller" "test" { metadata { labels = { @@ -789,14 +824,14 @@ func testAccKubernetesReplicationControllerConfig_generatedName(prefix string) s spec { container { - image = "nginx:1.7.9" + image = "%s" name = "tf-acc-test" } } } } } -`, prefix) +`, prefix, imageName) } func testAccKubernetesReplicationControllerConfigWithSecurityContext(rcName, imageName string) string { @@ -1262,9 +1297,9 @@ func testAccKubernetesReplicationControllerConfigWithEmptyDirVolumes(rcName, ima `, rcName, imageName) } -func testAccKubernetesReplicationControllerConfig_regression(provider, name string) string { +func testAccKubernetesReplicationControllerConfig_regression(provider, name, imageName string) string { return fmt.Sprintf(`resource "kubernetes_replication_controller" "test" { - provider = "%s" + provider = %s metadata { annotations = { TestAnnotationOne = "one" @@ -1281,7 +1316,7 @@ func testAccKubernetesReplicationControllerConfig_regression(provider, name stri } spec { - replicas = 500 # This is intentionally high to exercise the waiter + replicas = 5 selector = { TestLabelOne = "one" TestLabelTwo = "two" @@ -1300,7 +1335,7 @@ func testAccKubernetesReplicationControllerConfig_regression(provider, name stri spec { container { name = "nginx" - image = "nginx" + image = "%s" port { container_port = 80 @@ -1314,7 +1349,7 @@ func testAccKubernetesReplicationControllerConfig_regression(provider, name stri init_container { name = "install" - image = "busybox" + image = "%s" command = ["wget", "-O", "/work-dir/index.html", "http://kubernetes.io"] volume_mount { @@ -1346,5 +1381,35 @@ func testAccKubernetesReplicationControllerConfig_regression(provider, name stri } } } -`, provider, name) +`, provider, name, imageName, imageName) +} + +func testAccKubernetesReplicationControllerConfigMinimal(name, imageName string) string { + return fmt.Sprintf(`resource "kubernetes_replication_controller" "test" { + metadata { + name = "%s" + labels = { + test = "%s" + } + } + spec { + selector = { + test = "%s" + } + template { + metadata { + labels = { + test = "%s" + } + } + spec { + container { + image = "%s" + name = "containername" + } + } + } + } +} +`, name, name, name, name, imageName) } diff --git a/kubernetes/resource_kubernetes_service_account.go b/kubernetes/resource_kubernetes_service_account.go index d3e16ea45d..3fc9f6d2ae 100644 --- a/kubernetes/resource_kubernetes_service_account.go +++ b/kubernetes/resource_kubernetes_service_account.go @@ -98,13 +98,20 @@ func resourceKubernetesServiceAccountCreate(ctx context.Context, d *schema.Resou d.SetId(buildId(out.ObjectMeta)) secret, err := getServiceAccountDefaultSecret(ctx, out.Name, svcAcc, d.Timeout(schema.TimeoutCreate), conn) - d.Set("default_secret_name", secret.Name) + if err != nil { + return diag.FromErr(err) + } + + err = d.Set("default_secret_name", secret.Name) + if err != nil { + return diag.FromErr(err) + } return resourceKubernetesServiceAccountRead(ctx, d, meta) } func getServiceAccountDefaultSecret(ctx context.Context, name string, config api.ServiceAccount, timeout time.Duration, conn *kubernetes.Clientset) (*api.Secret, error) { var svcAccTokens []api.Secret - err := resource.Retry(timeout, func() *resource.RetryError { + err := resource.RetryContext(ctx, timeout, func() *resource.RetryError { resp, err := conn.CoreV1().ServiceAccounts(config.Namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { return resource.NonRetryableError(err) @@ -119,6 +126,10 @@ func getServiceAccountDefaultSecret(ctx context.Context, name string, config api secretList, err := conn.CoreV1().Secrets(config.Namespace).List(ctx, metav1.ListOptions{ FieldSelector: fmt.Sprintf("type=%s", api.SecretTypeServiceAccountToken), }) + if err != nil { + return resource.NonRetryableError(err) + } + for _, secret := range secretList.Items { for _, svcSecret := range diff { if secret.Name != svcSecret.Name { @@ -246,13 +257,19 @@ func resourceKubernetesServiceAccountRead(ctx context.Context, d *schema.Resourc return diag.FromErr(err) } } - d.Set("image_pull_secret", flattenLocalObjectReferenceArray(svcAcc.ImagePullSecrets)) + err = d.Set("image_pull_secret", flattenLocalObjectReferenceArray(svcAcc.ImagePullSecrets)) + if err != nil { + return diag.FromErr(err) + } defaultSecretName := d.Get("default_secret_name").(string) log.Printf("[DEBUG] Default secret name is %q", defaultSecretName) secrets := flattenServiceAccountSecrets(svcAcc.Secrets, defaultSecretName) log.Printf("[DEBUG] Flattened secrets: %#v", secrets) - d.Set("secret", secrets) + err = d.Set("secret", secrets) + if err != nil { + return diag.FromErr(err) + } return nil } diff --git a/kubernetes/resource_kubernetes_service_test.go b/kubernetes/resource_kubernetes_service_test.go index b868b2ff71..aa529fab22 100644 --- a/kubernetes/resource_kubernetes_service_test.go +++ b/kubernetes/resource_kubernetes_service_test.go @@ -721,7 +721,7 @@ func testAccKubernetesServiceConfig_basic(name string) string { func testAccKubernetesServiceConfig_regression(provider, name string) string { return fmt.Sprintf(`resource "kubernetes_service" "test" { - provider = "%s" + provider = %s metadata { annotations = { TestAnnotationOne = "one" diff --git a/kubernetes/resource_kubernetes_stateful_set.go b/kubernetes/resource_kubernetes_stateful_set.go index 0788dc35f6..0be2a0fdf6 100644 --- a/kubernetes/resource_kubernetes_stateful_set.go +++ b/kubernetes/resource_kubernetes_stateful_set.go @@ -56,9 +56,8 @@ func resourceKubernetesStatefulSetSchemaV1() map[string]*schema.Schema { Required: true, MaxItems: 1, MinItems: 1, - ForceNew: true, Elem: &schema.Resource{ - Schema: statefulSetSpecFields(false), + Schema: statefulSetSpecFields(), }, }, "wait_for_rollout": { @@ -273,6 +272,10 @@ func retryUntilStatefulSetRolloutComplete(ctx context.Context, conn *kubernetes. return resource.NonRetryableError(err) } + if res.Status.ReadyReplicas != *res.Spec.Replicas { + return resource.RetryableError(fmt.Errorf("StatefulSet %s/%s is not finished rolling out", ns, name)) + } + // NOTE: This is what kubectl uses to determine if a rollout is done. // We are using this here because the logic for determining if a StatefulSet // is done is gnarly and we don't want to duplicate it in the provider. diff --git a/kubernetes/resource_kubernetes_stateful_set_test.go b/kubernetes/resource_kubernetes_stateful_set_test.go index a2fa7716b9..f868eed03e 100644 --- a/kubernetes/resource_kubernetes_stateful_set_test.go +++ b/kubernetes/resource_kubernetes_stateful_set_test.go @@ -13,6 +13,32 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +func TestAccKubernetesStatefulSet_minimal(t *testing.T) { + name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + imageName := nginxImageVersion + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "kubernetes_stateful_set.test", + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckKubernetesStatefulSetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKubernetesStatefulSetConfigMinimal(name, imageName), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet("kubernetes_stateful_set.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_stateful_set.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_stateful_set.test", "metadata.0.uid"), + resource.TestCheckResourceAttrSet("kubernetes_stateful_set.test", "metadata.0.namespace"), + ), + }, + { + Config: testAccKubernetesStatefulSetConfigMinimal(name, imageName), + PlanOnly: true, + }, + }, + }) +} + func TestAccKubernetesStatefulSet_basic(t *testing.T) { var conf api.StatefulSet name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) @@ -28,7 +54,6 @@ func TestAccKubernetesStatefulSet_basic(t *testing.T) { Config: testAccKubernetesStatefulSetConfigBasic(name), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesStatefulSetExists("kubernetes_stateful_set.test", &conf), - testAccCheckKubernetesStatefulSetRolledOut("kubernetes_stateful_set.test"), resource.TestCheckResourceAttr("kubernetes_stateful_set.test", "wait_for_rollout", "true"), resource.TestCheckResourceAttrSet("kubernetes_stateful_set.test", "metadata.0.generation"), resource.TestCheckResourceAttrSet("kubernetes_stateful_set.test", "metadata.0.resource_version"), @@ -81,8 +106,10 @@ func TestAccKubernetesStatefulSet_basic_idempotency(t *testing.T) { var conf api.StatefulSet name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - IDRefreshName: "kubernetes_stateful_set.test", + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "kubernetes_stateful_set.test", + IDRefreshIgnore: []string{"spec.0.template.0.spec.0.container.0.resources.0.limits", + "spec.0.template.0.spec.0.container.0.resources.0.requests"}, ProviderFactories: testAccProviderFactories, CheckDestroy: testAccCheckKubernetesStatefulSetDestroy, Steps: []resource.TestStep{ @@ -108,9 +135,10 @@ func TestAccKubernetesStatefulSet_Update(t *testing.T) { var conf api.StatefulSet name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - IDRefreshName: "kubernetes_stateful_set.test", - IDRefreshIgnore: []string{"metadata.0.resource_version"}, + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "kubernetes_stateful_set.test", + IDRefreshIgnore: []string{"spec.0.template.0.spec.0.container.0.resources.0.limits", + "spec.0.template.0.spec.0.container.0.resources.0.requests"}, ProviderFactories: testAccProviderFactories, CheckDestroy: testAccCheckKubernetesStatefulSetDestroy, Steps: []resource.TestStep{ @@ -223,40 +251,35 @@ func TestAccKubernetesStatefulSet_Update(t *testing.T) { }) } -func TestAccKubernetesStatefulSet_waitForRollout_true(t *testing.T) { - var conf api.StatefulSet +func TestAccKubernetesStatefulSet_waitForRollout(t *testing.T) { + var conf1, conf2 api.StatefulSet + imageName := nginxImageVersion + imageName1 := nginxImageVersion1 name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - IDRefreshName: "kubernetes_stateful_set.test", + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "kubernetes_stateful_set.test", + IDRefreshIgnore: []string{ + "spec.0.template.0.spec.0.container.0.resources.0.limits", + "spec.0.template.0.spec.0.container.0.resources.0.requests", + "metadata.0.resource_version", + }, ProviderFactories: testAccProviderFactories, CheckDestroy: testAccCheckKubernetesStatefulSetDestroy, Steps: []resource.TestStep{ { - Config: testAccKubernetesStatefulSetConfigWaitForRollout(name, true), + Config: testAccKubernetesStatefulSetConfigWaitForRollout(name, imageName, "true"), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckKubernetesStatefulSetExists("kubernetes_stateful_set.test", &conf), - testAccCheckKubernetesStatefulSetRolledOut("kubernetes_stateful_set.test"), + testAccCheckKubernetesStatefulSetExists("kubernetes_stateful_set.test", &conf1), + resource.TestCheckResourceAttr("kubernetes_stateful_set.test", "wait_for_rollout", "true"), ), }, - }, - }) -} - -func TestAccKubernetesStatefulSet_waitForRollout_false(t *testing.T) { - var conf api.StatefulSet - name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - IDRefreshName: "kubernetes_stateful_set.test", - ProviderFactories: testAccProviderFactories, - CheckDestroy: testAccCheckKubernetesStatefulSetDestroy, - Steps: []resource.TestStep{ { - Config: testAccKubernetesStatefulSetConfigWaitForRollout(name, false), + Config: testAccKubernetesStatefulSetConfigWaitForRollout(name, imageName1, "false"), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckKubernetesStatefulSetExists("kubernetes_stateful_set.test", &conf), - testAccCheckKubernetesStatefulSetRollingOut("kubernetes_stateful_set.test"), + testAccCheckKubernetesStatefulSetExists("kubernetes_stateful_set.test", &conf2), + resource.TestCheckResourceAttr("kubernetes_stateful_set.test", "wait_for_rollout", "false"), + testAccCheckKubernetesStatefulSetForceNew(&conf1, &conf2, false), ), }, }, @@ -267,8 +290,10 @@ func TestAccKubernetesStatefulSet_regression(t *testing.T) { var conf1, conf2 api.StatefulSet name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - IDRefreshName: "kubernetes_stateful_set.test", + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "kubernetes_stateful_set.test", + IDRefreshIgnore: []string{"spec.0.template.0.spec.0.container.0.resources.0.limits", + "spec.0.template.0.spec.0.container.0.resources.0.requests"}, ExternalProviders: testAccExternalProviders, CheckDestroy: testAccCheckKubernetesStatefulSetDestroy, Steps: []resource.TestStep{ @@ -276,7 +301,6 @@ func TestAccKubernetesStatefulSet_regression(t *testing.T) { Config: requiredProviders() + testAccKubernetesStatefulSet_regression("kubernetes-released", name), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesStatefulSetExists("kubernetes_stateful_set.test", &conf1), - testAccCheckKubernetesStatefulSetRollingOut("kubernetes_stateful_set.test"), resource.TestCheckResourceAttr("kubernetes_stateful_set.test", "wait_for_rollout", "false"), resource.TestCheckResourceAttrSet("kubernetes_stateful_set.test", "metadata.0.generation"), resource.TestCheckResourceAttrSet("kubernetes_stateful_set.test", "metadata.0.resource_version"), @@ -319,7 +343,6 @@ func TestAccKubernetesStatefulSet_regression(t *testing.T) { Config: requiredProviders() + testAccKubernetesStatefulSet_regression("kubernetes-local", name), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKubernetesStatefulSetExists("kubernetes_stateful_set.test", &conf2), - testAccCheckKubernetesStatefulSetRollingOut("kubernetes_stateful_set.test"), resource.TestCheckResourceAttr("kubernetes_stateful_set.test", "wait_for_rollout", "false"), resource.TestCheckResourceAttrSet("kubernetes_stateful_set.test", "metadata.0.generation"), resource.TestCheckResourceAttrSet("kubernetes_stateful_set.test", "metadata.0.resource_version"), @@ -444,34 +467,34 @@ func testAccCheckKubernetesStatefulSetExists(n string, obj *appsv1.StatefulSet) } } -func testAccCheckKubernetesStatefulSetRollingOut(n string) resource.TestCheckFunc { - return func(s *terraform.State) error { - d, err := getStatefulSetFromResourceName(s, n) - if err != nil { - return err - } - - if d.Status.ReadyReplicas == *d.Spec.Replicas { - return fmt.Errorf("StatefulSet has already rolled out") - } - - return nil - } +func testAccKubernetesStatefulSetConfigMinimal(name, imageName string) string { + return fmt.Sprintf(`resource "kubernetes_stateful_set" "test" { + metadata { + name = "%s" + } + spec { + selector { + match_labels = { + app = "ss-test" + } + } + service_name = "ss-test-service" + template { + metadata { + labels = { + app = "ss-test" + } + } + spec { + container { + name = "ss-test" + image = "%s" + } + } + } + } } - -func testAccCheckKubernetesStatefulSetRolledOut(n string) resource.TestCheckFunc { - return func(s *terraform.State) error { - d, err := getStatefulSetFromResourceName(s, n) - if err != nil { - return err - } - - if d.Status.ReadyReplicas != *d.Spec.Replicas { - return fmt.Errorf("StatefulSet is still rolling out") - } - - return nil - } +`, name, imageName) } func testAccKubernetesStatefulSetConfigBasic(name string) string { @@ -1084,7 +1107,7 @@ func testAccKubernetesStatefulSetConfigUpdateStrategyOnDelete(name string) strin `, name) } -func testAccKubernetesStatefulSetConfigWaitForRollout(name string, v bool) string { +func testAccKubernetesStatefulSetConfigWaitForRollout(name, imageName, waitForRollout string) string { return fmt.Sprintf(`resource "kubernetes_service" "test" { metadata { name = "ss-test" @@ -1133,7 +1156,7 @@ resource "kubernetes_stateful_set" "test" { spec { container { name = "ss-test" - image = "nginx:1.19" + image = "%s" port { container_port = 80 @@ -1151,14 +1174,14 @@ resource "kubernetes_stateful_set" "test" { } } - wait_for_rollout = %t + wait_for_rollout = %s } -`, name, v) +`, name, imageName, waitForRollout) } func testAccKubernetesStatefulSet_regression(provider, name string) string { return fmt.Sprintf(`resource "kubernetes_stateful_set" "test" { - provider = "%s" + provider = %s metadata { annotations = { TestAnnotationOne = "one" diff --git a/kubernetes/resource_kubernetes_storage_class_test.go b/kubernetes/resource_kubernetes_storage_class_test.go index 6bb7cb1680..05a82d05a1 100644 --- a/kubernetes/resource_kubernetes_storage_class_test.go +++ b/kubernetes/resource_kubernetes_storage_class_test.go @@ -121,7 +121,7 @@ func TestAccKubernetesStorageClass_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "volume_binding_mode", "Immediate"), resource.TestCheckResourceAttr(resourceName, "allow_volume_expansion", "true"), resource.TestCheckResourceAttr(resourceName, "mount_options.#", "2"), - resource.TestCheckResourceAttr(resourceName, "mount_options.0", "foo"), + resource.TestCheckResourceAttr(resourceName, "mount_options.1", "foo"), resource.TestCheckResourceAttr(resourceName, "mount_options.0", "bar"), resource.TestCheckResourceAttr(resourceName, "parameters.%", "1"), resource.TestCheckResourceAttr(resourceName, "parameters.type", "pd-ssd"), diff --git a/kubernetes/schema_container.go b/kubernetes/schema_container.go index 39ffb0474c..5303883f97 100644 --- a/kubernetes/schema_container.go +++ b/kubernetes/schema_container.go @@ -156,26 +156,30 @@ func resourcesFieldV0() map[string]*schema.Schema { } } -func seLinuxOptionsField() map[string]*schema.Schema { +func seLinuxOptionsField(isUpdatable bool) map[string]*schema.Schema { return map[string]*schema.Schema{ "level": { Type: schema.TypeString, Optional: true, + ForceNew: !isUpdatable, Description: "Level is SELinux level label that applies to the container.", }, "role": { Type: schema.TypeString, Optional: true, + ForceNew: !isUpdatable, Description: "Role is a SELinux role label that applies to the container.", }, "type": { Type: schema.TypeString, Optional: true, + ForceNew: !isUpdatable, Description: "Type is a SELinux type label that applies to the container.", }, "user": { Type: schema.TypeString, Optional: true, + ForceNew: !isUpdatable, Description: "User is a SELinux user label that applies to the container.", }, } @@ -214,21 +218,19 @@ func volumeMountFields() map[string]*schema.Schema { } } -func containerFields(isUpdatable, isInitContainer bool) map[string]*schema.Schema { +func containerFields(isUpdatable bool) map[string]*schema.Schema { s := map[string]*schema.Schema{ "args": { - Type: schema.TypeList, - Optional: true, - // The API will default this to [], so prevent non-empty plans by setting this to Computed. - Computed: true, + Type: schema.TypeList, + Optional: true, + ForceNew: !isUpdatable, Elem: &schema.Schema{Type: schema.TypeString}, Description: "Arguments to the entrypoint. The docker image's CMD is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/containers#containers-and-commands", }, "command": { - Type: schema.TypeList, - Optional: true, - // The API will default this to [], so prevent non-empty plans by setting this to Computed. - Computed: true, + Type: schema.TypeList, + Optional: true, + ForceNew: !isUpdatable, Elem: &schema.Schema{Type: schema.TypeString}, Description: "Entrypoint array. Not executed within a shell. The docker image's ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/containers#containers-and-commands", }, @@ -241,6 +243,7 @@ func containerFields(isUpdatable, isInitContainer bool) map[string]*schema.Schem "name": { Type: schema.TypeString, Required: true, + ForceNew: !isUpdatable, Description: "Name of the environment variable. Must be a C_IDENTIFIER", }, "value": { @@ -266,16 +269,19 @@ func containerFields(isUpdatable, isInitContainer bool) map[string]*schema.Schem "key": { Type: schema.TypeString, Optional: true, + ForceNew: !isUpdatable, Description: "The key to select.", }, "name": { Type: schema.TypeString, Optional: true, + ForceNew: !isUpdatable, Description: "Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names", }, "optional": { Type: schema.TypeBool, Optional: true, + ForceNew: !isUpdatable, Description: "Specify whether the ConfigMap or its key must be defined.", }, }, @@ -291,12 +297,14 @@ func containerFields(isUpdatable, isInitContainer bool) map[string]*schema.Schem "api_version": { Type: schema.TypeString, Optional: true, + ForceNew: !isUpdatable, Default: "v1", Description: `Version of the schema the FieldPath is written in terms of, defaults to "v1".`, }, "field_path": { Type: schema.TypeString, Optional: true, + ForceNew: !isUpdatable, Description: "Path of the field to select in the specified API version", }, }, @@ -312,6 +320,7 @@ func containerFields(isUpdatable, isInitContainer bool) map[string]*schema.Schem "container_name": { Type: schema.TypeString, Optional: true, + ForceNew: !isUpdatable, }, "divisor": { Type: schema.TypeString, @@ -323,6 +332,7 @@ func containerFields(isUpdatable, isInitContainer bool) map[string]*schema.Schem "resource": { Type: schema.TypeString, Required: true, + ForceNew: !isUpdatable, Description: "Resource to select", }, }, @@ -338,16 +348,19 @@ func containerFields(isUpdatable, isInitContainer bool) map[string]*schema.Schem "key": { Type: schema.TypeString, Optional: true, + ForceNew: !isUpdatable, Description: "The key of the secret to select from. Must be a valid secret key.", }, "name": { Type: schema.TypeString, Optional: true, + ForceNew: !isUpdatable, Description: "Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names", }, "optional": { Type: schema.TypeBool, Optional: true, + ForceNew: !isUpdatable, Description: "Specify whether the Secret or its key must be defined.", }, }, @@ -375,11 +388,13 @@ func containerFields(isUpdatable, isInitContainer bool) map[string]*schema.Schem "name": { Type: schema.TypeString, Required: true, + ForceNew: !isUpdatable, Description: "Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names", }, "optional": { Type: schema.TypeBool, Optional: true, + ForceNew: !isUpdatable, Description: "Specify whether the ConfigMap must be defined", }, }, @@ -388,6 +403,7 @@ func containerFields(isUpdatable, isInitContainer bool) map[string]*schema.Schem "prefix": { Type: schema.TypeString, Optional: true, + ForceNew: !isUpdatable, Description: "An optional identifer to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER.", }, "secret_ref": { @@ -400,11 +416,13 @@ func containerFields(isUpdatable, isInitContainer bool) map[string]*schema.Schem "name": { Type: schema.TypeString, Required: true, + ForceNew: !isUpdatable, Description: "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", }, "optional": { Type: schema.TypeBool, Optional: true, + ForceNew: !isUpdatable, Description: "Specify whether the Secret must be defined", }, }, @@ -416,12 +434,14 @@ func containerFields(isUpdatable, isInitContainer bool) map[string]*schema.Schem "image": { Type: schema.TypeString, Optional: true, + ForceNew: !isUpdatable, Description: "Docker image name. More info: http://kubernetes.io/docs/user-guide/images", }, "image_pull_policy": { Type: schema.TypeString, Optional: true, Computed: true, + ForceNew: !isUpdatable, Description: "Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/images#updating-images", }, "lifecycle": { @@ -435,6 +455,7 @@ func containerFields(isUpdatable, isInitContainer bool) map[string]*schema.Schem Type: schema.TypeList, Description: `post_start is called immediately after a container is created. If the handler fails, the container is terminated and restarted according to its restart policy. Other management of the container blocks until the hook completes. More info: http://kubernetes.io/docs/user-guide/container-environment#hook-details`, Optional: true, + ForceNew: !isUpdatable, Elem: &schema.Resource{ Schema: handlerFields(), }, @@ -443,6 +464,7 @@ func containerFields(isUpdatable, isInitContainer bool) map[string]*schema.Schem Type: schema.TypeList, Description: `pre_stop is called immediately before a container is terminated. The container is terminated after the handler completes. The reason for termination is passed to the handler. Regardless of the outcome of the handler, the container is eventually terminated. Other management of the container blocks until the hook completes. More info: http://kubernetes.io/docs/user-guide/container-environment#hook-details`, Optional: true, + ForceNew: !isUpdatable, Elem: &schema.Resource{ Schema: handlerFields(), }, @@ -454,12 +476,14 @@ func containerFields(isUpdatable, isInitContainer bool) map[string]*schema.Schem Type: schema.TypeList, Optional: true, MaxItems: 1, + ForceNew: !isUpdatable, Description: "Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/pod-states#container-probes", Elem: probeSchema(), }, "name": { Type: schema.TypeString, Required: true, + ForceNew: !isUpdatable, Description: "Name of the container specified as a DNS_LABEL. Each container in a pod must have a unique name (DNS_LABEL). Cannot be updated.", }, "port": { @@ -472,27 +496,32 @@ func containerFields(isUpdatable, isInitContainer bool) map[string]*schema.Schem Type: schema.TypeInt, Required: true, ValidateFunc: validatePortNumOrName, + ForceNew: !isUpdatable, Description: "Number of port to expose on the pod's IP address. This must be a valid port number, 0 < x < 65536.", }, "host_ip": { Type: schema.TypeString, Optional: true, + ForceNew: !isUpdatable, Description: "What host IP to bind the external port to.", }, "host_port": { Type: schema.TypeInt, Optional: true, + ForceNew: !isUpdatable, Description: "Number of port to expose on the host. If specified, this must be a valid port number, 0 < x < 65536. If HostNetwork is specified, this must match ContainerPort. Most containers do not need this.", }, "name": { Type: schema.TypeString, Optional: true, + ForceNew: !isUpdatable, ValidateFunc: validatePortNumOrName, Description: "If specified, this must be an IANA_SVC_NAME and unique within the pod. Each named port in a pod must have a unique name. Name for the port that can be referred to by services", }, "protocol": { Type: schema.TypeString, Optional: true, + ForceNew: !isUpdatable, Description: `Protocol for port. Must be UDP or TCP. Defaults to "TCP".`, Default: "TCP", }, @@ -503,6 +532,7 @@ func containerFields(isUpdatable, isInitContainer bool) map[string]*schema.Schem Type: schema.TypeList, Optional: true, MaxItems: 1, + ForceNew: !isUpdatable, Description: "Periodic probe of container service readiness. Container will be removed from service endpoints if the probe fails. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/pod-states#container-probes", Elem: probeSchema(), }, @@ -510,6 +540,7 @@ func containerFields(isUpdatable, isInitContainer bool) map[string]*schema.Schem Type: schema.TypeList, Optional: true, MaxItems: 1, + ForceNew: !isUpdatable, Computed: true, Description: "Compute Resources required by this container. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/persistent-volumes#resources", Elem: &schema.Resource{ @@ -520,37 +551,43 @@ func containerFields(isUpdatable, isInitContainer bool) map[string]*schema.Schem Type: schema.TypeList, Optional: true, MaxItems: 1, + ForceNew: !isUpdatable, Description: "Security options the pod should run with. More info: http://releases.k8s.io/HEAD/docs/design/security_context.md", - Elem: securityContextSchema(), + Elem: securityContextSchema(isUpdatable), }, "startup_probe": { Type: schema.TypeList, Optional: true, MaxItems: 1, + ForceNew: !isUpdatable, Description: "StartupProbe indicates that the Pod has successfully initialized. If specified, no other probes are executed until this completes successfully. If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. This can be used to provide different probe parameters at the beginning of a Pod's lifecycle, when it might take a long time to load data or warm a cache, than during steady-state operation. This cannot be updated. This is an alpha feature enabled by the StartupProbe feature flag. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", Elem: probeSchema(), }, "stdin": { Type: schema.TypeBool, Optional: true, + ForceNew: !isUpdatable, Default: false, Description: "Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. ", }, "stdin_once": { Type: schema.TypeBool, Optional: true, + ForceNew: !isUpdatable, Default: false, Description: "Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF.", }, "termination_message_path": { Type: schema.TypeString, Optional: true, + ForceNew: !isUpdatable, Default: "/dev/termination-log", Description: "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Defaults to /dev/termination-log. Cannot be updated.", }, "termination_message_policy": { Type: schema.TypeString, Optional: true, + ForceNew: !isUpdatable, ValidateFunc: validation.StringInSlice([]string{"File", "FallbackToLogsOnError"}, false), Computed: true, Description: "Optional: Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated.", @@ -558,12 +595,14 @@ func containerFields(isUpdatable, isInitContainer bool) map[string]*schema.Schem "tty": { Type: schema.TypeBool, Optional: true, + ForceNew: !isUpdatable, Default: false, Description: "Whether this container should allocate a TTY for itself", }, "volume_mount": { Type: schema.TypeList, Optional: true, + ForceNew: !isUpdatable, Computed: true, Description: "Pod volumes to mount into the container's filesystem. Cannot be updated.", Elem: &schema.Resource{ @@ -573,20 +612,10 @@ func containerFields(isUpdatable, isInitContainer bool) map[string]*schema.Schem "working_dir": { Type: schema.TypeString, Optional: true, + ForceNew: !isUpdatable, Description: "Container's working directory. If not specified, the container runtime's default will be used, which might be configured in the container image. Cannot be updated.", }, } - - if !isUpdatable { - for k := range s { - if k == "image" && !isInitContainer { - // this field is updatable for non-init containers - continue - } - s[k].ForceNew = true - } - } - return s } @@ -632,12 +661,13 @@ func probeSchema() *schema.Resource { } -func securityContextSchema() *schema.Resource { +func securityContextSchema(isUpdatable bool) *schema.Resource { m := map[string]*schema.Schema{ "allow_privilege_escalation": { Type: schema.TypeBool, Optional: true, Default: true, + ForceNew: !isUpdatable, Description: `AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN`, }, "capabilities": { @@ -650,12 +680,14 @@ func securityContextSchema() *schema.Resource { "add": { Type: schema.TypeList, Optional: true, + ForceNew: !isUpdatable, Elem: &schema.Schema{Type: schema.TypeString}, Description: "Added capabilities", }, "drop": { Type: schema.TypeList, Optional: true, + ForceNew: !isUpdatable, Elem: &schema.Schema{Type: schema.TypeString}, Description: "Removed capabilities", }, @@ -665,12 +697,14 @@ func securityContextSchema() *schema.Resource { "privileged": { Type: schema.TypeBool, Optional: true, + ForceNew: !isUpdatable, Default: false, Description: `Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false.`, }, "read_only_root_filesystem": { Type: schema.TypeBool, Optional: true, + ForceNew: !isUpdatable, Default: false, Description: "Whether this container has a read-only root filesystem. Default is false.", }, @@ -678,17 +712,20 @@ func securityContextSchema() *schema.Resource { Type: schema.TypeString, Description: "The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", Optional: true, + ForceNew: !isUpdatable, ValidateFunc: validateTypeStringNullableInt, }, "run_as_non_root": { Type: schema.TypeBool, Description: "Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", + ForceNew: !isUpdatable, Optional: true, }, "run_as_user": { Type: schema.TypeString, Description: "The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", Optional: true, + ForceNew: !isUpdatable, ValidateFunc: validateTypeStringNullableInt, }, "se_linux_options": { @@ -697,7 +734,7 @@ func securityContextSchema() *schema.Resource { Optional: true, MaxItems: 1, Elem: &schema.Resource{ - Schema: seLinuxOptionsField(), + Schema: seLinuxOptionsField(isUpdatable), }, }, } diff --git a/kubernetes/schema_job_spec.go b/kubernetes/schema_job_spec.go index f55d6bd2cb..98fdb6a80c 100644 --- a/kubernetes/schema_job_spec.go +++ b/kubernetes/schema_job_spec.go @@ -1,7 +1,9 @@ package kubernetes import ( + "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "strconv" ) func jobMetadataSchema() *schema.Schema { @@ -21,7 +23,7 @@ func jobSpecFields() map[string]*schema.Schema { ForceNew: true, MaxItems: 1, Elem: &schema.Resource{ - Schema: podSpecFields(false, false, false), + Schema: podSpecFields(false, false), }, }, } @@ -36,17 +38,18 @@ func jobSpecFields() map[string]*schema.Schema { "active_deadline_seconds": { Type: schema.TypeInt, Optional: true, - ForceNew: true, + ForceNew: false, ValidateFunc: validatePositiveInteger, Description: "Optional duration in seconds the pod may be active on the node relative to StartTime before the system will actively try to mark it failed and kill associated containers. Value must be a positive integer.", }, "backoff_limit": { Type: schema.TypeInt, Optional: true, - ForceNew: true, + ForceNew: false, ValidateFunc: validateNonNegativeInteger, Description: "Specifies the number of retries before marking this job failed. Defaults to 6", }, + // This field is immutable in Jobs. "completions": { Type: schema.TypeInt, Optional: true, @@ -58,17 +61,18 @@ func jobSpecFields() map[string]*schema.Schema { "manual_selector": { Type: schema.TypeBool, Optional: true, - ForceNew: true, - Description: "Controls generation of pod labels and pod selectors. Leave unset unless you are certain what you are doing. When false or unset, the system pick labels unique to this job and appends those labels to the pod template. When true, the user is responsyble for picking unique labels and specifying the selector. Failure to pick a unique label may cause this and other jobs to not function correctly. More info: https://git.k8s.io/community/contributors/design-proposals/selector-generation.md", + ForceNew: false, + Description: "Controls generation of pod labels and pod selectors. Leave unset unless you are certain what you are doing. When false or unset, the system pick labels unique to this job and appends those labels to the pod template. When true, the user is responsible for picking unique labels and specifying the selector. Failure to pick a unique label may cause this and other jobs to not function correctly. More info: https://git.k8s.io/community/contributors/design-proposals/selector-generation.md", }, "parallelism": { Type: schema.TypeInt, Optional: true, - ForceNew: true, + ForceNew: false, Default: 1, ValidateFunc: validatePositiveInteger, Description: "Specifies the maximum desired number of pods the job should run at any given time. The actual number of pods running in steady state will be less than this number when ((.spec.completions - .status.successful) < .spec.parallelism), i.e. when the work left to do is less than max parallelism. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/", }, + // This field is immutable in Jobs. "selector": { Type: schema.TypeList, Description: "A label query over volumes to consider for binding.", @@ -117,6 +121,7 @@ func jobSpecFields() map[string]*schema.Schema { }, }, }, + // PodTemplate fields are immutable in Jobs. "template": { Type: schema.TypeList, Description: "Describes the pod that will be created when executing a job. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/", @@ -127,12 +132,19 @@ func jobSpecFields() map[string]*schema.Schema { Schema: podTemplateFields, }, }, + // This field can be edited in place. "ttl_seconds_after_finished": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ValidateFunc: validateTypeStringNullableInt, - Description: "ttlSecondsAfterFinished limits the lifetime of a Job that has finished execution (either Complete or Failed). If this field is set, ttlSecondsAfterFinished after the Job finishes, it is eligible to be automatically deleted. When the Job is being deleted, its lifecycle guarantees (e.g. finalizers) will be honored. If this field is unset, the Job won't be automatically deleted. If this field is set to zero, the Job becomes eligible to be deleted immediately after it finishes.", + Type: schema.TypeString, + Optional: true, + ForceNew: false, + ValidateFunc: func(value interface{}, key string) ([]string, []error) { + v, err := strconv.Atoi(value.(string)) + if err != nil { + return []string{}, []error{fmt.Errorf("%s is not a valid integer", key)} + } + return validateNonNegativeInteger(v, key) + }, + Description: "ttlSecondsAfterFinished limits the lifetime of a Job that has finished execution (either Complete or Failed). If this field is set, ttlSecondsAfterFinished after the Job finishes, it is eligible to be automatically deleted. When the Job is being deleted, its lifecycle guarantees (e.g. finalizers) will be honored. If this field is unset, the Job won't be automatically deleted. If this field is set to zero, the Job becomes eligible to be deleted immediately after it finishes.", }, } diff --git a/kubernetes/schema_pod_spec.go b/kubernetes/schema_pod_spec.go index 90b918cbd9..c732a268a4 100644 --- a/kubernetes/schema_pod_spec.go +++ b/kubernetes/schema_pod_spec.go @@ -5,16 +5,13 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -func podSpecFields(isUpdatable, isDeprecated, isComputed bool) map[string]*schema.Schema { - var deprecatedMessage string - if isDeprecated { - deprecatedMessage = "This field is deprecated because template was incorrectly defined as a PodSpec preventing the definition of metadata. Please use the one under the spec field" - } +func podSpecFields(isUpdatable, isComputed bool) map[string]*schema.Schema { s := map[string]*schema.Schema{ "affinity": { Type: schema.TypeList, Optional: true, MaxItems: 1, + ForceNew: !isUpdatable, Description: "Optional pod scheduling constraints.", Elem: &schema.Resource{ Schema: affinityFields(), @@ -24,24 +21,24 @@ func podSpecFields(isUpdatable, isDeprecated, isComputed bool) map[string]*schem Type: schema.TypeInt, Optional: true, Computed: isComputed, + ForceNew: false, // always updatable ValidateFunc: validatePositiveInteger, Description: "Optional duration in seconds the pod may be active on the node relative to StartTime before the system will actively try to mark it failed and kill associated containers. Value must be a positive integer.", - Deprecated: deprecatedMessage, }, "automount_service_account_token": { Type: schema.TypeBool, Optional: true, Default: true, + ForceNew: !isUpdatable, Description: "AutomountServiceAccountToken indicates whether a service account token should be automatically mounted.", }, "container": { Type: schema.TypeList, Optional: true, - Computed: isComputed, + ForceNew: false, // always updatable Description: "List of containers belonging to the pod. Containers cannot currently be added or removed. There must be at least one container in a Pod. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/containers", - Deprecated: deprecatedMessage, Elem: &schema.Resource{ - Schema: containerFields(isUpdatable, false), + Schema: containerFields(isUpdatable), }, }, "readiness_gate": { @@ -49,7 +46,6 @@ func podSpecFields(isUpdatable, isDeprecated, isComputed bool) map[string]*schem Optional: true, Computed: true, Description: "If specified, all readiness gates will be evaluated for pod readiness. A pod is ready when all its containers are ready AND all conditions specified in the readiness gates have status equal to \"True\" More info: https://git.k8s.io/enhancements/keps/sig-network/0007-pod-ready%2B%2B.md", - Deprecated: deprecatedMessage, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "condition_type": { @@ -64,21 +60,19 @@ func podSpecFields(isUpdatable, isDeprecated, isComputed bool) map[string]*schem "init_container": { Type: schema.TypeList, Optional: true, - Computed: isComputed, - ForceNew: true, + ForceNew: !isUpdatable, Description: "List of init containers belonging to the pod. Init containers always run to completion and each must complete successfully before the next is started. More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/", - Deprecated: deprecatedMessage, Elem: &schema.Resource{ - Schema: containerFields(isUpdatable, true), + Schema: containerFields(isUpdatable), }, }, "dns_policy": { Type: schema.TypeString, Optional: true, Computed: isComputed, + ForceNew: !isUpdatable, Default: conditionalDefault(!isComputed, "ClusterFirst"), Description: "Set DNS policy for containers within the pod. Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'. Optional: Defaults to 'ClusterFirst', see [Kubernetes reference](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy).", - Deprecated: deprecatedMessage, }, "dns_config": { Type: schema.TypeList, @@ -91,6 +85,7 @@ func podSpecFields(isUpdatable, isDeprecated, isComputed bool) map[string]*schem Type: schema.TypeList, Description: "A list of DNS name server IP addresses. This will be appended to the base nameservers generated from DNSPolicy. Duplicated nameservers will be removed.", Optional: true, + ForceNew: !isUpdatable, Elem: &schema.Schema{ Type: schema.TypeString, ValidateFunc: validation.IsIPAddress, @@ -106,11 +101,13 @@ func podSpecFields(isUpdatable, isDeprecated, isComputed bool) map[string]*schem Type: schema.TypeString, Description: "Name of the option.", Required: true, + ForceNew: !isUpdatable, }, "value": { Type: schema.TypeString, Description: "Value of the option. Optional: Defaults to empty.", Optional: true, + ForceNew: !isUpdatable, }, }, }, @@ -122,19 +119,18 @@ func podSpecFields(isUpdatable, isDeprecated, isComputed bool) map[string]*schem Elem: &schema.Schema{ Type: schema.TypeString, ValidateFunc: validateName, + ForceNew: !isUpdatable, }, }, }, }, }, "enable_service_links": { - Type: schema.TypeBool, - Optional: true, - // Kubernetes API defaults this to `true`. A non-empty plan is returned when we also set this default. - Computed: true, + Type: schema.TypeBool, + Optional: true, ForceNew: !isUpdatable, - Default: nil, - Description: "Enables generating environment variables for service discovery. Optional: service links are enabled by default.", + Default: true, + Description: "Enables generating environment variables for service discovery. Defaults to true.", }, "host_aliases": { Type: schema.TypeList, @@ -142,18 +138,19 @@ func podSpecFields(isUpdatable, isDeprecated, isComputed bool) map[string]*schem ForceNew: !isUpdatable, Computed: isComputed, Description: "List of hosts and IPs that will be injected into the pod's hosts file if specified. Optional: Defaults to empty.", - Deprecated: deprecatedMessage, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "hostnames": { Type: schema.TypeList, Required: true, + ForceNew: !isUpdatable, Description: "Hostnames for the IP address.", Elem: &schema.Schema{Type: schema.TypeString}, }, "ip": { Type: schema.TypeString, Required: true, + ForceNew: !isUpdatable, Description: "IP address of the host file entry.", }, }, @@ -163,39 +160,38 @@ func podSpecFields(isUpdatable, isDeprecated, isComputed bool) map[string]*schem Type: schema.TypeBool, Optional: true, Computed: isComputed, + ForceNew: !isUpdatable, Default: conditionalDefault(!isComputed, false), Description: "Use the host's ipc namespace. Optional: Defaults to false.", - Deprecated: deprecatedMessage, }, "host_network": { Type: schema.TypeBool, Optional: true, Computed: isComputed, + ForceNew: !isUpdatable, Default: conditionalDefault(!isComputed, false), Description: "Host networking requested for this pod. Use the host's network namespace. If this option is set, the ports that will be used must be specified.", - Deprecated: deprecatedMessage, }, "host_pid": { Type: schema.TypeBool, Optional: true, Computed: isComputed, + ForceNew: !isUpdatable, Default: conditionalDefault(!isComputed, false), Description: "Use the host's pid namespace.", - Deprecated: deprecatedMessage, }, "hostname": { Type: schema.TypeString, Optional: true, Computed: true, + ForceNew: !isUpdatable, Description: "Specifies the hostname of the Pod If not specified, the pod's hostname will be set to a system-defined value.", - Deprecated: deprecatedMessage, }, "image_pull_secrets": { Type: schema.TypeList, Description: "ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. If specified, these secrets will be passed to individual puller implementations for them to use. For example, in the case of docker, only DockerConfig type secrets are honored. More info: http://kubernetes.io/docs/user-guide/images#specifying-imagepullsecrets-on-a-pod", - Deprecated: deprecatedMessage, Optional: true, Computed: true, Elem: &schema.Resource{ @@ -204,6 +200,7 @@ func podSpecFields(isUpdatable, isDeprecated, isComputed bool) map[string]*schem Type: schema.TypeString, Description: "Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names", Required: true, + ForceNew: !isUpdatable, }, }, }, @@ -212,30 +209,30 @@ func podSpecFields(isUpdatable, isDeprecated, isComputed bool) map[string]*schem Type: schema.TypeString, Optional: true, Computed: true, + ForceNew: !isUpdatable, Description: "NodeName is a request to schedule this pod onto a specific node. If it is non-empty, the scheduler simply schedules this pod onto that node, assuming that it fits resource requirements.", - Deprecated: deprecatedMessage, }, "node_selector": { Type: schema.TypeMap, Optional: true, Computed: isComputed, + ForceNew: !isUpdatable, Description: "NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node's labels for the pod to be scheduled on that node. More info: http://kubernetes.io/docs/user-guide/node-selection.", - Deprecated: deprecatedMessage, }, "priority_class_name": { Type: schema.TypeString, Optional: true, Computed: isComputed, + ForceNew: !isUpdatable, Description: `If specified, indicates the pod's priority. "system-node-critical" and "system-cluster-critical" are two special keywords which indicate the highest priorities with the former being the highest priority. Any other name must be defined by creating a PriorityClass object with that name. If not specified, the pod priority will be default or zero if there is no default.`, - Deprecated: deprecatedMessage, }, "restart_policy": { Type: schema.TypeString, Optional: true, Computed: isComputed, + ForceNew: !isUpdatable, Default: conditionalDefault(!isComputed, "Always"), Description: "Restart policy for all containers within the pod. One of Always, OnFailure, Never. More info: http://kubernetes.io/docs/user-guide/pod-states#restartpolicy.", - Deprecated: deprecatedMessage, }, "security_context": { Type: schema.TypeList, @@ -243,7 +240,6 @@ func podSpecFields(isUpdatable, isDeprecated, isComputed bool) map[string]*schem Computed: isComputed, MaxItems: 1, Description: "SecurityContext holds pod-level security attributes and common container settings. Optional: Defaults to empty", - Deprecated: deprecatedMessage, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "fs_group": { @@ -251,23 +247,27 @@ func podSpecFields(isUpdatable, isDeprecated, isComputed bool) map[string]*schem Description: "A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod: 1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw---- If unset, the Kubelet will not modify the ownership and permissions of any volume.", Optional: true, ValidateFunc: validateTypeStringNullableInt, + ForceNew: !isUpdatable, }, "run_as_group": { Type: schema.TypeString, Description: "The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container.", Optional: true, ValidateFunc: validateTypeStringNullableInt, + ForceNew: !isUpdatable, }, "run_as_non_root": { Type: schema.TypeBool, Description: "Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", Optional: true, + ForceNew: !isUpdatable, }, "run_as_user": { Type: schema.TypeString, Description: "The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container.", Optional: true, ValidateFunc: validateTypeStringNullableInt, + ForceNew: !isUpdatable, }, "se_linux_options": { Type: schema.TypeList, @@ -275,13 +275,14 @@ func podSpecFields(isUpdatable, isDeprecated, isComputed bool) map[string]*schem Optional: true, MaxItems: 1, Elem: &schema.Resource{ - Schema: seLinuxOptionsField(), + Schema: seLinuxOptionsField(isUpdatable), }, }, "supplemental_groups": { Type: schema.TypeSet, Description: "A list of groups applied to the first process run in each container, in addition to the container's primary GID. If unspecified, no groups will be added to any container.", Optional: true, + ForceNew: !isUpdatable, Elem: &schema.Schema{ Type: schema.TypeInt, }, @@ -296,11 +297,13 @@ func podSpecFields(isUpdatable, isDeprecated, isComputed bool) map[string]*schem Type: schema.TypeString, Description: "Name of a property to set.", Required: true, + ForceNew: !isUpdatable, }, "value": { Type: schema.TypeString, Description: "Value of a property to set.", Required: true, + ForceNew: !isUpdatable, }, }, }, @@ -312,30 +315,31 @@ func podSpecFields(isUpdatable, isDeprecated, isComputed bool) map[string]*schem Type: schema.TypeString, Optional: true, Computed: true, + ForceNew: !isUpdatable, Description: "ServiceAccountName is the name of the ServiceAccount to use to run this pod. More info: http://releases.k8s.io/HEAD/docs/design/service_accounts.md.", - Deprecated: deprecatedMessage, }, "share_process_namespace": { Type: schema.TypeBool, Optional: true, Default: false, + ForceNew: !isUpdatable, Description: "Share a single process namespace between all of the containers in a pod. When this is set containers will be able to view and signal processes from other containers in the same pod, and the first process in each container will not be assigned PID 1. HostPID and ShareProcessNamespace cannot both be set. Optional: Defaults to false.", }, "subdomain": { Type: schema.TypeString, Optional: true, Computed: isComputed, + ForceNew: !isUpdatable, Description: `If specified, the fully qualified Pod hostname will be "...svc.". If not specified, the pod will not have a domainname at all..`, - Deprecated: deprecatedMessage, }, "termination_grace_period_seconds": { Type: schema.TypeInt, Optional: true, Computed: isComputed, + ForceNew: !isUpdatable, Default: conditionalDefault(!isComputed, 30), ValidateFunc: validateTerminationGracePeriodSeconds, Description: "Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period will be used instead. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process.", - Deprecated: deprecatedMessage, }, "toleration": { Type: schema.TypeList, @@ -347,18 +351,21 @@ func podSpecFields(isUpdatable, isDeprecated, isComputed bool) map[string]*schem Type: schema.TypeString, Description: "Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute.", Optional: true, + ForceNew: !isUpdatable, ValidateFunc: validation.StringInSlice([]string{"NoSchedule", "PreferNoSchedule", "NoExecute"}, false), }, "key": { Type: schema.TypeString, Description: "Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys.", Optional: true, + ForceNew: !isUpdatable, }, "operator": { Type: schema.TypeString, Description: "Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category.", Default: "Equal", Optional: true, + ForceNew: !isUpdatable, ValidateFunc: validation.StringInSlice([]string{"Exists", "Equal"}, false), }, "toleration_seconds": { @@ -366,12 +373,14 @@ func podSpecFields(isUpdatable, isDeprecated, isComputed bool) map[string]*schem Type: schema.TypeString, Description: "TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system.", Optional: true, + ForceNew: !isUpdatable, ValidateFunc: validateTypeStringNullableInt, }, "value": { Type: schema.TypeString, Description: "Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string.", Optional: true, + ForceNew: !isUpdatable, }, }, }, @@ -381,25 +390,9 @@ func podSpecFields(isUpdatable, isDeprecated, isComputed bool) map[string]*schem Optional: true, Computed: true, Description: "List of volumes that can be mounted by containers belonging to the pod. More info: http://kubernetes.io/docs/user-guide/volumes", - Deprecated: deprecatedMessage, Elem: volumeSchema(isUpdatable), }, } - - if !isUpdatable { - for k := range s { - if k == "active_deadline_seconds" { - // This field is always updatable - continue - } - if k == "container" { - // Some fields are always updatable - continue - } - s[k].ForceNew = true - } - } - return s } diff --git a/kubernetes/schema_pod_template.go b/kubernetes/schema_pod_template.go index 1125be45cd..d581994593 100644 --- a/kubernetes/schema_pod_template.go +++ b/kubernetes/schema_pod_template.go @@ -15,7 +15,7 @@ func podTemplateFields(owner string) map[string]*schema.Schema { Optional: true, MaxItems: 1, Elem: &schema.Resource{ - Schema: podSpecFields(true, false, false), + Schema: podSpecFields(true, false), }, }, } diff --git a/kubernetes/schema_stateful_set_spec.go b/kubernetes/schema_stateful_set_spec.go index 1cf480cc2c..052bfc1537 100644 --- a/kubernetes/schema_stateful_set_spec.go +++ b/kubernetes/schema_stateful_set_spec.go @@ -5,7 +5,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -func statefulSetSpecFields(isUpdatable bool) map[string]*schema.Schema { +func statefulSetSpecFields() map[string]*schema.Schema { s := map[string]*schema.Schema{ "pod_management_policy": { Type: schema.TypeString, diff --git a/kubernetes/structure_job.go b/kubernetes/structure_job.go index 41fa1c166a..e45bacaf07 100644 --- a/kubernetes/structure_job.go +++ b/kubernetes/structure_job.go @@ -118,5 +118,29 @@ func patchJobSpec(pathPrefix, prefix string, d *schema.ResourceData) (PatchOpera }) } + if d.HasChange(prefix + "backoff_limit") { + v := d.Get(prefix + "backoff_limit").(int) + ops = append(ops, &ReplaceOperation{ + Path: pathPrefix + "/backoffLimit", + Value: v, + }) + } + + if d.HasChange(prefix + "manual_selector") { + v := d.Get(prefix + "manual_selector").(bool) + ops = append(ops, &ReplaceOperation{ + Path: pathPrefix + "/manualSelector", + Value: v, + }) + } + + if d.HasChange(prefix + "parallelism") { + v := d.Get(prefix + "parallelism").(int) + ops = append(ops, &ReplaceOperation{ + Path: pathPrefix + "/parallelism", + Value: v, + }) + } + return ops, nil } diff --git a/kubernetes/structures_container.go b/kubernetes/structures_container.go index ddeb742b9b..d54641b84a 100644 --- a/kubernetes/structures_container.go +++ b/kubernetes/structures_container.go @@ -452,9 +452,15 @@ func expandContainers(ctrs []interface{}) ([]v1.Container, error) { } if command, ok := ctr["command"].([]interface{}); ok { cs[i].Command = expandStringSlice(command) + } else { + // https://github.com/hashicorp/terraform-plugin-sdk/issues/142 + // Set defaults manually until List defaults are supported in the SDK. + cs[i].Command = []string{} } if args, ok := ctr["args"].([]interface{}); ok { cs[i].Args = expandStringSlice(args) + } else { + cs[i].Args = []string{} } if v, ok := ctr["resources"].([]interface{}); ok && len(v) > 0 { diff --git a/kubernetes/structures_pod.go b/kubernetes/structures_pod.go index 61c573df78..24a29b4884 100644 --- a/kubernetes/structures_pod.go +++ b/kubernetes/structures_pod.go @@ -64,8 +64,6 @@ func flattenPodSpec(in v1.PodSpec) ([]interface{}, error) { if in.EnableServiceLinks != nil { att["enable_service_links"] = *in.EnableServiceLinks - } else { - att["enable_service_links"] = true } att["host_aliases"] = flattenHostaliases(in.HostAliases) diff --git a/kubernetes/structures_replication_controller.go b/kubernetes/structures_replication_controller.go index bb250632c3..79b2101d63 100644 --- a/kubernetes/structures_replication_controller.go +++ b/kubernetes/structures_replication_controller.go @@ -1,13 +1,11 @@ package kubernetes import ( - "errors" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "k8s.io/api/core/v1" ) -func flattenReplicationControllerSpec(in v1.ReplicationControllerSpec, d *schema.ResourceData, useDeprecatedSpecFields bool) ([]interface{}, error) { +func flattenReplicationControllerSpec(in v1.ReplicationControllerSpec, d *schema.ResourceData) ([]interface{}, error) { att := make(map[string]interface{}) att["min_ready_seconds"] = in.MinReadySeconds @@ -25,25 +23,15 @@ func flattenReplicationControllerSpec(in v1.ReplicationControllerSpec, d *schema return nil, err } template := make(map[string]interface{}) - - if useDeprecatedSpecFields { - // Use deprecated fields - for k, v := range podSpec[0].(map[string]interface{}) { - template[k] = v - } - } else { - // Use new fields - template["spec"] = podSpec - template["metadata"] = flattenMetadata(in.Template.ObjectMeta, d) - } - + template["spec"] = podSpec + template["metadata"] = flattenMetadata(in.Template.ObjectMeta, d) att["template"] = []interface{}{template} } return []interface{}{att}, nil } -func expandReplicationControllerSpec(rc []interface{}, useDeprecatedSpecFields bool) (*v1.ReplicationControllerSpec, error) { +func expandReplicationControllerSpec(rc []interface{}) (*v1.ReplicationControllerSpec, error) { obj := &v1.ReplicationControllerSpec{} if len(rc) == 0 || rc[0] == nil { return obj, nil @@ -53,7 +41,7 @@ func expandReplicationControllerSpec(rc []interface{}, useDeprecatedSpecFields b obj.Replicas = ptrToInt32(int32(in["replicas"].(int))) obj.Selector = expandStringMap(in["selector"].(map[string]interface{})) - template, err := expandReplicationControllerTemplate(in["template"].([]interface{}), obj.Selector, useDeprecatedSpecFields) + template, err := expandReplicationControllerTemplate(in["template"].([]interface{})) if err != nil { return obj, err } @@ -63,39 +51,17 @@ func expandReplicationControllerSpec(rc []interface{}, useDeprecatedSpecFields b return obj, nil } -func expandReplicationControllerTemplate(rct []interface{}, selector map[string]string, useDeprecatedSpecFields bool) (*v1.PodTemplateSpec, error) { +func expandReplicationControllerTemplate(rct []interface{}) (*v1.PodTemplateSpec, error) { obj := &v1.PodTemplateSpec{} + in := rct[0].(map[string]interface{}) + metadata := in["metadata"].([]interface{}) + obj.ObjectMeta = expandMetadata(metadata) - if useDeprecatedSpecFields { - // Add labels from selector to ensure proper selection of pods by the replication controller for deprecated use case - obj.ObjectMeta.Labels = selector - - // Get pod spec from deprecated fields - podSpecDeprecated, err := expandPodSpec(rct) - if err != nil { - return obj, err - } - obj.Spec = *podSpecDeprecated - } else { - in := rct[0].(map[string]interface{}) - metadata := in["metadata"].([]interface{}) - - // Return an error if new spec fields are used but no metadata is defined to preserve the Required property of the metadata field - // cf. https://www.terraform.io/docs/extend/best-practices/deprecations.html#renaming-a-required-attribute - if len(metadata) < 1 { - return obj, errors.New("`spec.template.metadata` is Required when new 'spec.template.spec' fields are used.") - } - - // Get user defined metadata - obj.ObjectMeta = expandMetadata(metadata) - - // Get pod spec from new fields - podSpec, err := expandPodSpec(in["spec"].([]interface{})) - if err != nil { - return obj, err - } - obj.Spec = *podSpec + podSpec, err := expandPodSpec(in["spec"].([]interface{})) + if err != nil { + return obj, err } + obj.Spec = *podSpec return obj, nil } diff --git a/kubernetes/structures_stateful_set.go b/kubernetes/structures_stateful_set.go index 6b52244db7..70ffa54d64 100644 --- a/kubernetes/structures_stateful_set.go +++ b/kubernetes/structures_stateful_set.go @@ -9,7 +9,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" v1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // Expanders @@ -109,55 +108,6 @@ func expandStatefulSetSpecUpdateStrategy(s []interface{}) (*v1.StatefulSetUpdate return ust, nil } -func expandStatefulSetSelectors(s []interface{}) (*metav1.LabelSelector, error) { - obj := &metav1.LabelSelector{} - if len(s) == 0 || s[0] == nil { - return obj, nil - } - in := s[0].(map[string]interface{}) - log.Printf("[DEBUG] StatefulSet Selector: %#v", in) - if v, ok := in["match_labels"].(map[string]interface{}); ok { - log.Printf("[DEBUG] StatefulSet Selector MatchLabels: %#v", v) - ml := make(map[string]string) - for k, l := range v { - ml[k] = l.(string) - log.Printf("[DEBUG] StatefulSet Selector MatchLabel: %#v -> %#v", k, v) - } - obj.MatchLabels = ml - } - if v, ok := in["match_expressions"].([]interface{}); ok { - log.Printf("[DEBUG] StatefulSet Selector MatchExpressions: %#v", v) - me, err := expandMatchExpressions(v) - if err != nil { - return obj, err - } - obj.MatchExpressions = me - } - return obj, nil -} - -func expandMatchExpressions(in []interface{}) ([]metav1.LabelSelectorRequirement, error) { - if len(in) == 0 { - return []metav1.LabelSelectorRequirement{}, nil - } - obj := make([]metav1.LabelSelectorRequirement, len(in)) - for i, c := range in { - p := c.(map[string]interface{}) - if v, ok := p["key"].(string); ok { - obj[i].Key = v - } - if v, ok := p["operator"].(metav1.LabelSelectorOperator); ok { - obj[i].Operator = v - } - if v, ok := p["values"].(*schema.Set); ok { - obj[i].Values = schemaSetToStringArray(v) - } - } - return obj, nil -} - -// Flattners - func flattenStatefulSetSpec(spec v1.StatefulSetSpec, d *schema.ResourceData) ([]interface{}, error) { att := make(map[string]interface{})