diff --git a/resource_kustomization.go b/resource_kustomization.go index 996ddee..30e1036 100644 --- a/resource_kustomization.go +++ b/resource_kustomization.go @@ -12,12 +12,8 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" k8smetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - k8sunstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - k8sruntime "k8s.io/apimachinery/pkg/runtime" k8sschema "k8s.io/apimachinery/pkg/runtime/schema" k8stypes "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/restmapper" ) func kustomizationResource() *schema.Resource { @@ -42,37 +38,6 @@ func kustomizationResource() *schema.Resource { } } -func getGVR(gvk k8sschema.GroupVersionKind, cs *kubernetes.Clientset) (gvr k8sschema.GroupVersionResource, err error) { - 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 for '%s': %s", gvk, err) - } - - gvr = mapping.Resource - - return gvr, nil -} - -func parseJSON(json string) (ur *k8sunstructured.Unstructured, err error) { - body := []byte(json) - u, err := k8sruntime.Decode(k8sunstructured.UnstructuredJSONScheme, body) - if err != nil { - return ur, err - } - - ur = u.(*k8sunstructured.Unstructured) - - return ur, nil -} - func kustomizationResourceCreate(d *schema.ResourceData, m interface{}) error { client := m.(*Config).Client clientset := m.(*Config).Clientset @@ -200,57 +165,33 @@ func kustomizationResourceDiff(d *schema.ResourceDiff, m interface{}) error { client := m.(*Config).Client clientset := m.(*Config).Clientset + originalJSON, modifiedJSON := d.GetChange("manifest") + if !d.HasChange("manifest") { return nil } - oldJSON, newJSON := d.GetChange("manifest") - - if oldJSON.(string) == "" { + if originalJSON.(string) == "" { return nil } - n, err := parseJSON(newJSON.(string)) - if err != nil { - return fmt.Errorf("ResourceDiff: %s", err) - } - o, err := parseJSON(oldJSON.(string)) - if err != nil { - return fmt.Errorf("ResourceDiff: %s", err) - } - - gvr, err := getGVR(o.GroupVersionKind(), clientset) - if err != nil { - return fmt.Errorf("ResourceDiff: %s", err) - } - namespace := o.GetNamespace() - name := o.GetName() - - setLastAppliedConfig(o, oldJSON.(string)) - setLastAppliedConfig(n, newJSON.(string)) - - original, err := o.MarshalJSON() + u, err := parseJSON(originalJSON.(string)) if err != nil { return fmt.Errorf("ResourceDiff: %s", err) } - modified, err := n.MarshalJSON() + gvr, err := getGVR(u.GroupVersionKind(), clientset) if err != nil { return fmt.Errorf("ResourceDiff: %s", err) } + namespace := u.GetNamespace() + name := u.GetName() - c, err := client. - Resource(gvr). - Namespace(namespace). - Get(name, k8smetav1.GetOptions{}) - if err != nil { - if k8serrors.IsNotFound(err) { - return nil - } - return fmt.Errorf("ResourceDiff: reading '%s' failed: %s", gvr, err) - } - - current, err := c.MarshalJSON() + original, modified, current, err := getOriginalModifiedCurrent( + originalJSON.(string), + modifiedJSON.(string), + true, + m) if err != nil { return fmt.Errorf("ResourceDiff: %s", err) } @@ -325,54 +266,33 @@ func kustomizationResourceUpdate(d *schema.ResourceData, m interface{}) error { client := m.(*Config).Client clientset := m.(*Config).Clientset - oldJSON, newJSON := d.GetChange("manifest") + originalJSON, modifiedJSON := d.GetChange("manifest") if !d.HasChange("manifest") { msg := fmt.Sprintf( "Update called without change. old: %s, new: %s", - oldJSON, - newJSON) + originalJSON, + modifiedJSON) return errors.New(msg) } - n, err := parseJSON(newJSON.(string)) - if err != nil { - return fmt.Errorf("ResourceUpdate: %s", err) - } - o, err := parseJSON(oldJSON.(string)) + u, err := parseJSON(originalJSON.(string)) if err != nil { return fmt.Errorf("ResourceUpdate: %s", err) } - gvr, err := getGVR(o.GroupVersionKind(), clientset) - if err != nil { - return fmt.Errorf("ResourceUpdate: %s", err) - } - namespace := o.GetNamespace() - name := o.GetName() - - setLastAppliedConfig(o, oldJSON.(string)) - setLastAppliedConfig(n, newJSON.(string)) - - original, err := o.MarshalJSON() - if err != nil { - return fmt.Errorf("ResourceUpdate: %s", err) - } - - modified, err := n.MarshalJSON() + gvr, err := getGVR(u.GroupVersionKind(), clientset) if err != nil { return fmt.Errorf("ResourceUpdate: %s", err) } + namespace := u.GetNamespace() + name := u.GetName() - c, err := client. - Resource(gvr). - Namespace(namespace). - Get(name, k8smetav1.GetOptions{}) - if err != nil { - return fmt.Errorf("ResourceUpdate: reading '%s' failed: %s", gvr, err) - } - - current, err := c.MarshalJSON() + original, modified, current, err := getOriginalModifiedCurrent( + originalJSON.(string), + modifiedJSON.(string), + false, + m) if err != nil { return fmt.Errorf("ResourceUpdate: %s", err) } diff --git a/util.go b/util.go index 4144c20..c4d4346 100644 --- a/util.go +++ b/util.go @@ -4,7 +4,13 @@ import ( "fmt" k8scorev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + k8smetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sunstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + k8sruntime "k8s.io/apimachinery/pkg/runtime" + k8sschema "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/restmapper" "k8s.io/apimachinery/pkg/util/jsonmergepatch" "k8s.io/apimachinery/pkg/util/mergepatch" @@ -25,6 +31,59 @@ func getLastAppliedConfig(u *k8sunstructured.Unstructured) string { return u.GetAnnotations()[lastAppliedConfig] } +func getOriginalModifiedCurrent(originalJSON string, modifiedJSON string, currentAllowNotFound bool, m interface{}) (original []byte, modified []byte, current []byte, err error) { + client := m.(*Config).Client + clientset := m.(*Config).Clientset + + n, err := parseJSON(modifiedJSON) + if err != nil { + return nil, nil, nil, err + } + o, err := parseJSON(originalJSON) + if err != nil { + return nil, nil, nil, err + } + + setLastAppliedConfig(o, originalJSON) + setLastAppliedConfig(n, modifiedJSON) + + gvr, err := getGVR(o.GroupVersionKind(), clientset) + if err != nil { + return nil, nil, nil, err + } + namespace := o.GetNamespace() + name := o.GetName() + + original, err = o.MarshalJSON() + if err != nil { + return nil, nil, nil, err + } + + modified, err = n.MarshalJSON() + if err != nil { + return nil, nil, nil, err + } + + c, err := client. + Resource(gvr). + Namespace(namespace). + Get(name, k8smetav1.GetOptions{}) + if err != nil { + if k8serrors.IsNotFound(err) && currentAllowNotFound { + return original, modified, current, nil + } + + return nil, nil, nil, fmt.Errorf("reading '%s' failed: %s", gvr, err) + } + + current, err = c.MarshalJSON() + if err != nil { + return nil, nil, nil, err + } + + return original, modified, current, nil +} + func getPatch(original []byte, modified []byte, current []byte) (patch []byte, err error) { preconditions := []mergepatch.PreconditionFunc{ mergepatch.RequireKeyUnchanged("apiVersion"), @@ -37,3 +96,34 @@ func getPatch(original []byte, modified []byte, current []byte) (patch []byte, e } return patch, nil } + +func getGVR(gvk k8sschema.GroupVersionKind, cs *kubernetes.Clientset) (gvr k8sschema.GroupVersionResource, err error) { + 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 for '%s': %s", gvk, err) + } + + gvr = mapping.Resource + + return gvr, nil +} + +func parseJSON(json string) (ur *k8sunstructured.Unstructured, err error) { + body := []byte(json) + u, err := k8sruntime.Decode(k8sunstructured.UnstructuredJSONScheme, body) + if err != nil { + return ur, err + } + + ur = u.(*k8sunstructured.Unstructured) + + return ur, nil +} diff --git a/util_test.go b/util_test.go new file mode 100644 index 0000000..f4a0fb5 --- /dev/null +++ b/util_test.go @@ -0,0 +1,42 @@ +package main + +import ( + "testing" +) + +func TestLastAppliedConfig(t *testing.T) { + srcJSON := "{\"apiVersion\": \"v1\", \"kind\": \"Namespace\", \"metadata\": {\"name\": \"test-unit\"}}" + u, err := parseJSON(srcJSON) + if err != nil { + t.Errorf("Error: %s", err) + } + setLastAppliedConfig(u, srcJSON) + + annotations := u.GetAnnotations() + count := len(annotations) + if count != 1 { + t.Errorf("TestLastAppliedConfig: incorect number of annotations, got: %d, want: %d.", count, 1) + } + + lac := getLastAppliedConfig(u) + if lac != srcJSON { + t.Errorf("TestLastAppliedConfig: incorect annotation value, got: %s, want: %s.", srcJSON, lac) + } +} + +func TestGetPatch(t *testing.T) { + srcJSON := "{\"apiVersion\": \"v1\", \"kind\": \"Namespace\", \"metadata\": {\"name\": \"test-unit\"}}" + + o, _ := parseJSON(srcJSON) + m, _ := parseJSON(srcJSON) + c, _ := parseJSON(srcJSON) + + original, _ := o.MarshalJSON() + modified, _ := m.MarshalJSON() + current, _ := c.MarshalJSON() + + _, err := getPatch(original, modified, current) + if err != nil { + t.Errorf("TestGetPatch: %s", err) + } +}