diff --git a/resource_kustomization.go b/resource_kustomization.go index a46b7c1..996ddee 100644 --- a/resource_kustomization.go +++ b/resource_kustomization.go @@ -43,16 +43,17 @@ func kustomizationResource() *schema.Resource { } func getGVR(gvk k8sschema.GroupVersionKind, cs *kubernetes.Clientset) (gvr k8sschema.GroupVersionResource, err error) { - gk := k8sschema.GroupKind{Group: gvk.Group, Kind: gvk.Kind} agr, err := restmapper.GetAPIGroupResources(cs.Discovery()) if err != nil { return gvr, fmt.Errorf("discovering API group resources failed: %s", err) } rm := restmapper.NewDiscoveryRESTMapper(agr) + + gk := k8sschema.GroupKind{Group: gvk.Group, Kind: gvk.Kind} mapping, err := rm.RESTMapping(gk, gvk.Version) if err != nil { - return gvr, fmt.Errorf("mapping GroupKind failed: %s", err) + return gvr, fmt.Errorf("mapping GroupKind failed for '%s': %s", gvk, err) } gvr = mapping.Resource @@ -82,14 +83,71 @@ func kustomizationResourceCreate(d *schema.ResourceData, m interface{}) error { return fmt.Errorf("ResourceCreate: %s", err) } - gvr, err := getGVR(u.GroupVersionKind(), clientset) + stateConf := &resource.StateChangeConf{ + Target: []string{"existing"}, + Pending: []string{"pending"}, + Timeout: d.Timeout(schema.TimeoutCreate), + Refresh: func() (interface{}, string, error) { + // CRDs: wait for GroupVersionKind to exist + gvr, err := getGVR(u.GroupVersionKind(), clientset) + if err != nil { + return nil, "pending", nil + } + + return gvr, "existing", nil + }, + } + gvrResp, err := stateConf.WaitForState() if err != nil { - return fmt.Errorf("ResourceCreate: %s", err) + return fmt.Errorf( + "ResourceCreate: GroupVersionKind '%s' %s", + u.GroupVersionKind(), + err) } + + gvr := gvrResp.(k8sschema.GroupVersionResource) namespace := u.GetNamespace() setLastAppliedConfig(u, srcJSON) + if namespace != "" { + // wait for the namespace to exist + nsGvk := k8sschema.GroupVersionKind{ + Group: "", + Version: "", + Kind: "Namespace"} + nsGvr, err := getGVR(nsGvk, clientset) + if err != nil { + return fmt.Errorf("ResourceCreate: %s", err) + } + + stateConf := &resource.StateChangeConf{ + Target: []string{"existing"}, + Pending: []string{"pending"}, + Timeout: d.Timeout(schema.TimeoutCreate), + Refresh: func() (interface{}, string, error) { + resp, err := client. + Resource(nsGvr). + Get(namespace, k8smetav1.GetOptions{}) + if err != nil { + if k8serrors.IsNotFound(err) { + return nil, "pending", nil + } + return nil, "", err + } + + return resp, "existing", nil + }, + } + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "ResourceCreate: namespace '%s' %s", + namespace, + err) + } + } + resp, err := client. Resource(gvr). Namespace(namespace). @@ -361,6 +419,12 @@ func kustomizationResourceDelete(d *schema.ResourceData, m interface{}) error { Namespace(namespace). Delete(name, nil) if err != nil { + // Consider not found during deletion a success + if k8serrors.IsNotFound(err) { + d.SetId("") + return nil + } + return fmt.Errorf("ResourceDelete: deleting '%s' failed: %s", gvr, err) } @@ -377,7 +441,7 @@ func kustomizationResourceDelete(d *schema.ResourceData, m interface{}) error { if k8serrors.IsNotFound(err) { return nil, "", nil } - return nil, "", fmt.Errorf("ResourceDelete: refreshing '%s' state failed: %s", gvr, err) + return nil, "", fmt.Errorf("refreshing '%s' state failed: %s", gvr, err) } return resp, "deleting", nil diff --git a/resource_kustomization_test.go b/resource_kustomization_test.go index 0ba6f9e..6333350 100644 --- a/resource_kustomization_test.go +++ b/resource_kustomization_test.go @@ -245,6 +245,76 @@ resource "kustomization_resource" "dep1" { ` } +// +// +// CRD Test +func TestAccResourceKustomization_crd(t *testing.T) { + + resource.Test(t, resource.TestCase{ + //PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // + // + // Applying both namespaced and cluster wide CRD + // and one custom object of each CRD + { + Config: testAccResourceKustomizationConfig_crd("test_kustomizations/crd"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet( + "kustomization_resource.clusteredcrd", + "id"), + resource.TestCheckResourceAttrSet( + "kustomization_resource.namespacedcrd", + "id"), + resource.TestCheckResourceAttrSet( + "kustomization_resource.clusteredco", + "id"), + resource.TestCheckResourceAttrSet( + "kustomization_resource.namespacedco", + "id"), + resource.TestCheckResourceAttrSet( + "kustomization_resource.ns", + "id"), + ), + }, + // + // + // Test state import + { + ResourceName: "kustomization_resource.test[\"apiextensions.k8s.io_v1beta1_CustomResourceDefinition|~X|clusteredcrds.test.example.com\"]", + ImportStateId: "apiextensions.k8s.io_v1beta1_CustomResourceDefinition|~X|clusteredcrds.test.example.com", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccResourceKustomizationConfig_crd(path string) string { + return testAccDataSourceKustomizationConfig_basic(path) + ` +resource "kustomization_resource" "clusteredcrd" { + manifest = data.kustomization.test.manifests["apiextensions.k8s.io_v1beta1_CustomResourceDefinition|~X|clusteredcrds.test.example.com"] +} + +resource "kustomization_resource" "namespacedcrd" { + manifest = data.kustomization.test.manifests["apiextensions.k8s.io_v1beta1_CustomResourceDefinition|~X|namespacedcrds.test.example.com"] +} + +resource "kustomization_resource" "clusteredco" { + manifest = data.kustomization.test.manifests["test.example.com_v1alpha1_Clusteredcrd|~X|clusteredco"] +} + +resource "kustomization_resource" "namespacedco" { + manifest = data.kustomization.test.manifests["test.example.com_v1alpha1_Namespacedcrd|test-crd|namespacedco"] +} + +resource "kustomization_resource" "ns" { + manifest = data.kustomization.test.manifests["~G_v1_Namespace|~X|test-crd"] +} +` +} + // // // Test check functions diff --git a/test_kustomizations/crd/co.yaml b/test_kustomizations/crd/co.yaml new file mode 100644 index 0000000..37a6eae --- /dev/null +++ b/test_kustomizations/crd/co.yaml @@ -0,0 +1,12 @@ +apiVersion: test.example.com/v1alpha1 +kind: Namespacedcrd +metadata: + name: namespacedco + namespace: test-crd +spec: {} +--- +apiVersion: test.example.com/v1alpha1 +kind: Clusteredcrd +metadata: + name: clusteredco +spec: {} diff --git a/test_kustomizations/crd/crd.yaml b/test_kustomizations/crd/crd.yaml new file mode 100644 index 0000000..c8db44b --- /dev/null +++ b/test_kustomizations/crd/crd.yaml @@ -0,0 +1,27 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: namespacedcrds.test.example.com +spec: + group: test.example.com + names: + kind: Namespacedcrd + plural: namespacedcrds + shortNames: + - ncrds + scope: Namespaced + version: v1alpha1 +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: clusteredcrds.test.example.com +spec: + group: test.example.com + names: + kind: Clusteredcrd + plural: clusteredcrds + shortNames: + - ccrds + scope: Cluster + version: v1alpha1 diff --git a/test_kustomizations/crd/kustomization.yaml b/test_kustomizations/crd/kustomization.yaml new file mode 100644 index 0000000..04951b0 --- /dev/null +++ b/test_kustomizations/crd/kustomization.yaml @@ -0,0 +1,4 @@ +resources: +- namespace.yaml +- crd.yaml +- co.yaml diff --git a/test_kustomizations/crd/namespace.yaml b/test_kustomizations/crd/namespace.yaml new file mode 100644 index 0000000..d0f606a --- /dev/null +++ b/test_kustomizations/crd/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: test-crd