From a8775dde2d8b5f371f52cb231fe2f476920a5e1b Mon Sep 17 00:00:00 2001 From: Matthew Yates Date: Thu, 27 May 2021 14:35:14 +0100 Subject: [PATCH] Add new resource 'kubectl_kustomize_documents' This will allow the use of https://kustomize.io/ to generate a series of k8s YAML docs, then apply them to a cluster in much the same way as 'kubectl_file_documents' --- .../kubectl_kustomize_documents.md | 22 ++++ go.mod | 1 + go.sum | 16 ++- ...data_source_kubectl_kustomize_documents.go | 100 ++++++++++++++++++ ...source_kubectl_kustomize_documents_test.go | 43 ++++++++ kubernetes/provider.go | 9 +- test/data/kustomize/helloWorld/README.md | 1 + test/data/kustomize/helloWorld/configMap.yaml | 7 ++ .../data/kustomize/helloWorld/deployment.yaml | 30 ++++++ .../kustomize/helloWorld/kustomization.yaml | 9 ++ test/data/kustomize/helloWorld/service.yaml | 12 +++ 11 files changed, 241 insertions(+), 9 deletions(-) create mode 100644 docs/data-sources/kubectl_kustomize_documents.md create mode 100644 kubernetes/data_source_kubectl_kustomize_documents.go create mode 100644 kubernetes/data_source_kubectl_kustomize_documents_test.go create mode 100644 test/data/kustomize/helloWorld/README.md create mode 100644 test/data/kustomize/helloWorld/configMap.yaml create mode 100644 test/data/kustomize/helloWorld/deployment.yaml create mode 100644 test/data/kustomize/helloWorld/kustomization.yaml create mode 100644 test/data/kustomize/helloWorld/service.yaml diff --git a/docs/data-sources/kubectl_kustomize_documents.md b/docs/data-sources/kubectl_kustomize_documents.md new file mode 100644 index 00000000..336b0eca --- /dev/null +++ b/docs/data-sources/kubectl_kustomize_documents.md @@ -0,0 +1,22 @@ +# Data Source: kubectl_kustomize_documents + +This provider provides a `data` resource `kubectl_kustomize_documents` to +render a kustomize target to a series of yaml documents. See https://kustomize.io/ +for more info. + +## Example Usage + +```hcl +data "kubectl_kustomize_documents" "manifests" { + target = "https://github.com/kubernetes-sigs/kustomize/examples/multibases?ref=v1.0.6" +} + +resource "kubectl_manifest" "test" { + count = length(data.kubectl_file_documents.manifests.documents) + yaml_body = element(data.kubectl_file_documents.manifests.documents, count.index) +} +``` + +## Attribute Reference + +* `documents` - List of YAML documents (string). diff --git a/go.mod b/go.mod index b33289f9..627db1e5 100644 --- a/go.mod +++ b/go.mod @@ -22,5 +22,6 @@ require ( k8s.io/client-go v0.21.3 k8s.io/kube-aggregator v0.21.3 k8s.io/kubectl v0.21.3 + sigs.k8s.io/kustomize/api v0.8.10 sigs.k8s.io/yaml v1.2.0 ) diff --git a/go.sum b/go.sum index da7854fa..e6b347ca 100644 --- a/go.sum +++ b/go.sum @@ -373,8 +373,9 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/googleapis/gnostic v0.5.1 h1:A8Yhf6EtqTv9RMsU6MQTyrtV1TjWlR6xU9BsZIwuTCM= +github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= github.com/gophercloud/gophercloud v0.0.0-20190208042652-bc37892e1968/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= github.com/gophercloud/utils v0.0.0-20190128072930-fbb6ab446f01/go.mod h1:wjDF8z83zTeg5eMLml5EBSlAhbF7G8DobyI1YsMuyzw= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -717,6 +718,7 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= @@ -1154,8 +1156,9 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= @@ -1187,8 +1190,9 @@ k8s.io/klog/v2 v2.8.0 h1:Q3gmuM9hKEjefWFFYF0Mat+YyFJvsUyYuwyNNJ5C9Ts= k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/kube-aggregator v0.21.3 h1:jS/6ZZGPCkBQhzGGusAd2St+KP/FtQBCXOCOo3H7/U4= k8s.io/kube-aggregator v0.21.3/go.mod h1:9OIUuR5KIsNZYP/Xsh4HBsaqbS7ICJpRz3XSKtKajRc= -k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 h1:vEx13qjvaZ4yfObSSXW7BrMc/KQBBT/Jyee8XtLf4x0= k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kubectl v0.21.3 h1:RmHvvz7tLnFmVqUzJuR44D8oE5zv1iyDojxSQllY+II= k8s.io/kubectl v0.21.3/go.mod h1:/x/kzrhfL1h1W07z6a1UTbd8SWZUYAWXskigkG4OBCg= k8s.io/metrics v0.21.3/go.mod h1:mN3Klf203Lw1hOsfg1MG7DR/kKUhwiyu8GSFCXZdz+o= @@ -1198,12 +1202,14 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.19/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/kustomize/api v0.8.8 h1:G2z6JPSSjtWWgMeWSoHdXqyftJNmMmyxXpwENGoOtGE= sigs.k8s.io/kustomize/api v0.8.8/go.mod h1:He1zoK0nk43Pc6NlV085xDXDXTNprtcyKZVm3swsdNY= +sigs.k8s.io/kustomize/api v0.8.10 h1:CqbdK/qT7JE+uVETkrVMk7pQf0fPFXk9+QQ//Q7sAtc= +sigs.k8s.io/kustomize/api v0.8.10/go.mod h1:ImeIkhUU7GIhamOtKPlkllt+fkBKL5f6/4NLhVwkinA= sigs.k8s.io/kustomize/cmd/config v0.9.10/go.mod h1:Mrby0WnRH7hA6OwOYnYpfpiY0WJIMgYrEDfwOeFdMK0= sigs.k8s.io/kustomize/kustomize/v4 v4.1.2/go.mod h1:PxBvo4WGYlCLeRPL+ziT64wBXqbgfcalOS/SXa/tcyo= -sigs.k8s.io/kustomize/kyaml v0.10.17 h1:4zrV0ym5AYa0e512q7K3Wp1u7mzoWW0xR3UHJcGWGIg= sigs.k8s.io/kustomize/kyaml v0.10.17/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg= +sigs.k8s.io/kustomize/kyaml v0.10.20 h1:L9JNKvJfCBpmYFr4tP0igpfj/pXP7nW2aXOWNtF5k1g= +sigs.k8s.io/kustomize/kyaml v0.10.20/go.mod h1:TYWhGwW9vjoRh3rWqBwB/ZOXyEGRVWe7Ggc3+KZIO+c= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= diff --git a/kubernetes/data_source_kubectl_kustomize_documents.go b/kubernetes/data_source_kubectl_kustomize_documents.go new file mode 100644 index 00000000..dce6f98d --- /dev/null +++ b/kubernetes/data_source_kubectl_kustomize_documents.go @@ -0,0 +1,100 @@ +package kubernetes + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "sigs.k8s.io/kustomize/api/krusty" + "sigs.k8s.io/kustomize/api/resmap" + "sigs.k8s.io/kustomize/api/types" + "sigs.k8s.io/kustomize/api/filesys" +) + +func dataSourceKubectlKustomizeDocuments() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceKubectlKustomizeDocumentsRead, + Schema: map[string]*schema.Schema{ + "target": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "load_restrictor": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "rootOnly", + }, + "add_managed_by_label": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "documents": &schema.Schema{ + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Computed: true, + }, + }, + } +} + +func dataSourceKubectlKustomizeDocumentsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + target := d.Get("target").(string) + + opts, err := makeKustOpts(d) + if err != nil { + return diag.FromErr(err) + } + + k := krusty.MakeKustomizer(opts) + memFS := filesys.MakeFsOnDisk() + + rm, err := k.Run(memFS, target) + if err != nil { + return diag.FromErr(fmt.Errorf("error rendering kustomization: %w", err)) + } + + documents, err := readFromResMap(rm) + if err != nil { + return diag.FromErr(fmt.Errorf("error reading documents: %w", err)) + } + + d.SetId(target) + d.Set("documents", documents) + return nil +} + +func makeKustOpts(d *schema.ResourceData) (*krusty.Options, error) { + opts := &krusty.Options{} + + rName := d.Get("load_restrictor").(string) + switch rName { + case "none": + opts.LoadRestrictions = types.LoadRestrictionsNone + case "rootOnly": + opts.LoadRestrictions = types.LoadRestrictionsRootOnly + default: + return nil, fmt.Errorf("invalid restrictor '%s'", rName) + } + + opts.AddManagedbyLabel = d.Get("add_managed_by_label").(bool) + + return opts, nil +} + +func readFromResMap(rm resmap.ResMap) ([]string, error) { + docs := make([]string, 0) + + for _, res := range rm.Resources() { + b, err := res.AsYAML() + if err != nil { + return nil, err + } + + docs = append(docs, string(b)) + } + + return docs, nil +} diff --git a/kubernetes/data_source_kubectl_kustomize_documents_test.go b/kubernetes/data_source_kubectl_kustomize_documents_test.go new file mode 100644 index 00000000..cae22b52 --- /dev/null +++ b/kubernetes/data_source_kubectl_kustomize_documents_test.go @@ -0,0 +1,43 @@ +package kubernetes + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "testing" +) + +var kustTargetUrl = "https://github.com/kubernetes-sigs/kustomize/examples/multibases?ref=v1.0.6" + +func TestAccKubectlDataSourceKustomizeDocuments_url(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: nil, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: kubectlKustomizeDocumentsConfig(kustTargetUrl), + Check: resource.TestCheckResourceAttr("data.kubectl_kustomize_documents.test", "documents.#", "3"), + }, + }, + }) +} + +func TestAccKubectlDataSourceKustomizeDocuments_localDir(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: nil, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: kubectlKustomizeDocumentsConfig("../test/data/kustomize/helloWorld"), + Check: resource.TestCheckResourceAttr("data.kubectl_kustomize_documents.test", "documents.#", "3"), + }, + }, + }) +} + +func kubectlKustomizeDocumentsConfig(target string) string { + return fmt.Sprintf(` +data "kubectl_kustomize_documents" "test" { + target = "%s" +} + `, target) +} diff --git a/kubernetes/provider.go b/kubernetes/provider.go index eb5830b8..77748db0 100644 --- a/kubernetes/provider.go +++ b/kubernetes/provider.go @@ -157,10 +157,11 @@ func Provider() *schema.Provider { }, DataSourcesMap: map[string]*schema.Resource{ - "kubectl_filename_list": dataSourceKubectlFilenameList(), - "kubectl_file_documents": dataSourceKubectlFileDocuments(), - "kubectl_path_documents": dataSourceKubectlPathDocuments(), - "kubectl_server_version": dataSourceKubectlServerVersion(), + "kubectl_filename_list": dataSourceKubectlFilenameList(), + "kubectl_file_documents": dataSourceKubectlFileDocuments(), + "kubectl_path_documents": dataSourceKubectlPathDocuments(), + "kubectl_server_version": dataSourceKubectlServerVersion(), + "kubectl_kustomize_documents": dataSourceKubectlKustomizeDocuments(), }, ResourcesMap: map[string]*schema.Resource{ diff --git a/test/data/kustomize/helloWorld/README.md b/test/data/kustomize/helloWorld/README.md new file mode 100644 index 00000000..9aa38eb2 --- /dev/null +++ b/test/data/kustomize/helloWorld/README.md @@ -0,0 +1 @@ +Example sourced from https://github.com/kubernetes-sigs/kustomize/tree/v3.3.1/examples/helloWorld diff --git a/test/data/kustomize/helloWorld/configMap.yaml b/test/data/kustomize/helloWorld/configMap.yaml new file mode 100644 index 00000000..e335ab8c --- /dev/null +++ b/test/data/kustomize/helloWorld/configMap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: the-map +data: + altGreeting: "Good Morning!" + enableRisky: "false" diff --git a/test/data/kustomize/helloWorld/deployment.yaml b/test/data/kustomize/helloWorld/deployment.yaml new file mode 100644 index 00000000..6e794090 --- /dev/null +++ b/test/data/kustomize/helloWorld/deployment.yaml @@ -0,0 +1,30 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: the-deployment +spec: + replicas: 3 + template: + metadata: + labels: + deployment: hello + spec: + containers: + - name: the-container + image: monopole/hello:1 + command: ["/hello", + "--port=8080", + "--enableRiskyFeature=$(ENABLE_RISKY)"] + ports: + - containerPort: 8080 + env: + - name: ALT_GREETING + valueFrom: + configMapKeyRef: + name: the-map + key: altGreeting + - name: ENABLE_RISKY + valueFrom: + configMapKeyRef: + name: the-map + key: enableRisky diff --git a/test/data/kustomize/helloWorld/kustomization.yaml b/test/data/kustomize/helloWorld/kustomization.yaml new file mode 100644 index 00000000..41965951 --- /dev/null +++ b/test/data/kustomize/helloWorld/kustomization.yaml @@ -0,0 +1,9 @@ +# Example configuration for the webserver +# at https://github.com/monopole/hello +commonLabels: + app: hello + +resources: +- deployment.yaml +- service.yaml +- configMap.yaml diff --git a/test/data/kustomize/helloWorld/service.yaml b/test/data/kustomize/helloWorld/service.yaml new file mode 100644 index 00000000..e238f700 --- /dev/null +++ b/test/data/kustomize/helloWorld/service.yaml @@ -0,0 +1,12 @@ +kind: Service +apiVersion: v1 +metadata: + name: the-service +spec: + selector: + deployment: hello + type: LoadBalancer + ports: + - protocol: TCP + port: 8666 + targetPort: 8080