From 5747337f4757dffd4a10c9f119c53e6f962a4de1 Mon Sep 17 00:00:00 2001 From: Tiger Kaovilai Date: Thu, 4 May 2023 03:09:31 -0400 Subject: [PATCH] Add `generate bundle --ignore-if-only-createdAt` option Signed-off-by: Tiger Kaovilai --- changelog/fragments/6419.yaml | 38 ++++++++++++++ .../operator-sdk/generate/bundle/bundle.go | 4 ++ .../cmd/operator-sdk/generate/bundle/cmd.go | 8 +-- .../clusterserviceversion.go | 49 ++++++++++++++++++- internal/generate/internal/genutil.go | 10 ++++ internal/util/k8sutil/object.go | 9 ++++ 6 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 changelog/fragments/6419.yaml diff --git a/changelog/fragments/6419.yaml b/changelog/fragments/6419.yaml new file mode 100644 index 00000000000..3ec2e3e769e --- /dev/null +++ b/changelog/fragments/6419.yaml @@ -0,0 +1,38 @@ +# entries is a list of entries to include in +# release notes and/or the migration guide +entries: + - description: > + Add ability to ignore bundle updates on `generate bundle` command if createdAt timestamp is the only change. + The flag to use is `--ignore-if-only-createdAt`. + + # kind is one of: + # - addition + # - change + # - deprecation + # - removal + # - bugfix + kind: "addition" + + # Is this a breaking change? + breaking: false + + # NOTE: ONLY USE `pull_request_override` WHEN ADDING THIS + # FILE FOR A PREVIOUSLY MERGED PULL_REQUEST! + # + # The generator auto-detects the PR number from the commit + # message in which this file was originally added. + # + # What is the pull request number (without the "#")? + # pull_request_override: 0 + + + # Migration can be defined to automatically add a section to + # the migration guide. This is required for breaking changes. + # migration: + # header: Header text for the migration section + # body: | + # Body of the migration section. This should be formatted as markdown and can + # span multiple lines. + + # Using the YAML string '|' operator means that newlines in this string will + # be honored and interpretted as newlines in the rendered markdown. diff --git a/internal/cmd/operator-sdk/generate/bundle/bundle.go b/internal/cmd/operator-sdk/generate/bundle/bundle.go index cd772f25bae..be987f86e93 100644 --- a/internal/cmd/operator-sdk/generate/bundle/bundle.go +++ b/internal/cmd/operator-sdk/generate/bundle/bundle.go @@ -201,6 +201,10 @@ func (c bundleCmd) runManifests() (err error) { opts = append(opts, gencsv.WithWriter(stdout)) } else { opts = append(opts, gencsv.WithBundleWriter(c.outputDir)) + if c.ignoreIfOnlyCreatedAt && genutil.IsExist(c.outputDir) { + opts = append(opts, gencsv.WithBundleReader(c.outputDir)) + opts = append(opts, gencsv.WithIgnoreIfOnlyCreatedAt()) + } } csvGen := gencsv.Generator{ diff --git a/internal/cmd/operator-sdk/generate/bundle/cmd.go b/internal/cmd/operator-sdk/generate/bundle/cmd.go index e4803a962b5..e1006cdf073 100644 --- a/internal/cmd/operator-sdk/generate/bundle/cmd.go +++ b/internal/cmd/operator-sdk/generate/bundle/cmd.go @@ -42,9 +42,10 @@ type bundleCmd struct { extraServiceAccounts []string // Metadata options. - channels string - defaultChannel string - overwrite bool + channels string + defaultChannel string + overwrite bool + ignoreIfOnlyCreatedAt bool // These are set if a PROJECT config is not present. layout string @@ -138,6 +139,7 @@ func (c *bundleCmd) addFlagsTo(fs *pflag.FlagSet) { "Names of service accounts, outside of the operator's Deployment account, "+ "that have bindings to {Cluster}Roles that should be added to the CSV") fs.BoolVar(&c.overwrite, "overwrite", true, "Overwrite the bundle's metadata and Dockerfile if they exist") + fs.BoolVar(&c.ignoreIfOnlyCreatedAt, "ignore-if-only-createdAt", false, "Ignore if only createdAt is changed") fs.BoolVarP(&c.quiet, "quiet", "q", false, "Run in quiet mode") fs.BoolVar(&c.stdout, "stdout", false, "Write bundle manifest to stdout") diff --git a/internal/generate/clusterserviceversion/clusterserviceversion.go b/internal/generate/clusterserviceversion/clusterserviceversion.go index 6e66aaa0f8c..293e0df829d 100644 --- a/internal/generate/clusterserviceversion/clusterserviceversion.go +++ b/internal/generate/clusterserviceversion/clusterserviceversion.go @@ -18,6 +18,7 @@ import ( "fmt" "io" "path/filepath" + "reflect" "strings" "github.com/blang/semver/v4" @@ -61,6 +62,10 @@ type Generator struct { // Func that returns the writer the generated CSV's bytes are written to. getWriter func() (io.Writer, error) + // Func that returns the reader the previous CSV's bytes are read from. + getReader func() (io.Reader, error) + + ignoreIfOnlyCreatedAt bool } // Option is a function that modifies a Generator. @@ -88,6 +93,18 @@ func WithBundleWriter(dir string) Option { } } +// WithBundleGetter sets a Generator's getter to a bundle CSV file under +// /manifests. +func WithBundleReader(dir string) Option { + return func(g *Generator) error { + fileName := makeCSVFileName(g.OperatorName) + g.getReader = func() (io.Reader, error) { + return genutil.Open(filepath.Join(dir, bundle.ManifestsDir), fileName) + } + return nil + } +} + // WithPackageWriter sets a Generator's writer to a package CSV file under // /. func WithPackageWriter(dir string) Option { @@ -100,6 +117,13 @@ func WithPackageWriter(dir string) Option { } } +func WithIgnoreIfOnlyCreatedAt() Option { + return func(g *Generator) error { + g.ignoreIfOnlyCreatedAt = true + return nil + } +} + // Generate configures the generator with col and opts then runs it. func (g *Generator) Generate(opts ...Option) (err error) { for _, opt := range opts { @@ -119,7 +143,30 @@ func (g *Generator) Generate(opts ...Option) (err error) { // Add extra annotations to csv g.setAnnotations(csv) - + // If a reader is set, and there is a flag to not update createdAt, then + // set the CSV's createdAt to the previous CSV's createdAt if its the only change. + if g.ignoreIfOnlyCreatedAt && g.getReader != nil { + r, err := g.getReader() + if err != nil { + return err + } + var prevCSV operatorsv1alpha1.ClusterServiceVersion + err = genutil.ReadObject(r, &prevCSV) + if err != nil { + return err + } + if prevCSV.ObjectMeta.Annotations["createdAt"] != "" { + csvWithoutCreatedAtChange := csv.DeepCopy() + // Set WebhookDefinitions if not nil to avoid diffing on it + if prevCSV.Spec.WebhookDefinitions == nil { + prevCSV.Spec.WebhookDefinitions = []operatorsv1alpha1.WebhookDescription{} + } + csvWithoutCreatedAtChange.ObjectMeta.Annotations["createdAt"] = prevCSV.ObjectMeta.Annotations["createdAt"] + if reflect.DeepEqual(csvWithoutCreatedAtChange, &prevCSV) { + csv = csvWithoutCreatedAtChange + } + } + } w, err := g.getWriter() if err != nil { return err diff --git a/internal/generate/internal/genutil.go b/internal/generate/internal/genutil.go index 630fece9b9b..6e8787ca8d6 100644 --- a/internal/generate/internal/genutil.go +++ b/internal/generate/internal/genutil.go @@ -22,6 +22,7 @@ import ( "os" "path/filepath" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" "github.com/operator-framework/operator-sdk/internal/util/k8sutil" @@ -111,3 +112,12 @@ func IsNotExist(path string) bool { _, err := os.Stat(path) return err != nil && errors.Is(err, os.ErrNotExist) } + +func ReadObject(r io.Reader, obj client.Object) error { + var buf bytes.Buffer + if _, err := buf.ReadFrom(r); err != nil { + return err + } + k8sutil.GetObjectFromBytes(buf.Bytes(), obj) + return nil +} diff --git a/internal/util/k8sutil/object.go b/internal/util/k8sutil/object.go index 9f7d23c2aca..5bc1cf17c6f 100644 --- a/internal/util/k8sutil/object.go +++ b/internal/util/k8sutil/object.go @@ -16,6 +16,7 @@ package k8sutil import ( "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/yaml" ) type MarshalFunc func(interface{}) ([]byte, error) @@ -53,3 +54,11 @@ func deleteKeyFromUnstructured(u map[string]interface{}, key string) { } } } + +func GetObjectFromBytes(b []byte, obj interface{}) (interface{}, error) { + var u map[string]interface{} + if err := yaml.Unmarshal(b, &u); err != nil { + return nil, err + } + return runtime.DefaultUnstructuredConverter.FromUnstructured(u, obj), nil +}