From a21f962e4b609f61257d4e78745e26c298ac53d4 Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Tue, 27 Feb 2024 15:19:24 -0500 Subject: [PATCH] render: support rendering FBC from bundle directories (#748) * render: support rendering FBC from bundle and packagemanifest directories In order to generate expected olm.bundles, this commit also adds a new --image-ref-template flag to the `render` subcommand, which callers can use to generate image references from source data based on package name, bundle name, and bundle version. Signed-off-by: Joe Lanford * make --image-ref-template alpha (and include simple framework for alpha flags and usage) Signed-off-by: Joe Lanford --------- Signed-off-by: Joe Lanford --- alpha/action/render.go | 159 ++++++++++++++++++++++++++++-------- alpha/action/render_test.go | 124 ++++++++++++++++++++++++++-- cmd/opm/alpha/cmd.go | 15 ++-- cmd/opm/index/add.go | 8 +- cmd/opm/index/cmd.go | 4 +- cmd/opm/main.go | 3 +- cmd/opm/registry/add.go | 8 +- cmd/opm/registry/cmd.go | 4 +- cmd/opm/render/cmd.go | 38 +++++++-- cmd/opm/root/cmd.go | 31 ++++++- test/e2e/e2e_suite_test.go | 2 +- 11 files changed, 328 insertions(+), 68 deletions(-) diff --git a/alpha/action/render.go b/alpha/action/render.go index 465f54db9..5dbb980cf 100644 --- a/alpha/action/render.go +++ b/alpha/action/render.go @@ -12,6 +12,7 @@ import ( "sort" "strings" "sync" + "text/template" "github.com/h2non/filetype" "github.com/h2non/filetype/matchers" @@ -39,6 +40,7 @@ const ( RefSqliteFile RefDCImage RefDCDir + RefBundleDir RefAll = 0 ) @@ -50,10 +52,11 @@ func (r RefType) Allowed(refType RefType) bool { var ErrNotAllowed = errors.New("not allowed") type Render struct { - Refs []string - Registry image.Registry - AllowedRefMask RefType - Migrate bool + Refs []string + Registry image.Registry + AllowedRefMask RefType + Migrate bool + ImageRefTemplate *template.Template skipSqliteDeprecationLog bool } @@ -125,25 +128,44 @@ func (r Render) createRegistry() (*containerdregistry.Registry, error) { } func (r Render) renderReference(ctx context.Context, ref string) (*declcfg.DeclarativeConfig, error) { - if stat, serr := os.Stat(ref); serr == nil { - if stat.IsDir() { - if !r.AllowedRefMask.Allowed(RefDCDir) { - return nil, fmt.Errorf("cannot render declarative config directory: %w", ErrNotAllowed) - } - return declcfg.LoadFS(ctx, os.DirFS(ref)) - } else { - // The only supported file type is an sqlite DB file, - // since declarative configs will be in a directory. - if err := checkDBFile(ref); err != nil { - return nil, err - } - if !r.AllowedRefMask.Allowed(RefSqliteFile) { - return nil, fmt.Errorf("cannot render sqlite file: %w", ErrNotAllowed) + stat, err := os.Stat(ref) + if err != nil { + return r.imageToDeclcfg(ctx, ref) + } + if stat.IsDir() { + dirEntries, err := os.ReadDir(ref) + if err != nil { + return nil, err + } + if isBundle(dirEntries) { + // Looks like a bundle directory + if !r.AllowedRefMask.Allowed(RefBundleDir) { + return nil, fmt.Errorf("cannot render bundle directory %q: %w", ref, ErrNotAllowed) } - return sqliteToDeclcfg(ctx, ref) + return r.renderBundleDirectory(ref) } + + // Otherwise, assume it is a declarative config root directory. + if !r.AllowedRefMask.Allowed(RefDCDir) { + return nil, fmt.Errorf("cannot render declarative config directory: %w", ErrNotAllowed) + } + return declcfg.LoadFS(ctx, os.DirFS(ref)) + } + // The only supported file type is an sqlite DB file, + // since declarative configs will be in a directory. + if err := checkDBFile(ref); err != nil { + return nil, err + } + if !r.AllowedRefMask.Allowed(RefSqliteFile) { + return nil, fmt.Errorf("cannot render sqlite file: %w", ErrNotAllowed) + } + + db, err := sqlite.Open(ref) + if err != nil { + return nil, err } - return r.imageToDeclcfg(ctx, ref) + defer db.Close() + return sqliteToDeclcfg(ctx, db) } func (r Render) imageToDeclcfg(ctx context.Context, imageRef string) (*declcfg.DeclarativeConfig, error) { @@ -169,7 +191,12 @@ func (r Render) imageToDeclcfg(ctx context.Context, imageRef string) (*declcfg.D if !r.AllowedRefMask.Allowed(RefSqliteImage) { return nil, fmt.Errorf("cannot render sqlite image: %w", ErrNotAllowed) } - cfg, err = sqliteToDeclcfg(ctx, filepath.Join(tmpDir, dbFile)) + db, err := sqlite.Open(filepath.Join(tmpDir, dbFile)) + if err != nil { + return nil, err + } + defer db.Close() + cfg, err = sqliteToDeclcfg(ctx, db) if err != nil { return nil, err } @@ -190,10 +217,11 @@ func (r Render) imageToDeclcfg(ctx context.Context, imageRef string) (*declcfg.D return nil, err } - cfg, err = bundleToDeclcfg(img.Bundle) + bundle, err := bundleToDeclcfg(img.Bundle) if err != nil { return nil, err } + cfg = &declcfg.DeclarativeConfig{Bundles: []declcfg.Bundle{*bundle}} } else { labelKeys := sets.StringKeySet(labels) labelVals := []string{} @@ -221,17 +249,11 @@ func checkDBFile(ref string) error { return nil } -func sqliteToDeclcfg(ctx context.Context, dbFile string) (*declcfg.DeclarativeConfig, error) { +func sqliteToDeclcfg(ctx context.Context, db *sql.DB) (*declcfg.DeclarativeConfig, error) { logDeprecationMessage.Do(func() { sqlite.LogSqliteDeprecation() }) - db, err := sqlite.Open(dbFile) - if err != nil { - return nil, err - } - defer db.Close() - migrator, err := sqlite.NewSQLLiteMigrator(db) if err != nil { return nil, err @@ -303,7 +325,7 @@ func populateDBRelatedImages(ctx context.Context, cfg *declcfg.DeclarativeConfig return nil } -func bundleToDeclcfg(bundle *registry.Bundle) (*declcfg.DeclarativeConfig, error) { +func bundleToDeclcfg(bundle *registry.Bundle) (*declcfg.Bundle, error) { objs, props, err := registry.ObjectsAndPropertiesFromBundle(bundle) if err != nil { return nil, fmt.Errorf("get properties for bundle %q: %v", bundle.Name, err) @@ -323,7 +345,7 @@ func bundleToDeclcfg(bundle *registry.Bundle) (*declcfg.DeclarativeConfig, error } } - dBundle := declcfg.Bundle{ + return &declcfg.Bundle{ Schema: "olm.bundle", Name: bundle.Name, Package: bundle.Package, @@ -332,9 +354,7 @@ func bundleToDeclcfg(bundle *registry.Bundle) (*declcfg.DeclarativeConfig, error RelatedImages: relatedImages, Objects: objs, CsvJSON: string(csvJson), - } - - return &declcfg.DeclarativeConfig{Bundles: []declcfg.Bundle{dBundle}}, nil + }, nil } func getRelatedImages(b *registry.Bundle) ([]declcfg.RelatedImage, error) { @@ -363,7 +383,7 @@ func getRelatedImages(b *registry.Bundle) ([]declcfg.RelatedImage, error) { allImages = allImages.Insert(ri.Image) } - if !allImages.Has(b.BundleImage) { + if b.BundleImage != "" && !allImages.Has(b.BundleImage) { relatedImages = append(relatedImages, declcfg.RelatedImage{ Image: b.BundleImage, }) @@ -454,3 +474,72 @@ func combineConfigs(cfgs []declcfg.DeclarativeConfig) *declcfg.DeclarativeConfig } return out } + +func isBundle(entries []os.DirEntry) bool { + foundManifests := false + foundMetadata := false + for _, e := range entries { + if e.IsDir() { + switch e.Name() { + case "manifests": + foundManifests = true + case "metadata": + foundMetadata = true + } + } + if foundMetadata && foundManifests { + return true + } + } + return false +} + +type imageReferenceTemplateData struct { + Package string + Name string + Version string +} + +func (r *Render) renderBundleDirectory(ref string) (*declcfg.DeclarativeConfig, error) { + img, err := registry.NewImageInput(image.SimpleReference(""), ref) + if err != nil { + return nil, err + } + if err := r.templateBundleImageRef(img.Bundle); err != nil { + return nil, fmt.Errorf("failed templating image reference from bundle for %q: %v", ref, err) + } + fbcBundle, err := bundleToDeclcfg(img.Bundle) + if err != nil { + return nil, err + } + return &declcfg.DeclarativeConfig{Bundles: []declcfg.Bundle{*fbcBundle}}, nil +} + +func (r *Render) templateBundleImageRef(bundle *registry.Bundle) error { + if r.ImageRefTemplate == nil { + return nil + } + + var pkgProp property.Package + for _, p := range bundle.Properties { + if p.Type != property.TypePackage { + continue + } + if err := json.Unmarshal(p.Value, &pkgProp); err != nil { + return err + } + break + } + + var buf strings.Builder + tmplInput := imageReferenceTemplateData{ + Package: bundle.Package, + Name: bundle.Name, + Version: pkgProp.Version, + } + if err := r.ImageRefTemplate.Execute(&buf, tmplInput); err != nil { + return err + } + bundle.BundleImage = buf.String() + return nil +} diff --git a/alpha/action/render_test.go b/alpha/action/render_test.go index 5c2043825..b1ee6840f 100644 --- a/alpha/action/render_test.go +++ b/alpha/action/render_test.go @@ -12,6 +12,7 @@ import ( "path/filepath" "testing" "testing/fstest" + "text/template" "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/stretchr/testify/assert" @@ -736,6 +737,99 @@ func TestRender(t *testing.T) { }, assertion: require.NoError, }, + { + name: "Success/BundleDirectoryWithImageRefTemplate", + render: action.Render{ + Refs: []string{"testdata/foo-bundle-v0.2.0"}, + ImageRefTemplate: template.Must(template.New("imageRef").Parse("test.registry/{{.Package}}-operator/{{.Package}}:v{{.Version}}")), + Registry: reg, + }, + expectCfg: &declcfg.DeclarativeConfig{ + Bundles: []declcfg.Bundle{ + { + Schema: "olm.bundle", + Name: "foo.v0.2.0", + Package: "foo", + Image: "test.registry/foo-operator/foo:v0.2.0", + Properties: []property.Property{ + property.MustBuildGVK("test.foo", "v1", "Foo"), + property.MustBuildGVKRequired("test.bar", "v1alpha1", "Bar"), + property.MustBuildPackage("foo", "0.2.0"), + property.MustBuildPackageRequired("bar", "<0.1.0"), + mustBuildCSVMetadata(bytes.NewReader(foov2csv)), + }, + Objects: []string{string(foov2csv), string(foov2crd)}, + CsvJSON: string(foov2csv), + RelatedImages: []declcfg.RelatedImage{ + { + Image: "test.registry/foo-operator/foo-2:v0.2.0", + }, + { + Image: "test.registry/foo-operator/foo-init-2:v0.2.0", + }, + { + Image: "test.registry/foo-operator/foo-init:v0.2.0", + }, + { + Name: "other", + Image: "test.registry/foo-operator/foo-other:v0.2.0", + }, + { + Name: "operator", + Image: "test.registry/foo-operator/foo:v0.2.0", + }, + }, + }, + }, + }, + assertion: require.NoError, + }, + { + name: "Success/BundleDirectory", + render: action.Render{ + Refs: []string{"testdata/foo-bundle-v0.2.0"}, + Registry: reg, + }, + expectCfg: &declcfg.DeclarativeConfig{ + Bundles: []declcfg.Bundle{ + { + Schema: "olm.bundle", + Name: "foo.v0.2.0", + Package: "foo", + Properties: []property.Property{ + property.MustBuildGVK("test.foo", "v1", "Foo"), + property.MustBuildGVKRequired("test.bar", "v1alpha1", "Bar"), + property.MustBuildPackage("foo", "0.2.0"), + property.MustBuildPackageRequired("bar", "<0.1.0"), + property.MustBuildBundleObject(foov2crd), + property.MustBuildBundleObject(foov2csv), + }, + Objects: []string{string(foov2csv), string(foov2crd)}, + CsvJSON: string(foov2csv), + RelatedImages: []declcfg.RelatedImage{ + { + Image: "test.registry/foo-operator/foo-2:v0.2.0", + }, + { + Image: "test.registry/foo-operator/foo-init-2:v0.2.0", + }, + { + Image: "test.registry/foo-operator/foo-init:v0.2.0", + }, + { + Name: "other", + Image: "test.registry/foo-operator/foo-other:v0.2.0", + }, + { + Name: "operator", + Image: "test.registry/foo-operator/foo:v0.2.0", + }, + }, + }, + }, + }, + assertion: require.NoError, + }, } for _, s := range specs { @@ -790,7 +884,7 @@ func TestAllowRefMask(t *testing.T) { render: action.Render{ Refs: []string{"test.registry/foo-operator/foo-index-sqlite:v0.2.0"}, Registry: reg, - AllowedRefMask: action.RefDCImage | action.RefDCDir | action.RefSqliteFile | action.RefBundleImage, + AllowedRefMask: action.RefDCImage | action.RefDCDir | action.RefSqliteFile | action.RefBundleImage | action.RefBundleDir, }, expectErr: action.ErrNotAllowed, }, @@ -808,7 +902,7 @@ func TestAllowRefMask(t *testing.T) { render: action.Render{ Refs: []string{dbFile}, Registry: reg, - AllowedRefMask: action.RefDCImage | action.RefDCDir | action.RefSqliteImage | action.RefBundleImage, + AllowedRefMask: action.RefDCImage | action.RefDCDir | action.RefSqliteImage | action.RefBundleImage | action.RefBundleDir, }, expectErr: action.ErrNotAllowed, }, @@ -826,7 +920,7 @@ func TestAllowRefMask(t *testing.T) { render: action.Render{ Refs: []string{"test.registry/foo-operator/foo-index-declcfg:v0.2.0"}, Registry: reg, - AllowedRefMask: action.RefDCDir | action.RefSqliteImage | action.RefSqliteFile | action.RefBundleImage, + AllowedRefMask: action.RefDCDir | action.RefSqliteImage | action.RefSqliteFile | action.RefBundleImage | action.RefBundleDir, }, expectErr: action.ErrNotAllowed, }, @@ -844,7 +938,7 @@ func TestAllowRefMask(t *testing.T) { render: action.Render{ Refs: []string{"testdata/foo-index-v0.2.0-declcfg"}, Registry: reg, - AllowedRefMask: action.RefDCImage | action.RefSqliteImage | action.RefSqliteFile | action.RefBundleImage, + AllowedRefMask: action.RefDCImage | action.RefSqliteImage | action.RefSqliteFile | action.RefBundleImage | action.RefBundleDir, }, expectErr: action.ErrNotAllowed, }, @@ -862,7 +956,25 @@ func TestAllowRefMask(t *testing.T) { render: action.Render{ Refs: []string{"test.registry/foo-operator/foo-bundle:v0.2.0"}, Registry: reg, - AllowedRefMask: action.RefDCImage | action.RefDCDir | action.RefSqliteImage | action.RefSqliteFile, + AllowedRefMask: action.RefDCImage | action.RefDCDir | action.RefSqliteImage | action.RefSqliteFile | action.RefBundleDir, + }, + expectErr: action.ErrNotAllowed, + }, + { + name: "BundleDir/Allowed", + render: action.Render{ + Refs: []string{"testdata/foo-bundle-v0.2.0"}, + Registry: reg, + AllowedRefMask: action.RefBundleDir, + }, + expectErr: nil, + }, + { + name: "BundleDir/NotAllowed", + render: action.Render{ + Refs: []string{"testdata/foo-bundle-v0.2.0"}, + Registry: reg, + AllowedRefMask: action.RefDCImage | action.RefDCDir | action.RefSqliteImage | action.RefSqliteFile | action.RefBundleImage, }, expectErr: action.ErrNotAllowed, }, @@ -875,6 +987,7 @@ func TestAllowRefMask(t *testing.T) { "test.registry/foo-operator/foo-index-declcfg:v0.2.0", "testdata/foo-index-v0.2.0-declcfg", "test.registry/foo-operator/foo-bundle:v0.2.0", + "testdata/foo-bundle-v0.2.0", }, Registry: reg, }, @@ -908,6 +1021,7 @@ func TestAllowRefMaskAllowed(t *testing.T) { action.RefSqliteImage, action.RefSqliteFile, action.RefBundleImage, + action.RefBundleDir, }, fail: []action.RefType{}, }, diff --git a/cmd/opm/alpha/cmd.go b/cmd/opm/alpha/cmd.go index 55511cb2e..202d9597f 100644 --- a/cmd/opm/alpha/cmd.go +++ b/cmd/opm/alpha/cmd.go @@ -9,13 +9,16 @@ import ( "github.com/operator-framework/operator-registry/cmd/opm/alpha/template" ) -func NewCmd() *cobra.Command { +func NewCmd(showAlphaHelp bool) *cobra.Command { runCmd := &cobra.Command{ - Hidden: true, - Use: "alpha", - Short: "Run an alpha subcommand", - Args: cobra.NoArgs, - Run: func(_ *cobra.Command, _ []string) {}, // adding an empty function here to preserve non-zero exit status for misstated subcommands/flags for the command hierarchy + Use: "alpha", + Short: "Run an alpha subcommand", + Args: cobra.NoArgs, + Run: func(_ *cobra.Command, _ []string) {}, // adding an empty function here to preserve non-zero exit status for misstated subcommands/flags for the command hierarchy + } + + if !showAlphaHelp { + runCmd.Hidden = true } runCmd.AddCommand( diff --git a/cmd/opm/index/add.go b/cmd/opm/index/add.go index 0cb7c4801..e3db1854a 100644 --- a/cmd/opm/index/add.go +++ b/cmd/opm/index/add.go @@ -41,7 +41,7 @@ var ( `) ) -func addIndexAddCmd(parent *cobra.Command) { +func addIndexAddCmd(parent *cobra.Command, showAlphaHelp bool) { indexCmd := &cobra.Command{ Use: "add", Short: "Add operator bundles to an index.", @@ -78,8 +78,10 @@ func addIndexAddCmd(parent *cobra.Command) { logrus.Panic(err.Error()) } indexCmd.Flags().Bool("enable-alpha", false, "enable unsupported alpha features of the OPM CLI") - if err := indexCmd.Flags().MarkHidden("enable-alpha"); err != nil { - logrus.Panic(err.Error()) + if !showAlphaHelp { + if err := indexCmd.Flags().MarkHidden("enable-alpha"); err != nil { + logrus.Panic(err.Error()) + } } if err := indexCmd.Flags().MarkHidden("debug"); err != nil { logrus.Panic(err.Error()) diff --git a/cmd/opm/index/cmd.go b/cmd/opm/index/cmd.go index dd3f75d94..8ee008e09 100644 --- a/cmd/opm/index/cmd.go +++ b/cmd/opm/index/cmd.go @@ -8,7 +8,7 @@ import ( ) // AddCommand adds the index subcommand to the given parent command. -func AddCommand(parent *cobra.Command) { +func AddCommand(parent *cobra.Command, showAlphaHelp bool) { cmd := &cobra.Command{ Use: "index", Short: "generate operator index container images", @@ -34,7 +34,7 @@ func AddCommand(parent *cobra.Command) { parent.AddCommand(cmd) cmd.AddCommand(newIndexDeleteCmd()) - addIndexAddCmd(cmd) + addIndexAddCmd(cmd, showAlphaHelp) cmd.AddCommand(newIndexExportCmd()) cmd.AddCommand(newIndexPruneCmd()) cmd.AddCommand(newIndexDeprecateTruncateCmd()) diff --git a/cmd/opm/main.go b/cmd/opm/main.go index bd3c29166..c88f52148 100644 --- a/cmd/opm/main.go +++ b/cmd/opm/main.go @@ -10,7 +10,8 @@ import ( ) func main() { - cmd := root.NewCmd() + showAlphaHelp := os.Getenv("HELP_ALPHA") == "true" + cmd := root.NewCmd(showAlphaHelp) if err := cmd.Execute(); err != nil { agg, ok := err.(utilerrors.Aggregate) if !ok { diff --git a/cmd/opm/registry/add.go b/cmd/opm/registry/add.go index dd7237e37..12e1fa308 100644 --- a/cmd/opm/registry/add.go +++ b/cmd/opm/registry/add.go @@ -14,7 +14,7 @@ import ( "github.com/operator-framework/operator-registry/pkg/sqlite" ) -func newRegistryAddCmd() *cobra.Command { +func newRegistryAddCmd(showAlphaHelp bool) *cobra.Command { rootCmd := &cobra.Command{ Use: "add", Short: "add operator bundle to operator registry DB", @@ -48,8 +48,10 @@ func newRegistryAddCmd() *cobra.Command { logrus.Panic(err.Error()) } rootCmd.Flags().Bool("enable-alpha", false, "enable unsupported alpha features of the OPM CLI") - if err := rootCmd.Flags().MarkHidden("enable-alpha"); err != nil { - logrus.Panic(err.Error()) + if !showAlphaHelp { + if err := rootCmd.Flags().MarkHidden("enable-alpha"); err != nil { + logrus.Panic(err.Error()) + } } if err := rootCmd.Flags().MarkDeprecated("skip-tls", "use --use-http and --skip-tls-verify instead"); err != nil { logrus.Panic(err.Error()) diff --git a/cmd/opm/registry/cmd.go b/cmd/opm/registry/cmd.go index f4b058bc8..dde29cf0a 100644 --- a/cmd/opm/registry/cmd.go +++ b/cmd/opm/registry/cmd.go @@ -8,7 +8,7 @@ import ( ) // NewOpmRegistryCmd returns the appregistry-server command -func NewOpmRegistryCmd() *cobra.Command { +func NewOpmRegistryCmd(showAlphaHelp bool) *cobra.Command { rootCmd := &cobra.Command{ Use: "registry", Short: "interact with operator-registry database", @@ -28,7 +28,7 @@ func NewOpmRegistryCmd() *cobra.Command { } rootCmd.AddCommand(newRegistryServeCmd()) - rootCmd.AddCommand(newRegistryAddCmd()) + rootCmd.AddCommand(newRegistryAddCmd(showAlphaHelp)) rootCmd.AddCommand(newRegistryRmCmd()) rootCmd.AddCommand(newRegistryPruneCmd()) rootCmd.AddCommand(newRegistryPruneStrandedCmd()) diff --git a/cmd/opm/render/cmd.go b/cmd/opm/render/cmd.go index e624ab197..88c706933 100644 --- a/cmd/opm/render/cmd.go +++ b/cmd/opm/render/cmd.go @@ -4,6 +4,7 @@ import ( "io" "log" "os" + "text/template" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -14,19 +15,19 @@ import ( "github.com/operator-framework/operator-registry/pkg/sqlite" ) -func NewCmd() *cobra.Command { +func NewCmd(showAlphaHelp bool) *cobra.Command { var ( - render action.Render - output string + render action.Render + output string + imageRefTemplate string ) cmd := &cobra.Command{ - Use: "render [index-image | bundle-image | sqlite-file]...", + Use: "render [catalog-image | catalog-directory | bundle-image | bundle-directory | sqlite-file]...", Short: "Generate a stream of file-based catalog objects from catalogs and bundles", Long: `Generate a stream of file-based catalog objects to stdout from the provided catalog images, file-based catalog directories, bundle images, and sqlite database files. - -` + sqlite.DeprecationMessage, +`, Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { render.Refs = args @@ -54,6 +55,14 @@ database files. render.Registry = reg + if imageRefTemplate != "" { + tmpl, err := template.New("image-ref-template").Parse(imageRefTemplate) + if err != nil { + log.Fatalf("invalid image reference template: %v", err) + } + render.ImageRefTemplate = tmpl + } + cfg, err := render.Run(cmd.Context()) if err != nil { log.Fatal(err) @@ -66,6 +75,23 @@ database files. } cmd.Flags().StringVarP(&output, "output", "o", "json", "Output format of the streamed file-based catalog objects (json|yaml)") cmd.Flags().BoolVar(&render.Migrate, "migrate", false, "Perform migrations on the rendered FBC") + + // Alpha flags + cmd.Flags().StringVar(&imageRefTemplate, "alpha-image-ref-template", "", "When bundle image reference information is unavailable, populate it with this template") + + if showAlphaHelp { + cmd.Long += ` +If rendering sources that do not carry bundle image reference information +(e.g. bundle directories), the --alpha-image-ref-template flag can be used to +generate image references for the rendered file-based catalog objects. +This is useful when generating a catalog with image references prior to +those images actually existing. Available template variables are: + - {{.Package}} : the package name the bundle belongs to + - {{.Name}} : the name of the bundle (for registry+v1 bundles, this is the CSV name) + - {{.Version}} : the version of the bundle +` + } + cmd.Long += "\n" + sqlite.DeprecationMessage return cmd } diff --git a/cmd/opm/root/cmd.go b/cmd/opm/root/cmd.go index 71631c612..d23657aee 100644 --- a/cmd/opm/root/cmd.go +++ b/cmd/opm/root/cmd.go @@ -1,8 +1,12 @@ package root import ( + "fmt" + "strings" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/spf13/pflag" "github.com/operator-framework/operator-registry/cmd/opm/alpha" "github.com/operator-framework/operator-registry/cmd/opm/generate" @@ -16,11 +20,13 @@ import ( "github.com/operator-framework/operator-registry/cmd/opm/version" ) -func NewCmd() *cobra.Command { +func NewCmd(showAlphaHelp bool) *cobra.Command { cmd := &cobra.Command{ Use: "opm", Short: "operator package manager", - Long: "CLI to interact with operator-registry and build indexes of operator content", + Long: `CLI to interact with operator-registry and build indexes of operator content. + +To view help related to alpha features, set HELP_ALPHA=true in the environment.`, PreRunE: func(cmd *cobra.Command, _ []string) error { if debug, _ := cmd.Flags().GetBool("debug"); debug { logrus.SetLevel(logrus.DebugLevel) @@ -38,8 +44,8 @@ func NewCmd() *cobra.Command { logrus.Panic(err.Error()) } - cmd.AddCommand(registry.NewOpmRegistryCmd(), alpha.NewCmd(), initcmd.NewCmd(), migrate.NewCmd(), serve.NewCmd(), render.NewCmd(), validate.NewCmd(), generate.NewCmd()) - index.AddCommand(cmd) + cmd.AddCommand(registry.NewOpmRegistryCmd(showAlphaHelp), alpha.NewCmd(showAlphaHelp), initcmd.NewCmd(), migrate.NewCmd(), serve.NewCmd(), render.NewCmd(showAlphaHelp), validate.NewCmd(), generate.NewCmd()) + index.AddCommand(cmd, showAlphaHelp) version.AddCommand(cmd) cmd.Flags().Bool("debug", false, "enable debug logging") @@ -47,5 +53,22 @@ func NewCmd() *cobra.Command { logrus.Panic(err.Error()) } + // Mark all alpha flags as hidden and prepend their usage with an alpha warning + configureAlphaFlags(cmd, !showAlphaHelp) + return cmd } + +func configureAlphaFlags(cmd *cobra.Command, hideFlags bool) { + cmd.Flags().VisitAll(func(f *pflag.Flag) { + if strings.HasPrefix(f.Name, "alpha-") { + if hideFlags { + f.Hidden = true + } + f.Usage = fmt.Sprintf("(ALPHA: This flag will be removed or renamed in a future release, potentially without notice) %s", f.Usage) + } + }) + for _, subCmd := range cmd.Commands() { + configureAlphaFlags(subCmd, hideFlags) + } +} diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go index a7a6e69ce..366c364b0 100644 --- a/test/e2e/e2e_suite_test.go +++ b/test/e2e/e2e_suite_test.go @@ -55,7 +55,7 @@ var _ = BeforeSuite(func() { deprovision = ctx.MustProvision(ctx.Ctx()) - opm = opmroot.NewCmd() // Creating multiple instances would cause flag registration conflicts + opm = opmroot.NewCmd(false) // Creating multiple instances would cause flag registration conflicts }) func configureRegistry() {