Skip to content

Commit

Permalink
Merge pull request #50 from gavinbunney/dev/namespace
Browse files Browse the repository at this point in the history
Added support for `override_namespace`
  • Loading branch information
gavinbunney authored Oct 12, 2020
2 parents e98aa79 + c87fec8 commit c2a7e92
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 2 deletions.
1 change: 1 addition & 0 deletions docs/resources/kubectl_manifest.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
41 changes: 39 additions & 2 deletions kubernetes/resource_kubectl_manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -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 {
Expand Down Expand Up @@ -331,14 +339,19 @@ 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,
Sensitive: true,
},
"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": {
Expand Down Expand Up @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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))
Expand Down
177 changes: 177 additions & 0 deletions kubernetes/resource_kubectl_manifest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -69,13 +70,186 @@ 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 = <<EOT
apiVersion: v1
kind: Namespace
metadata:
name: %s
EOT
}
resource "kubectl_manifest" "test" {
depends_on = [kubectl_manifest.ns]
override_namespace = "%s"
yaml_body = <<EOT
%s
EOT
}
`, namespace, namespace, yaml_body)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckkubectlDestroy,
Steps: []resource.TestStep{
{
Config: config,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("kubectl_manifest.test", "namespace", namespace),
resource.TestCheckResourceAttr("kubectl_manifest.test", "override_namespace", namespace),
resource.TestCheckResourceAttr("kubectl_manifest.test", "yaml_body", yaml_body+"\n"),
resource.TestCheckResourceAttr("kubectl_manifest.test", "yaml_body_parsed", fmt.Sprintf(`apiVersion: v1
data: (sensitive value)
kind: Secret
metadata:
name: mysecret
namespace: %s
type: Opaque
`, namespace)),
resource.TestCheckResourceAttr("kubectl_manifest.test", "yaml_incluster", fmt.Sprintf(`apiVersion=v1,kind=Secret,metadata.name=mysecret,metadata.namespace=%s,type=Opaque`, namespace)),
),
},
},
})
}

func TestAccKubectlSetNamespace(t *testing.T) {

namespace := "dev-" + acctest.RandString(10)
yaml_body := `
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
`

config := fmt.Sprintf(`
resource "kubectl_manifest" "ns" {
yaml_body = <<EOT
apiVersion: v1
kind: Namespace
metadata:
name: %s
EOT
}
resource "kubectl_manifest" "test" {
depends_on = [kubectl_manifest.ns]
override_namespace = "%s"
yaml_body = <<EOT
%s
EOT
}
`, namespace, namespace, yaml_body)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckkubectlDestroy,
Steps: []resource.TestStep{
{
Config: config,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("kubectl_manifest.test", "id", "/api/v1/namespaces/"+namespace+"/secrets/mysecret"),
resource.TestCheckResourceAttr("kubectl_manifest.test", "namespace", namespace),
resource.TestCheckResourceAttr("kubectl_manifest.test", "override_namespace", namespace),
resource.TestCheckResourceAttr("kubectl_manifest.test", "yaml_body", yaml_body+"\n"),
resource.TestCheckResourceAttr("kubectl_manifest.test", "yaml_body_parsed", fmt.Sprintf(`apiVersion: v1
data: (sensitive value)
kind: Secret
metadata:
name: mysecret
namespace: %s
type: Opaque
`, namespace)),
resource.TestCheckResourceAttr("kubectl_manifest.test", "yaml_incluster", fmt.Sprintf(`apiVersion=v1,kind=Secret,metadata.name=mysecret,metadata.namespace=%s,type=Opaque`, namespace)),
),
},
},
})
}

func TestAccKubectlSetNamespace_nonnamespaced_resource(t *testing.T) {

namespace := "dev-" + acctest.RandString(10)
yaml_body := fmt.Sprintf(`
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: mysuperrole-%s
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "watch", "list"]
`, namespace)

config := fmt.Sprintf(`
resource "kubectl_manifest" "test" {
override_namespace = "%s"
yaml_body = <<EOT
%s
EOT
}
`, namespace, yaml_body)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckkubectlDestroy,
Steps: []resource.TestStep{
{
Config: config,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("kubectl_manifest.test", "namespace", namespace),
resource.TestCheckResourceAttr("kubectl_manifest.test", "override_namespace", namespace),
resource.TestCheckResourceAttr("kubectl_manifest.test", "yaml_body", yaml_body+"\n"),
resource.TestCheckResourceAttr("kubectl_manifest.test", "yaml_body_parsed", fmt.Sprintf(`apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: mysuperrole-%s
namespace: %s
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- watch
- list
`, namespace, namespace)),
resource.TestCheckResourceAttr("kubectl_manifest.test", "yaml_incluster", fmt.Sprintf(`apiVersion=rbac.authorization.k8s.io/v1,kind=ClusterRole,metadata.name=mysuperrole-%s,rules.#=1,rules.0.apiGroups.#=1,rules.0.apiGroups.0=,rules.0.resources.#=1,rules.0.resources.0=secrets,rules.0.verbs.#=3,rules.0.verbs.0=get,rules.0.verbs.1=watch,rules.0.verbs.2=list`, namespace)),
),
},
},
})
}

func TestAccKubectlSensitiveFields_secret(t *testing.T) {

yaml_body := `
apiVersion: v1
kind: Secret
metadata:
name: mysecret
namespace: default
type: Opaque
data:
USER_NAME: YWRtaW4=
Expand All @@ -98,12 +272,15 @@ resource "kubectl_manifest" "test" {
{
Config: config,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("kubectl_manifest.test", "namespace", "default"),
resource.TestCheckNoResourceAttr("kubectl_manifest.test", "override_namespace"),
resource.TestCheckResourceAttr("kubectl_manifest.test", "yaml_body", yaml_body+"\n"),
resource.TestCheckResourceAttr("kubectl_manifest.test", "yaml_body_parsed", `apiVersion: v1
data: (sensitive value)
kind: Secret
metadata:
name: mysecret
namespace: default
type: Opaque
`),
),
Expand Down

0 comments on commit c2a7e92

Please sign in to comment.