diff --git a/docs/resources/kubectl_manifest.md b/docs/resources/kubectl_manifest.md index 467a3911..2f9fe797 100644 --- a/docs/resources/kubectl_manifest.md +++ b/docs/resources/kubectl_manifest.md @@ -37,6 +37,7 @@ YAML * `sensitive_fields` - Optional. List of fields (dot-syntax) which are sensitive and should be obfuscated in output. Defaults to `["data"]` for Secrets. * `force_new` - Optional. Forces delete & create of resources if the `yaml_body` changes. Default `false`. * `ignore_fields` - Optional. List of map fields to ignore when applying the manifest. See below for more details. +* `override_namespace` - Optional. Override the namespace to apply the kubernetes resource to, ignoring any declared namespace in the `yaml_body`. * `validate_schema` - Optional. Setting to `false` will mimic `kubectl apply --validate=false` mode. Default `true`. * `wait` - Optional. Set this flag to wait or not for finalized to complete for deleted objects. Default `false`. * `wait_for_rollout` - Optional. Set this flag to wait or not for Deployments and APIService to complete rollout. Default `true`. diff --git a/kubernetes/resource_kubectl_manifest.go b/kubernetes/resource_kubectl_manifest.go index 537e0b87..2c0f8257 100644 --- a/kubernetes/resource_kubectl_manifest.go +++ b/kubernetes/resource_kubectl_manifest.go @@ -204,6 +204,10 @@ metadata: return err } + if overrideNamespace, ok := d.GetOk("override_namespace"); ok { + parsedYaml.unstruct.SetNamespace(overrideNamespace.(string)) + } + // set calculated fields based on parsed yaml values _ = d.SetNew("api_version", parsedYaml.unstruct.GetAPIVersion()) _ = d.SetNew("kind", parsedYaml.unstruct.GetKind()) @@ -218,6 +222,10 @@ metadata: obfuscatedYaml.unstruct.Object = make(map[string]interface{}) } + if overrideNamespace, ok := d.GetOk("override_namespace"); ok { + obfuscatedYaml.unstruct.SetNamespace(overrideNamespace.(string)) + } + var sensitiveFields []string sensitiveFieldsRaw, hasSensitiveFields := d.GetOk("sensitive_fields") if hasSensitiveFields { @@ -331,6 +339,11 @@ metadata: Computed: true, ForceNew: true, }, + "override_namespace": { + Type: schema.TypeString, + Description: "Override the namespace to apply the kubernetes resource to", + Optional: true, + }, "yaml_body": { Type: schema.TypeString, Required: true, @@ -338,7 +351,7 @@ metadata: }, "yaml_body_parsed": { Type: schema.TypeString, - Description: "Yaml body that is being applied, with sensitive values unobfuscated", + Description: "Yaml body that is being applied, with sensitive values obfuscated", Computed: true, }, "sensitive_fields": { @@ -406,6 +419,10 @@ func resourceKubectlManifestApply(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("failed to parse kubernetes resource: %+v", err) } + if overrideNamespace, ok := d.GetOk("override_namespace"); ok { + manifest.unstruct.SetNamespace(overrideNamespace.(string)) + } + log.Printf("[DEBUG] %v apply kubernetes resource:\n%s", manifest, yaml) // Create a client to talk to the resource API based on the APIVersion and Kind @@ -416,6 +433,18 @@ func resourceKubectlManifestApply(d *schema.ResourceData, meta interface{}) erro } // Update the resource in Kubernetes, using a temp file + yamlJson, err := manifest.unstruct.MarshalJSON() + if err != nil { + return fmt.Errorf("%v failed to convert object to json: %+v", manifest, err) + } + + yamlParsed, err := yamlWriter.JSONToYAML(yamlJson) + if err != nil { + return fmt.Errorf("%v failed to convert json to yaml: %+v", manifest, err) + } + + yaml = string(yamlParsed) + tmpfile, _ := ioutil.TempFile("", "*kubectl_manifest.yaml") _, _ = tmpfile.Write([]byte(yaml)) _ = tmpfile.Close() @@ -449,7 +478,7 @@ func resourceKubectlManifestApply(d *schema.ResourceData, meta interface{}) erro err = applyOptions.Run() _ = os.Remove(tmpfile.Name()) if err != nil { - return fmt.Errorf("%v failed to run apply options: %+v", manifest, err) + return fmt.Errorf("%v failed to run apply: %+v", manifest, err) } log.Printf("[INFO] %v manifest applied, fetch resource from kubernetes", manifest) @@ -506,6 +535,10 @@ func resourceKubectlManifestRead(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("failed to parse kubernetes resource: %+v", err) } + if overrideNamespace, ok := d.GetOk("override_namespace"); ok { + manifest.unstruct.SetNamespace(overrideNamespace.(string)) + } + // Create a client to talk to the resource API based on the APIVersion and Kind // defined in the YAML client, err := getRestClientFromUnstructured(manifest, meta.(*KubeProvider)) @@ -558,6 +591,10 @@ func resourceKubectlManifestDelete(d *schema.ResourceData, meta interface{}) err return fmt.Errorf("failed to parse kubernetes resource: %+v", err) } + if overrideNamespace, ok := d.GetOk("override_namespace"); ok { + manifest.unstruct.SetNamespace(overrideNamespace.(string)) + } + log.Printf("[DEBUG] %v delete kubernetes resource:\n%s", manifest, yaml) client, err := getRestClientFromUnstructured(manifest, meta.(*KubeProvider)) diff --git a/kubernetes/resource_kubectl_manifest_test.go b/kubernetes/resource_kubectl_manifest_test.go index 83959209..a20d374b 100644 --- a/kubernetes/resource_kubectl_manifest_test.go +++ b/kubernetes/resource_kubectl_manifest_test.go @@ -2,6 +2,7 @@ package kubernetes import ( "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -69,6 +70,178 @@ spec: }) } +func TestAccKubectlOverrideNamespace(t *testing.T) { + + namespace := "dev-" + acctest.RandString(10) + yaml_body := ` +apiVersion: v1 +kind: Secret +metadata: + name: mysecret + namespace: prod +type: Opaque +data: +` + + config := fmt.Sprintf(` +resource "kubectl_manifest" "ns" { + yaml_body = <