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/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 0131a689..9230d5ff 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/pristine" "github.com/splunk/qbec/internal/sio" "github.com/splunk/qbec/internal/types" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -125,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") @@ -176,6 +183,9 @@ 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 } @@ -213,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 diff --git a/internal/remote/pristine.go b/internal/pristine/pristine.go similarity index 88% rename from internal/remote/pristine.go rename to internal/pristine/pristine.go index 3a28dd50..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 - createFromPristine(obj model.K8sLocalObject) (model.K8sLocalObject, error) +type PristineReadWriter interface { + PristineReader + 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") @@ -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 97% rename from internal/remote/pristine_test.go rename to internal/pristine/pristine_test.go index 969ed7a1..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) }) @@ -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() 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 diff --git a/internal/remote/client.go b/internal/remote/client.go index 2dc8e2a2..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{ @@ -476,7 +477,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") } @@ -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(),