From 7e352869a859d9ce71f50008e736745db22cb319 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Wed, 6 Jan 2021 00:07:38 +0100 Subject: [PATCH 1/5] initial support for pristine in show --- internal/commands/show.go | 5 ++++- internal/remote/client.go | 6 +++--- internal/remote/pristine.go | 12 ++++++------ internal/remote/pristine_test.go | 4 ++-- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/internal/commands/show.go b/internal/commands/show.go index 0131a689..56ece40e 100644 --- a/internal/commands/show.go +++ b/internal/commands/show.go @@ -26,6 +26,7 @@ import ( "github.com/spf13/cobra" "github.com/splunk/qbec/internal/model" "github.com/splunk/qbec/internal/objsort" + "github.com/splunk/qbec/internal/remote" "github.com/splunk/qbec/internal/sio" "github.com/splunk/qbec/internal/types" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -181,7 +182,9 @@ func doShow(args []string, config showCommandConfig) error { } for _, o := range objects { - displayObjects = append(displayObjects, mapper(o)) + p := remote.QbecPristine{} + ret, _ := p.CreateFromPristine(o) + displayObjects = append(displayObjects, mapper(ret)) } switch format { diff --git a/internal/remote/client.go b/internal/remote/client.go index 2dc8e2a2..9027e7dc 100644 --- a/internal/remote/client.go +++ b/internal/remote/client.go @@ -79,7 +79,7 @@ type DeleteOptions struct { type internalSyncOptions struct { secretDryRun bool // dry-run phase for objects having secrets info - pristiner pristineReadWriter // pristine writer + pristiner PristineReadWriter // pristine writer pristineAnnotation string // pristine annotation to manipulate for secrets dry-run } @@ -407,7 +407,7 @@ func (c *Client) ensureType(gvk schema.GroupVersionKind, opts SyncOptions) error // It does not do anything in dry-run mode. It also does not create new objects if the caller has disabled the feature. func (c *Client) Sync(original model.K8sLocalObject, opts SyncOptions) (_ *SyncResult, finalError error) { // set up the pristine strategy. - var prw pristineReadWriter = qbecPristine{} + var prw PristineReadWriter = QbecPristine{} sensitive := types.HasSensitiveInfo(original.ToUnstructured()) internal := internalSyncOptions{ @@ -476,7 +476,7 @@ func (c *Client) doSync(original model.K8sLocalObject, opts SyncOptions, interna opts.DryRun = true // won't affect caller since passed by value obj, _ = types.HideSensitiveLocalInfo(original) } else { - o, err := internal.pristiner.createFromPristine(original) + o, err := internal.pristiner.CreateFromPristine(original) if err != nil { return nil, errors.Wrap(err, "create from pristine") } diff --git a/internal/remote/pristine.go b/internal/remote/pristine.go index 3a28dd50..04328f63 100644 --- a/internal/remote/pristine.go +++ b/internal/remote/pristine.go @@ -76,14 +76,14 @@ type pristineReader interface { getPristine(annotations map[string]string, obj *unstructured.Unstructured) (pristine *unstructured.Unstructured, source string) } -type pristineReadWriter interface { +type PristineReadWriter interface { pristineReader - createFromPristine(obj model.K8sLocalObject) (model.K8sLocalObject, error) + CreateFromPristine(obj model.K8sLocalObject) (model.K8sLocalObject, error) } -type qbecPristine struct{} +type QbecPristine struct{} -func (k qbecPristine) getPristine(annotations map[string]string, _ *unstructured.Unstructured) (*unstructured.Unstructured, string) { +func (k QbecPristine) getPristine(annotations map[string]string, _ *unstructured.Unstructured) (*unstructured.Unstructured, string) { serialized := annotations[model.QbecNames.PristineAnnotation] if serialized == "" { return nil, "" @@ -96,7 +96,7 @@ func (k qbecPristine) getPristine(annotations map[string]string, _ *unstructured return &unstructured.Unstructured{Object: m}, "qbec annotation" } -func (k qbecPristine) createFromPristine(pristine model.K8sLocalObject) (model.K8sLocalObject, error) { +func (k QbecPristine) CreateFromPristine(pristine model.K8sLocalObject) (model.K8sLocalObject, error) { b, err := json.Marshal(pristine) if err != nil { return nil, errors.Wrap(err, "pristine JSON marshal") @@ -164,7 +164,7 @@ func (f fallbackPristine) getPristine(annotations map[string]string, orig *unstr } func getPristineVersion(obj *unstructured.Unstructured, includeFallback bool) (*unstructured.Unstructured, string) { - pristineReaders := []pristineReader{qbecPristine{}, kubectlPristine{}} + pristineReaders := []pristineReader{QbecPristine{}, kubectlPristine{}} if includeFallback { pristineReaders = append(pristineReaders, fallbackPristine{}) } diff --git a/internal/remote/pristine_test.go b/internal/remote/pristine_test.go index 969ed7a1..f6086b6b 100644 --- a/internal/remote/pristine_test.go +++ b/internal/remote/pristine_test.go @@ -191,9 +191,9 @@ func TestPristineReaderNoFallback(t *testing.T) { func TestCreateFromPristine(t *testing.T) { un := loadFile(t, "input.yaml") - p := qbecPristine{} + p := QbecPristine{} obj := model.NewK8sLocalObject(un.Object, model.LocalAttrs{App: "app", Tag: "", Component: "comp1", Env: "dev"}) - ret, err := p.createFromPristine(obj) + ret, err := p.CreateFromPristine(obj) require.Nil(t, err) a := assert.New(t) eLabels := obj.ToUnstructured().GetLabels() From 65fd8efd80589df804e3d8524d55362d3181d594 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Wed, 6 Jan 2021 00:21:45 +0100 Subject: [PATCH 2/5] move pristine to separate package --- internal/commands/diff.go | 3 ++- internal/commands/show.go | 4 ++-- internal/{remote => pristine}/pristine.go | 24 +++++++++---------- .../{remote => pristine}/pristine_test.go | 4 ++-- internal/remote/client.go | 11 +++++---- 5 files changed, 24 insertions(+), 22 deletions(-) rename internal/{remote => pristine}/pristine.go (91%) rename internal/{remote => pristine}/pristine_test.go (98%) diff --git a/internal/commands/diff.go b/internal/commands/diff.go index 98cdc74c..e9708c2f 100644 --- a/internal/commands/diff.go +++ b/internal/commands/diff.go @@ -27,6 +27,7 @@ import ( "github.com/splunk/qbec/internal/diff" "github.com/splunk/qbec/internal/model" "github.com/splunk/qbec/internal/objsort" + "github.com/splunk/qbec/internal/pristine" "github.com/splunk/qbec/internal/remote" "github.com/splunk/qbec/internal/sio" "github.com/splunk/qbec/internal/types" @@ -283,7 +284,7 @@ func (d *differ) diff(ob model.K8sMeta) error { var left, right *unstructured.Unstructured if remoteObject != nil { var source string - left, source = remote.GetPristineVersionForDiff(remoteObject) + left, source = pristine.GetPristineVersionForDiff(remoteObject) leftName += " (source: " + source + ")" } left = fixup(left) diff --git a/internal/commands/show.go b/internal/commands/show.go index 56ece40e..8e3b1ff2 100644 --- a/internal/commands/show.go +++ b/internal/commands/show.go @@ -26,7 +26,7 @@ import ( "github.com/spf13/cobra" "github.com/splunk/qbec/internal/model" "github.com/splunk/qbec/internal/objsort" - "github.com/splunk/qbec/internal/remote" + "github.com/splunk/qbec/internal/pristine" "github.com/splunk/qbec/internal/sio" "github.com/splunk/qbec/internal/types" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -182,7 +182,7 @@ func doShow(args []string, config showCommandConfig) error { } for _, o := range objects { - p := remote.QbecPristine{} + p := pristine.QbecPristine{} ret, _ := p.CreateFromPristine(o) displayObjects = append(displayObjects, mapper(ret)) } diff --git a/internal/remote/pristine.go b/internal/pristine/pristine.go similarity index 91% rename from internal/remote/pristine.go rename to internal/pristine/pristine.go index 04328f63..5b4efdfd 100644 --- a/internal/remote/pristine.go +++ b/internal/pristine/pristine.go @@ -14,7 +14,7 @@ limitations under the License. */ -package remote +package pristine import ( "bytes" @@ -72,18 +72,18 @@ func unzipData(s string) (map[string]interface{}, error) { return data, nil } -type pristineReader interface { - getPristine(annotations map[string]string, obj *unstructured.Unstructured) (pristine *unstructured.Unstructured, source string) +type PristineReader interface { + GetPristine(annotations map[string]string, obj *unstructured.Unstructured) (pristine *unstructured.Unstructured, source string) } type PristineReadWriter interface { - pristineReader + PristineReader CreateFromPristine(obj model.K8sLocalObject) (model.K8sLocalObject, error) } type QbecPristine struct{} -func (k QbecPristine) getPristine(annotations map[string]string, _ *unstructured.Unstructured) (*unstructured.Unstructured, string) { +func (k QbecPristine) GetPristine(annotations map[string]string, _ *unstructured.Unstructured) (*unstructured.Unstructured, string) { serialized := annotations[model.QbecNames.PristineAnnotation] if serialized == "" { return nil, "" @@ -125,9 +125,9 @@ func (k QbecPristine) CreateFromPristine(pristine model.K8sLocalObject) (model.K const kubectlLastConfig = "kubectl.kubernetes.io/last-applied-configuration" -type kubectlPristine struct{} +type KubectlPristine struct{} -func (k kubectlPristine) getPristine(annotations map[string]string, _ *unstructured.Unstructured) (*unstructured.Unstructured, string) { +func (k KubectlPristine) GetPristine(annotations map[string]string, _ *unstructured.Unstructured) (*unstructured.Unstructured, string) { serialized := annotations[kubectlLastConfig] if serialized == "" { return nil, "" @@ -150,7 +150,7 @@ func (k kubectlPristine) getPristine(annotations map[string]string, _ *unstructu type fallbackPristine struct{} -func (f fallbackPristine) getPristine(annotations map[string]string, orig *unstructured.Unstructured) (*unstructured.Unstructured, string) { +func (f fallbackPristine) GetPristine(annotations map[string]string, orig *unstructured.Unstructured) (*unstructured.Unstructured, string) { delete(annotations, "deployment.kubernetes.io/revision") orig.SetDeletionTimestamp(nil) orig.SetCreationTimestamp(metav1.Time{}) @@ -163,8 +163,8 @@ func (f fallbackPristine) getPristine(annotations map[string]string, orig *unstr return orig, "fallback - live object with some attributes removed" } -func getPristineVersion(obj *unstructured.Unstructured, includeFallback bool) (*unstructured.Unstructured, string) { - pristineReaders := []pristineReader{QbecPristine{}, kubectlPristine{}} +func GetPristineVersion(obj *unstructured.Unstructured, includeFallback bool) (*unstructured.Unstructured, string) { + pristineReaders := []PristineReader{QbecPristine{}, KubectlPristine{}} if includeFallback { pristineReaders = append(pristineReaders, fallbackPristine{}) } @@ -173,7 +173,7 @@ func getPristineVersion(obj *unstructured.Unstructured, includeFallback bool) (* annotations = map[string]string{} } for _, p := range pristineReaders { - out, str := p.getPristine(annotations, obj) + out, str := p.GetPristine(annotations, obj) if out != nil { return out, str } @@ -185,5 +185,5 @@ func getPristineVersion(obj *unstructured.Unstructured, includeFallback bool) (* // live object. If no annotations are found, it halfheartedly deletes known runtime information that is // set on the server and returns the supplied object with those attributes removed. func GetPristineVersionForDiff(obj *unstructured.Unstructured) (*unstructured.Unstructured, string) { - return getPristineVersion(obj, true) + return GetPristineVersion(obj, true) } diff --git a/internal/remote/pristine_test.go b/internal/pristine/pristine_test.go similarity index 98% rename from internal/remote/pristine_test.go rename to internal/pristine/pristine_test.go index f6086b6b..9c37867f 100644 --- a/internal/remote/pristine_test.go +++ b/internal/pristine/pristine_test.go @@ -1,4 +1,4 @@ -package remote +package pristine import ( "encoding/base64" @@ -174,7 +174,7 @@ func testPristineReader(t *testing.T, useFallback bool) { if useFallback { pristine, source = GetPristineVersionForDiff(obj) } else { - pristine, source = getPristineVersion(obj, false) + pristine, source = GetPristineVersion(obj, false) } test.asserter(t, pristine, source) }) diff --git a/internal/remote/client.go b/internal/remote/client.go index 9027e7dc..195672ab 100644 --- a/internal/remote/client.go +++ b/internal/remote/client.go @@ -26,6 +26,7 @@ import ( "github.com/jonboulle/clockwork" "github.com/pkg/errors" "github.com/splunk/qbec/internal/model" + "github.com/splunk/qbec/internal/pristine" "github.com/splunk/qbec/internal/remote/k8smeta" "github.com/splunk/qbec/internal/sio" "github.com/splunk/qbec/internal/types" @@ -78,9 +79,9 @@ type DeleteOptions struct { } type internalSyncOptions struct { - secretDryRun bool // dry-run phase for objects having secrets info - pristiner PristineReadWriter // pristine writer - pristineAnnotation string // pristine annotation to manipulate for secrets dry-run + secretDryRun bool // dry-run phase for objects having secrets info + pristiner pristine.PristineReadWriter // pristine writer + pristineAnnotation string // pristine annotation to manipulate for secrets dry-run } type resourceClient interface { @@ -407,7 +408,7 @@ func (c *Client) ensureType(gvk schema.GroupVersionKind, opts SyncOptions) error // It does not do anything in dry-run mode. It also does not create new objects if the caller has disabled the feature. func (c *Client) Sync(original model.K8sLocalObject, opts SyncOptions) (_ *SyncResult, finalError error) { // set up the pristine strategy. - var prw PristineReadWriter = QbecPristine{} + var prw pristine.PristineReadWriter = pristine.QbecPristine{} sensitive := types.HasSensitiveInfo(original.ToUnstructured()) internal := internalSyncOptions{ @@ -665,7 +666,7 @@ func (c *Client) maybeUpdate(obj model.K8sLocalObject, remObj *unstructured.Unst p := patcher{ provider: c.resourceInterfaceWithDefaultNs, cfgProvider: func(obj *unstructured.Unstructured) ([]byte, error) { - pristine, _ := getPristineVersion(obj, false) + pristine, _ := pristine.GetPristineVersion(obj, false) if pristine == nil { p := map[string]interface{}{ "kind": obj.GetKind(), From 623fb9fa072d5cfc9429b270870332a8569cadca Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Wed, 6 Jan 2021 00:41:39 +0100 Subject: [PATCH 3/5] add --show-pristine option --- internal/commands/config.go | 1 + internal/commands/show.go | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/internal/commands/config.go b/internal/commands/config.go index 33e7664b..de849d31 100644 --- a/internal/commands/config.go +++ b/internal/commands/config.go @@ -190,6 +190,7 @@ type config struct { stdout io.Writer // standard output stderr io.Writer // standard error cleanEvalMode bool // clean mode for eval + showPristine bool // generate last-applied annotation } // init checks variables and sets up defaults. In strict mode, it requires all variables diff --git a/internal/commands/show.go b/internal/commands/show.go index 8e3b1ff2..5d9735d2 100644 --- a/internal/commands/show.go +++ b/internal/commands/show.go @@ -182,9 +182,16 @@ func doShow(args []string, config showCommandConfig) error { } for _, o := range objects { - p := pristine.QbecPristine{} - ret, _ := p.CreateFromPristine(o) - displayObjects = append(displayObjects, mapper(ret)) + if config.showPristine { + p := pristine.QbecPristine{} + ret, err := p.CreateFromPristine(o) + if err != nil { + return err + } + displayObjects = append(displayObjects, mapper(ret)) + } else { + displayObjects = append(displayObjects, mapper(o)) + } } switch format { @@ -216,17 +223,19 @@ func newShowCommand(cp configProvider) *cobra.Command { filterFunc: addFilterParams(cmd, true), } - var clean bool + var clean, pristine bool cmd.Flags().StringVarP(&config.format, "format", "o", "yaml", "Output format. Supported values are: json, yaml") cmd.Flags().BoolVarP(&config.namesOnly, "objects", "O", false, "Only print names of objects instead of their contents") cmd.Flags().BoolVar(&config.sortAsApply, "sort-apply", false, "sort output in apply order (requires cluster access)") cmd.Flags().BoolVar(&clean, "clean", false, "do not display qbec-generated labels and annotations") + cmd.Flags().BoolVar(&pristine, "show-pristine", false, "generate and display last-applied annotation") cmd.Flags().BoolVarP(&config.showSecrets, "show-secrets", "S", false, "do not obfuscate secret values in the output") cmd.RunE = func(c *cobra.Command, args []string) error { config.config = cp() config.formatSpecified = c.Flags().Changed("format") config.config.cleanEvalMode = clean + config.config.showPristine = pristine return wrapError(doShow(args, config)) } return cmd From 46653c00bdaffb4de4c968159bedeb03fdbdca75 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Wed, 6 Jan 2021 00:42:02 +0100 Subject: [PATCH 4/5] add showPristine mapper --- internal/commands/show.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/commands/show.go b/internal/commands/show.go index 5d9735d2..9230d5ff 100644 --- a/internal/commands/show.go +++ b/internal/commands/show.go @@ -126,6 +126,12 @@ func cleanMeta(obj model.K8sLocalObject) *unstructured.Unstructured { return un } +func showPristine(obj model.K8sLocalObject) *unstructured.Unstructured { + p := pristine.QbecPristine{} + ret, _ := p.CreateFromPristine(obj) + return ret.ToUnstructured() +} + func doShow(args []string, config showCommandConfig) error { if len(args) != 1 { return newUsageError("exactly one environment required") @@ -177,21 +183,15 @@ func doShow(args []string, config showCommandConfig) error { var displayObjects []*unstructured.Unstructured mapper := func(o model.K8sLocalObject) *unstructured.Unstructured { return o.ToUnstructured() } + if config.showPristine { + mapper = showPristine + } if config.cleanEvalMode { mapper = cleanMeta } for _, o := range objects { - if config.showPristine { - p := pristine.QbecPristine{} - ret, err := p.CreateFromPristine(o) - if err != nil { - return err - } - displayObjects = append(displayObjects, mapper(ret)) - } else { - displayObjects = append(displayObjects, mapper(o)) - } + displayObjects = append(displayObjects, mapper(o)) } switch format { From 4434ef674a3bfc4a6a8eb4fd407e754fee7a9add Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Wed, 6 Jan 2021 11:41:18 +0100 Subject: [PATCH 5/5] fix tests for pristine --- internal/{remote => pristine}/testdata/pristine/input.yaml | 0 internal/{remote => pristine}/testdata/pristine/kc-applied.yaml | 0 internal/{remote => pristine}/testdata/pristine/kc-created.yaml | 0 internal/{remote => pristine}/testdata/pristine/qbec-applied.yaml | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename internal/{remote => pristine}/testdata/pristine/input.yaml (100%) rename internal/{remote => pristine}/testdata/pristine/kc-applied.yaml (100%) rename internal/{remote => pristine}/testdata/pristine/kc-created.yaml (100%) rename internal/{remote => pristine}/testdata/pristine/qbec-applied.yaml (100%) diff --git a/internal/remote/testdata/pristine/input.yaml b/internal/pristine/testdata/pristine/input.yaml similarity index 100% rename from internal/remote/testdata/pristine/input.yaml rename to internal/pristine/testdata/pristine/input.yaml diff --git a/internal/remote/testdata/pristine/kc-applied.yaml b/internal/pristine/testdata/pristine/kc-applied.yaml similarity index 100% rename from internal/remote/testdata/pristine/kc-applied.yaml rename to internal/pristine/testdata/pristine/kc-applied.yaml diff --git a/internal/remote/testdata/pristine/kc-created.yaml b/internal/pristine/testdata/pristine/kc-created.yaml similarity index 100% rename from internal/remote/testdata/pristine/kc-created.yaml rename to internal/pristine/testdata/pristine/kc-created.yaml diff --git a/internal/remote/testdata/pristine/qbec-applied.yaml b/internal/pristine/testdata/pristine/qbec-applied.yaml similarity index 100% rename from internal/remote/testdata/pristine/qbec-applied.yaml rename to internal/pristine/testdata/pristine/qbec-applied.yaml