diff --git a/internal/cmd/operator-sdk/cleanup/cmd.go b/internal/cmd/operator-sdk/cleanup/cmd.go index 84eeb18c1e..c4768ec23d 100644 --- a/internal/cmd/operator-sdk/cleanup/cmd.go +++ b/internal/cmd/operator-sdk/cleanup/cmd.go @@ -21,7 +21,7 @@ import ( log "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "github.com/operator-framework/operator-sdk/internal/operator" + "github.com/operator-framework/operator-sdk/internal/olm/operator" ) func NewCmd() *cobra.Command { diff --git a/internal/cmd/operator-sdk/olm/install.go b/internal/cmd/operator-sdk/olm/install.go index 45317cbc64..4ac66e9fb5 100644 --- a/internal/cmd/operator-sdk/olm/install.go +++ b/internal/cmd/operator-sdk/olm/install.go @@ -15,14 +15,14 @@ package olm import ( - "github.com/operator-framework/operator-sdk/internal/olm" + "github.com/operator-framework/operator-sdk/internal/olm/installer" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) func newInstallCmd() *cobra.Command { - mgr := &olm.Manager{} + mgr := &installer.Manager{} cmd := &cobra.Command{ Use: "install", Short: "Install Operator Lifecycle Manager in your cluster", @@ -34,7 +34,7 @@ func newInstallCmd() *cobra.Command { }, } - cmd.Flags().StringVar(&mgr.Version, "version", olm.DefaultVersion, "version of OLM resources to install") + cmd.Flags().StringVar(&mgr.Version, "version", installer.DefaultVersion, "version of OLM resources to install") mgr.AddToFlagSet(cmd.Flags()) return cmd } diff --git a/internal/cmd/operator-sdk/olm/install_test.go b/internal/cmd/operator-sdk/olm/install_test.go index f79c9c1ac0..9b0854dc9b 100644 --- a/internal/cmd/operator-sdk/olm/install_test.go +++ b/internal/cmd/operator-sdk/olm/install_test.go @@ -17,7 +17,8 @@ package olm import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "github.com/operator-framework/operator-sdk/internal/olm" + + "github.com/operator-framework/operator-sdk/internal/olm/installer" ) var _ = Describe("Running an olm install command", func() { @@ -30,7 +31,7 @@ var _ = Describe("Running an olm install command", func() { flag := cmd.Flags().Lookup("version") Expect(flag).NotTo(BeNil()) - Expect(flag.DefValue).To(Equal(olm.DefaultVersion)) + Expect(flag.DefValue).To(Equal(installer.DefaultVersion)) Expect(flag.Usage).NotTo(BeNil()) }) }) diff --git a/internal/cmd/operator-sdk/olm/status.go b/internal/cmd/operator-sdk/olm/status.go index 7caeb6c8f2..a054f01ce5 100644 --- a/internal/cmd/operator-sdk/olm/status.go +++ b/internal/cmd/operator-sdk/olm/status.go @@ -15,14 +15,14 @@ package olm import ( - "github.com/operator-framework/operator-sdk/internal/olm" - log "github.com/sirupsen/logrus" "github.com/spf13/cobra" + + "github.com/operator-framework/operator-sdk/internal/olm/installer" ) func newStatusCmd() *cobra.Command { - mgr := olm.Manager{} + mgr := installer.Manager{} cmd := &cobra.Command{ Use: "status", Short: "Get the status of the Operator Lifecycle Manager installation in your cluster", @@ -34,7 +34,7 @@ func newStatusCmd() *cobra.Command { }, } - cmd.Flags().StringVar(&mgr.OLMNamespace, "olm-namespace", olm.DefaultOLMNamespace, "namespace where OLM is installed") + cmd.Flags().StringVar(&mgr.OLMNamespace, "olm-namespace", installer.DefaultOLMNamespace, "namespace where OLM is installed") cmd.Flags().StringVar(&mgr.Version, "version", "", "version of OLM installed on cluster; if unset"+ "operator-sdk attempts to auto-discover the version") mgr.AddToFlagSet(cmd.Flags()) diff --git a/internal/cmd/operator-sdk/olm/status_test.go b/internal/cmd/operator-sdk/olm/status_test.go index e98c66a4db..9ad5cd57cf 100644 --- a/internal/cmd/operator-sdk/olm/status_test.go +++ b/internal/cmd/operator-sdk/olm/status_test.go @@ -17,7 +17,8 @@ package olm import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "github.com/operator-framework/operator-sdk/internal/olm" + + "github.com/operator-framework/operator-sdk/internal/olm/installer" ) var _ = Describe("Running an olm status command", func() { @@ -30,7 +31,7 @@ var _ = Describe("Running an olm status command", func() { flag := cmd.Flags().Lookup("olm-namespace") Expect(flag).NotTo(BeNil()) - Expect(flag.DefValue).To(Equal(olm.DefaultOLMNamespace)) + Expect(flag.DefValue).To(Equal(installer.DefaultOLMNamespace)) Expect(flag.Usage).NotTo(BeNil()) flag = cmd.Flags().Lookup("version") diff --git a/internal/cmd/operator-sdk/olm/uninstall.go b/internal/cmd/operator-sdk/olm/uninstall.go index 8e7284d03f..fde30af1fb 100644 --- a/internal/cmd/operator-sdk/olm/uninstall.go +++ b/internal/cmd/operator-sdk/olm/uninstall.go @@ -15,14 +15,14 @@ package olm import ( - "github.com/operator-framework/operator-sdk/internal/olm" + "github.com/operator-framework/operator-sdk/internal/olm/installer" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) func newUninstallCmd() *cobra.Command { - mgr := olm.Manager{} + mgr := installer.Manager{} cmd := &cobra.Command{ Use: "uninstall", Short: "Uninstall Operator Lifecycle Manager from your cluster", @@ -35,7 +35,7 @@ func newUninstallCmd() *cobra.Command { } cmd.Flags().StringVar(&mgr.Version, "version", "", "version of OLM resources to uninstall.") - cmd.Flags().StringVar(&mgr.OLMNamespace, "olm-namespace", olm.DefaultOLMNamespace, + cmd.Flags().StringVar(&mgr.OLMNamespace, "olm-namespace", installer.DefaultOLMNamespace, "namespace from where OLM is to be uninstalled.") mgr.AddToFlagSet(cmd.Flags()) return cmd diff --git a/internal/cmd/operator-sdk/olm/uninstall_test.go b/internal/cmd/operator-sdk/olm/uninstall_test.go index 57180d19b0..adfcfd6602 100644 --- a/internal/cmd/operator-sdk/olm/uninstall_test.go +++ b/internal/cmd/operator-sdk/olm/uninstall_test.go @@ -17,7 +17,7 @@ package olm import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "github.com/operator-framework/operator-sdk/internal/olm" + "github.com/operator-framework/operator-sdk/internal/olm/installer" ) var _ = Describe("Running an olm uninstall command", func() { @@ -35,7 +35,7 @@ var _ = Describe("Running an olm uninstall command", func() { flag = cmd.Flags().Lookup("olm-namespace") Expect(flag).NotTo(BeNil()) - Expect(flag.DefValue).To(Equal(olm.DefaultOLMNamespace)) + Expect(flag.DefValue).To(Equal(installer.DefaultOLMNamespace)) Expect(flag.Usage).NotTo(BeNil()) }) }) diff --git a/internal/cmd/operator-sdk/run/bundle/cmd.go b/internal/cmd/operator-sdk/run/bundle/cmd.go index 153893c420..bea663a168 100644 --- a/internal/cmd/operator-sdk/run/bundle/cmd.go +++ b/internal/cmd/operator-sdk/run/bundle/cmd.go @@ -21,18 +21,13 @@ import ( "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "github.com/operator-framework/operator-sdk/internal/operator" - "github.com/operator-framework/operator-sdk/internal/operator/bundle" + "github.com/operator-framework/operator-sdk/internal/olm/operator" + "github.com/operator-framework/operator-sdk/internal/olm/operator/bundle" ) -func NewCmd() *cobra.Command { +func NewCmd(cfg *operator.Configuration) *cobra.Command { var timeout time.Duration - // TODO(joelanford): move the initialization of cfg up to - // the "run" subcommand when migrating packagemanifests - // to this design. - cfg := &operator.Configuration{} - i := bundle.NewInstall(cfg) cmd := &cobra.Command{ Use: "bundle ", @@ -48,11 +43,10 @@ func NewCmd() *cobra.Command { i.BundleImage = args[0] // TODO(joelanford): Add cleanup logic if this fails? - csv, err := i.Run(ctx) + _, err := i.Run(ctx) if err != nil { logrus.Fatalf("Failed to run bundle: %v\n", err) } - logrus.Infof("CSV %q installed\n", csv.Name) }, } cmd.Flags().SortFlags = false diff --git a/internal/cmd/operator-sdk/run/cmd.go b/internal/cmd/operator-sdk/run/cmd.go index 66c9636ecd..539a245d23 100644 --- a/internal/cmd/operator-sdk/run/cmd.go +++ b/internal/cmd/operator-sdk/run/cmd.go @@ -18,6 +18,7 @@ import ( "github.com/spf13/cobra" "github.com/operator-framework/operator-sdk/internal/cmd/operator-sdk/run/packagemanifests" + "github.com/operator-framework/operator-sdk/internal/olm/operator" ) func NewCmd() *cobra.Command { @@ -29,10 +30,12 @@ func NewCmd() *cobra.Command { Currently only the package manifests format is supported via the 'packagemanifests' subcommand.`, } + cfg := &operator.Configuration{} + cmd.AddCommand( // TODO(joelanford): enable bundle command when implementation is complete //bundle.NewCmd(), - packagemanifests.NewCmd(), + packagemanifests.NewCmd(cfg), ) return cmd diff --git a/internal/cmd/operator-sdk/run/cmd_test.go b/internal/cmd/operator-sdk/run/cmd_test.go index a7604258f0..c7784db851 100644 --- a/internal/cmd/operator-sdk/run/cmd_test.go +++ b/internal/cmd/operator-sdk/run/cmd_test.go @@ -30,7 +30,7 @@ var _ = Describe("Running a run command", func() { subcommands := cmd.Commands() Expect(len(subcommands)).To(Equal(1)) - Expect(subcommands[0].Use).To(Equal("packagemanifests ")) + Expect(subcommands[0].Use).To(Equal("packagemanifests [packagemanifests-root-dir]")) }) }) }) diff --git a/internal/cmd/operator-sdk/run/packagemanifests/packagemanifests.go b/internal/cmd/operator-sdk/run/packagemanifests/packagemanifests.go index da97440b4e..c37fe212ef 100644 --- a/internal/cmd/operator-sdk/run/packagemanifests/packagemanifests.go +++ b/internal/cmd/operator-sdk/run/packagemanifests/packagemanifests.go @@ -15,64 +15,50 @@ package packagemanifests import ( - "fmt" + "context" + "time" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" - olmoperator "github.com/operator-framework/operator-sdk/internal/olm/operator" + "github.com/operator-framework/operator-sdk/internal/olm/operator" + "github.com/operator-framework/operator-sdk/internal/olm/operator/packagemanifests" ) -type packagemanifestsCmd struct { - olmoperator.PackageManifestsCmd -} - -func NewCmd() *cobra.Command { - c := &packagemanifestsCmd{} +func NewCmd(cfg *operator.Configuration) *cobra.Command { + var timeout time.Duration + i := packagemanifests.NewInstall(cfg) cmd := &cobra.Command{ - Use: "packagemanifests ", + Use: "packagemanifests [packagemanifests-root-dir]", Short: "Deploy an Operator in the package manifests format with OLM", Long: `'run packagemanifests' deploys an Operator's package manifests with OLM. The command's argument -must be set to a valid package manifests root directory, ex. '/packagemanifests'.`, - Aliases: []string{"pm"}, - PreRunE: func(cmd *cobra.Command, args []string) error { - err := c.validate(args) - if err != nil { - log.Fatalf("Failed to validate input: %v", err) +will default to './packagemanifests' if unset; if set, the argument must be a package manifests root directory, +ex. '/packagemanifests'.`, + Aliases: []string{"pm"}, + Args: cobra.MaximumNArgs(1), + PersistentPreRunE: func(_ *cobra.Command, _ []string) error { return cfg.Load() }, + Run: func(cmd *cobra.Command, args []string) { + ctx, cancel := context.WithTimeout(cmd.Context(), timeout) + defer cancel() + + if len(args) == 0 { + i.PackageManifestsDirectory = "packagemanifests" + } else { + i.PackageManifestsDirectory = args[0] } - c.setDefaults(args) - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - log.Infof("Running operator from directory %s", c.ManifestsDir) - if err := c.Run(); err != nil { - log.Fatalf("Failed to run operator: %v", err) + // TODO(joelanford): Add cleanup logic if this fails? + _, err := i.Run(ctx) + if err != nil { + log.Fatalf("Failed to run packagemanifests: %v\n", err) } - return nil }, } + cmd.Flags().SortFlags = false + cfg.BindFlags(cmd.PersistentFlags()) + i.BindFlags(cmd.Flags()) - c.PackageManifestsCmd.AddToFlagSet(cmd.Flags()) - + cmd.Flags().DurationVar(&timeout, "timeout", 2*time.Minute, "install timeout") return cmd } - -func (c *packagemanifestsCmd) validate(args []string) error { - if len(args) > 0 { - if len(args) > 1 { - return fmt.Errorf("exactly one argument is required") - } - } - - return nil -} - -func (c *packagemanifestsCmd) setDefaults(args []string) { - if len(args) != 0 { - c.ManifestsDir = args[0] - } else { - c.ManifestsDir = "packagemanifests" - } -} diff --git a/internal/cmd/operator-sdk/run/packagemanifests/packagemanifests_test.go b/internal/cmd/operator-sdk/run/packagemanifests/packagemanifests_test.go index f963baaacc..ceb9b8a36f 100644 --- a/internal/cmd/operator-sdk/run/packagemanifests/packagemanifests_test.go +++ b/internal/cmd/operator-sdk/run/packagemanifests/packagemanifests_test.go @@ -17,12 +17,15 @@ package packagemanifests import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + + "github.com/operator-framework/operator-sdk/internal/olm/operator" ) var _ = Describe("Running a run packagemanifests command", func() { Describe("NewCmd", func() { It("builds a cobra command", func() { - cmd := NewCmd() + cfg := &operator.Configuration{} + cmd := NewCmd(cfg) Expect(cmd).NotTo(BeNil()) Expect(cmd.Use).NotTo(BeNil()) Expect(cmd.Short).NotTo(BeNil()) @@ -32,39 +35,4 @@ var _ = Describe("Running a run packagemanifests command", func() { Expect(aliases[0]).To(Equal("pm")) }) }) - Describe("validate", func() { - var ( - c packagemanifestsCmd - err error - ) - BeforeEach(func() { - c = packagemanifestsCmd{} - }) - It("fails if provided more than 1 arg", func() { - err = c.validate([]string{"foo", "bar"}) - Expect(err).NotTo(BeNil()) - Expect(err.Error()).To(ContainSubstring("exactly one argument is required")) - }) - It("succeeds and if exactly 1 arg is provided", func() { - arg := "baz" - err = c.validate([]string{arg}) - Expect(err).To(BeNil()) - }) - }) - Describe("setDefaults", func() { - var ( - c packagemanifestsCmd - ) - BeforeEach(func() { - c = packagemanifestsCmd{} - }) - It("defaults to 'packagemanifests' if no args are provided", func() { - c.setDefaults([]string{}) - Expect(c.ManifestsDir).To(Equal("packagemanifests")) - }) - It("sets ManifestDir to the first arg if provided more than 0", func() { - c.setDefaults([]string{"config/potato"}) - Expect(c.ManifestsDir).To(Equal("config/potato")) - }) - }) }) diff --git a/internal/olm/client/client.go b/internal/olm/client/client.go index 9cfb7341fd..0bf36877f6 100644 --- a/internal/olm/client/client.go +++ b/internal/olm/client/client.go @@ -15,7 +15,7 @@ // Package olm provides an API to install, uninstall, and check the // status of an Operator Lifecycle Manager installation. // TODO: move to OLM repository? -package olm +package client import ( "context" @@ -56,7 +56,7 @@ type Client struct { KubeClient client.Client } -func ClientForConfig(cfg *rest.Config) (*Client, error) { +func NewClientForConfig(cfg *rest.Config) (*Client, error) { rm, err := apiutil.NewDynamicRESTMapper(cfg) if err != nil { return nil, fmt.Errorf("failed to create dynamic rest mapper: %v", err) diff --git a/internal/olm/client/status.go b/internal/olm/client/status.go index e51edf4cc7..2585d6ec59 100644 --- a/internal/olm/client/status.go +++ b/internal/olm/client/status.go @@ -15,7 +15,7 @@ // Package olm provides an API to install, uninstall, and check the // status of an Operator Lifecycle Manager installation. // TODO: move to OLM repository? -package olm +package client import ( "bytes" diff --git a/internal/olm/client.go b/internal/olm/installer/client.go similarity index 99% rename from internal/olm/client.go rename to internal/olm/installer/client.go index 1a69e1e061..155f923114 100644 --- a/internal/olm/client.go +++ b/internal/olm/installer/client.go @@ -15,7 +15,7 @@ // Package olm provides an API to install, uninstall, and check the // status of an Operator Lifecycle Manager installation. // TODO: move to OLM repository? -package olm +package installer import ( "context" @@ -52,7 +52,7 @@ type Client struct { } func ClientForConfig(cfg *rest.Config) (*Client, error) { - cl, err := olmresourceclient.ClientForConfig(cfg) + cl, err := olmresourceclient.NewClientForConfig(cfg) if err != nil { return nil, fmt.Errorf("failed to get OLM resource client: %v", err) } diff --git a/internal/olm/manager.go b/internal/olm/installer/manager.go similarity index 99% rename from internal/olm/manager.go rename to internal/olm/installer/manager.go index b32aadbf74..981a8b7b50 100644 --- a/internal/olm/manager.go +++ b/internal/olm/installer/manager.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package olm +package installer import ( "context" diff --git a/internal/operator/bundle/install.go b/internal/olm/operator/bundle/install.go similarity index 75% rename from internal/operator/bundle/install.go rename to internal/olm/operator/bundle/install.go index 769ed7f808..367610e861 100644 --- a/internal/operator/bundle/install.go +++ b/internal/olm/operator/bundle/install.go @@ -21,27 +21,30 @@ import ( "path/filepath" "strings" + apimanifests "github.com/operator-framework/api/pkg/manifests" "github.com/operator-framework/api/pkg/operators/v1alpha1" - "github.com/operator-framework/operator-registry/pkg/registry" "github.com/spf13/pflag" - "github.com/operator-framework/operator-sdk/internal/operator" - "github.com/operator-framework/operator-sdk/internal/operator/internal" + "github.com/operator-framework/operator-sdk/internal/olm/operator" + "github.com/operator-framework/operator-sdk/internal/olm/operator/registry" registryutil "github.com/operator-framework/operator-sdk/internal/registry" ) type Install struct { BundleImage string - *internal.IndexImageCatalogCreator - *internal.OperatorInstaller + *registry.IndexImageCatalogCreator + *registry.OperatorInstaller + + cfg *operator.Configuration } func NewInstall(cfg *operator.Configuration) Install { i := Install{ - OperatorInstaller: internal.NewOperatorInstaller(cfg), + OperatorInstaller: registry.NewOperatorInstaller(cfg), + cfg: cfg, } - i.IndexImageCatalogCreator = internal.NewIndexImageCatalogCreator(cfg) + i.IndexImageCatalogCreator = registry.NewIndexImageCatalogCreator(cfg) i.CatalogCreator = i.IndexImageCatalogCreator return i } @@ -65,7 +68,11 @@ func (i Install) Run(ctx context.Context) (*v1alpha1.ClusterServiceVersion, erro func (i *Install) setup(ctx context.Context) error { labels, csv, err := loadBundle(ctx, i.BundleImage) if err != nil { - return fmt.Errorf("load bundle: %v", err) + return err + } + + if err := i.InstallMode.CheckCompatibility(csv, i.cfg.Namespace); err != nil { + return err } i.OperatorInstaller.PackageName = labels["operators.operatorframework.io.bundle.package.v1"] @@ -73,6 +80,7 @@ func (i *Install) setup(ctx context.Context) error { i.OperatorInstaller.StartingCSV = csv.Name i.OperatorInstaller.Channel = strings.Split(labels["operators.operatorframework.io.bundle.channels.v1"], ",")[0] + i.IndexImageCatalogCreator.PackageName = i.OperatorInstaller.PackageName i.IndexImageCatalogCreator.InjectBundles = []string{i.BundleImage} i.IndexImageCatalogCreator.InjectBundleMode = "replaces" if i.IndexImageCatalogCreator.IndexImage == defaultIndexImage { @@ -82,7 +90,7 @@ func (i *Install) setup(ctx context.Context) error { return nil } -func loadBundle(ctx context.Context, bundleImage string) (labels registryutil.Labels, csv *registry.ClusterServiceVersion, err error) { +func loadBundle(ctx context.Context, bundleImage string) (registryutil.Labels, *v1alpha1.ClusterServiceVersion, error) { bundlePath, err := registryutil.ExtractBundleImage(ctx, nil, bundleImage, false) if err != nil { return nil, nil, fmt.Errorf("pull bundle image: %v", err) @@ -91,7 +99,7 @@ func loadBundle(ctx context.Context, bundleImage string) (labels registryutil.La _ = os.RemoveAll(bundlePath) }() - labels, _, err = registryutil.FindBundleMetadata(bundlePath) + labels, _, err := registryutil.FindBundleMetadata(bundlePath) if err != nil { return nil, nil, fmt.Errorf("load bundle metadata: %v", err) } @@ -101,10 +109,10 @@ func loadBundle(ctx context.Context, bundleImage string) (labels registryutil.La return nil, nil, fmt.Errorf("manifests directory not defined in bundle metadata") } manifestsDir := filepath.Join(bundlePath, relManifestsDir) - csv, err = registry.ReadCSVFromBundleDirectory(manifestsDir) + bundle, err := apimanifests.GetBundleFromDir(manifestsDir) if err != nil { - return nil, nil, fmt.Errorf("read bundle csv: %v", err) + return nil, nil, fmt.Errorf("load bundle: %v", err) } - return labels, csv, nil + return labels, bundle.CSV, nil } diff --git a/internal/operator/config.go b/internal/olm/operator/config.go similarity index 100% rename from internal/operator/config.go rename to internal/olm/operator/config.go diff --git a/internal/operator/constants.go b/internal/olm/operator/constants.go similarity index 100% rename from internal/operator/constants.go rename to internal/olm/operator/constants.go diff --git a/internal/operator/internal/install_mode.go b/internal/olm/operator/install_mode.go similarity index 79% rename from internal/operator/internal/install_mode.go rename to internal/olm/operator/install_mode.go index dd5dda6e4c..0be3cb3589 100644 --- a/internal/operator/internal/install_mode.go +++ b/internal/olm/operator/install_mode.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package internal +package operator import ( "flag" @@ -90,3 +90,18 @@ func (i InstallMode) Validate() error { } return nil } + +// CheckCompatibility checks if an InstallMode is compatible with the operator's namespace and is supported by csv. +func (i InstallMode) CheckCompatibility(csv *v1alpha1.ClusterServiceVersion, operatorNamespace string) error { + if i.InstallModeType == v1alpha1.InstallModeTypeOwnNamespace { + if i.TargetNamespaces[0] != operatorNamespace { + return fmt.Errorf("install mode %s must match operator namespace %q", i, operatorNamespace) + } + } + for _, mode := range csv.Spec.InstallModes { + if mode.Type == i.InstallModeType && !mode.Supported { + return fmt.Errorf("install mode type %q not supported in CSV %q", i.InstallModeType, csv.GetName()) + } + } + return nil +} diff --git a/internal/olm/operator/olm.go b/internal/olm/operator/olm.go deleted file mode 100644 index 9726ad0468..0000000000 --- a/internal/olm/operator/olm.go +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2019 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package olm - -import ( - "fmt" - - apimanifests "github.com/operator-framework/api/pkg/manifests" - operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" - operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/operator-framework/operator-sdk/internal/operator" - "github.com/operator-framework/operator-sdk/internal/util/k8sutil" -) - -func getSubscriptionName(csvName string) string { - name := k8sutil.FormatOperatorNameDNS1123(csvName) - return fmt.Sprintf("%s-sub", name) -} - -// getChannelForCSVName returns the channel for a given csvName. csvName -// has the format "{operator-name}.(v)?{X.Y.Z}". An error is returned if -// no channel with current CSV name csvName is found. -func getChannelForCSVName(pkg *apimanifests.PackageManifest, csvName string) (apimanifests.PackageChannel, error) { - for _, c := range pkg.Channels { - if c.CurrentCSVName == csvName { - return c, nil - } - } - return apimanifests.PackageChannel{}, fmt.Errorf("no channel in package manifest %s exists for CSV %s", - pkg.PackageName, csvName) -} - -// withCatalogSource returns a function that sets the Subscription argument's -// target CatalogSource's name and namespace. -func withCatalogSource(csName, csNamespace string) func(*operatorsv1alpha1.Subscription) { - return func(sub *operatorsv1alpha1.Subscription) { - sub.Spec.CatalogSource = csName - sub.Spec.CatalogSourceNamespace = csNamespace - } -} - -// withPackageChannel returns a function that sets the Subscription argument's -// target package, channel, and starting CSV to those in channel. -func withPackageChannel(pkgName string, channel apimanifests.PackageChannel) func(*operatorsv1alpha1.Subscription) { - return func(sub *operatorsv1alpha1.Subscription) { - if sub.Spec == nil { - sub.Spec = &operatorsv1alpha1.SubscriptionSpec{} - } - sub.Spec.Package = pkgName - sub.Spec.Channel = channel.Name - sub.Spec.StartingCSV = channel.CurrentCSVName - } -} - -// newSubscription creates a new Subscription for a CSV with a name derived -// from csvName, the CSV's objectmeta.name, in namespace. opts will be applied -// to the Subscription object. -func newSubscription(csvName, namespace string, - opts ...func(*operatorsv1alpha1.Subscription)) *operatorsv1alpha1.Subscription { - sub := &operatorsv1alpha1.Subscription{ - TypeMeta: metav1.TypeMeta{ - APIVersion: operatorsv1alpha1.SchemeGroupVersion.String(), - Kind: operatorsv1alpha1.SubscriptionKind, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: getSubscriptionName(csvName), - Namespace: namespace, - }, - } - for _, opt := range opts { - opt(sub) - } - return sub -} - -func getCatalogSourceName(pkgName string) string { - name := k8sutil.FormatOperatorNameDNS1123(pkgName) - return fmt.Sprintf("%s-ocs", name) -} - -// newCatalogSource creates a new CatalogSource with a name derived from -// pkgName, the package manifest's packageName, in namespace. opts will -// be applied to the CatalogSource object. -func newCatalogSource(pkgName, namespace string, - opts ...func(*operatorsv1alpha1.CatalogSource)) *operatorsv1alpha1.CatalogSource { - cs := &operatorsv1alpha1.CatalogSource{ - TypeMeta: metav1.TypeMeta{ - APIVersion: operatorsv1alpha1.SchemeGroupVersion.String(), - Kind: operatorsv1alpha1.CatalogSourceKind, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: getCatalogSourceName(pkgName), - Namespace: namespace, - }, - Spec: operatorsv1alpha1.CatalogSourceSpec{ - DisplayName: pkgName, - Publisher: "operator-sdk", - }, - } - for _, opt := range opts { - opt(cs) - } - return cs -} - -// withGRPC returns a function that sets the OperatorGroup argument's -// targetNamespaces to namespaces. namespaces can be length 0..N; if -// namespaces length is 0, targetNamespaces is set to an empty string, -// indicating a global scope. -func withTargetNamespaces(namespaces ...string) func(*operatorsv1.OperatorGroup) { - return func(og *operatorsv1.OperatorGroup) { - if len(namespaces) != 0 && namespaces[0] != "" { - og.Spec.TargetNamespaces = namespaces - } - } -} - -// newSDKOperatorGroup creates a new OperatorGroup with name -// sdkOperatorGroupName in namespace. opts will be applied to the -// OperatorGroup object. Note that the default OperatorGroup has a global -// scope. -func newSDKOperatorGroup(namespace string, opts ...func(*operatorsv1.OperatorGroup)) *operatorsv1.OperatorGroup { - og := &operatorsv1.OperatorGroup{ - TypeMeta: metav1.TypeMeta{ - APIVersion: operatorsv1.SchemeGroupVersion.String(), - Kind: operatorsv1.OperatorGroupKind, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: operator.SDKOperatorGroupName, - Namespace: namespace, - }, - } - for _, opt := range opts { - opt(og) - } - return og -} diff --git a/internal/olm/operator/operator_manager.go b/internal/olm/operator/operator_manager.go deleted file mode 100644 index 906ebc8673..0000000000 --- a/internal/olm/operator/operator_manager.go +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2019 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package olm - -import ( - "context" - "fmt" - "log" - "sync" - "time" - - operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" - operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" - "github.com/spf13/pflag" - apiextinstall "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes/scheme" - - internalolmclient "github.com/operator-framework/operator-sdk/internal/olm/client" - "github.com/operator-framework/operator-sdk/internal/util/k8sutil" -) - -// TODO(estroz): figure out a good way to deal with creating scorecard objects -// and injecting proxy container - -const ( - defaultTimeout = time.Minute * 2 - defaultNamespace = "default" - - installModeFormat = "InstallModeType[=ns1,ns2[, ...]]" -) - -func init() { - // OLM schemes must be added to the global Scheme so controller-runtime's - // client recognizes OLM objects. - apiextinstall.Install(scheme.Scheme) - if err := operatorsv1.AddToScheme(scheme.Scheme); err != nil { - log.Fatalf("Failed to add OLM operator API v1 types to scheme: %v", err) - } -} - -// OperatorCmd configures deployment and teardown of an operator via OLM. -// Intended to be used by an exported struct, as it lackas a Run method. -type OperatorCmd struct { - // KubeconfigPath is the local path to a kubeconfig. This uses well-defined - // default loading rules to load the config if empty. - KubeconfigPath string - // Namespace is the cluster namespace in which operator resources are created. - // Namespace must already exist in the cluster. - Namespace string - // InstallMode specifies which supported installMode should be used to - // create an OperatorGroup. The format for this field is as follows: - // - // "InstallModeType=[ns1,ns2[, ...]]" - // - // The InstallModeType string passed must be marked as "supported" in the - // CSV being installed. The namespaces passed must exist in the cluster. - // An empty set of namespaces can be used for AllNamespaces. - InstallMode string - // Timeout dictates how long to wait for a REST call to complete. A call - // exceeding Timeout will generate an error. - Timeout time.Duration - // ForceRegistry forces deletion of registry resources. - ForceRegistry bool - - once sync.Once -} - -func (c *OperatorCmd) AddToFlagSet(fs *pflag.FlagSet) { - fs.StringVar(&c.KubeconfigPath, "kubeconfig", "", - "The file path to kubernetes configuration file. Defaults to location "+ - "specified by $KUBECONFIG, or to default file rules if not set") - fs.StringVar(&c.Namespace, "namespace", "", - "The namespace where operator resources are created. It must already exist in the cluster") - fs.StringVar(&c.InstallMode, "install-mode", "", - "InstallMode to create OperatorGroup with. Format: "+installModeFormat) - fs.DurationVar(&c.Timeout, "timeout", defaultTimeout, - "Time to wait for the command to complete before failing") -} - -func (c *OperatorCmd) validate() error { - if c.InstallMode != "" { - if _, _, err := parseInstallModeKV(c.InstallMode, c.Namespace); err != nil { - return err - } - } - return nil -} - -func (c *OperatorCmd) initialize() { - c.once.Do(func() { - if c.Timeout <= 0 { - c.Timeout = defaultTimeout - } - }) -} - -type operatorManager struct { - client *internalolmclient.Client - // Namespace in which operator and OLM objects are created. - namespace string - - installMode operatorsv1alpha1.InstallModeType //nolint:structcheck - targetNamespaces []string //nolint:structcheck -} - -func (c *OperatorCmd) newManager() (*operatorManager, error) { - m := &operatorManager{} - - // Cluster and operator namespace info. - // TODO(joelanford): Migrate this to use `internal/operator.Configuration` - rc, ns, err := k8sutil.GetKubeconfigAndNamespace(c.KubeconfigPath) - if err != nil { - return nil, fmt.Errorf("failed to get namespace from kubeconfig %s: %w", c.KubeconfigPath, err) - } - if ns == "" { - ns = defaultNamespace - } - if m.namespace = c.Namespace; m.namespace == "" { - m.namespace = ns - } - if m.client == nil { - m.client, err = internalolmclient.ClientForConfig(rc) - if err != nil { - return nil, fmt.Errorf("failed to create SDK OLM client: %w", err) - } - } - - return m, nil -} - -// TODO(estroz): check registry health on each "status" subcommand invocation -func (m *operatorManager) status(ctx context.Context, us ...*unstructured.Unstructured) internalolmclient.Status { - objs := []runtime.Object{} - for _, u := range us { - uc := u.DeepCopy() - uc.SetNamespace(m.namespace) - objs = append(objs, uc) - } - return m.client.GetObjectsStatus(ctx, objs...) -} diff --git a/internal/olm/operator/packagemanifests.go b/internal/olm/operator/packagemanifests.go deleted file mode 100644 index ca32eea8e6..0000000000 --- a/internal/olm/operator/packagemanifests.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2019 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package olm - -import ( - "context" - "errors" - "fmt" - "os" - - "github.com/spf13/pflag" -) - -// PackageManifestsCmd configures deployment and teardown of an operator -// managed in a package manifests format via OLM. -type PackageManifestsCmd struct { - OperatorCmd - - // ManifestsDir is a directory containing 1..N package directories and - // a package manifest. - // Version can be set to the version of the desired operator package - // and Run() will deploy that operator version. - ManifestsDir string - // Version is the version of the operator to deploy. It must be - // a semantic version, ex. 0.0.1. - Version string -} - -func (c *PackageManifestsCmd) AddToFlagSet(fs *pflag.FlagSet) { - c.OperatorCmd.AddToFlagSet(fs) - - fs.StringVar(&c.Version, "version", "", "Packaged version of the operator to deploy") -} - -func (c *PackageManifestsCmd) validate() error { - if c.ManifestsDir == "" { - return errors.New("manifests dir must be set") - } - manDirInfo, err := os.Stat(c.ManifestsDir) - if err != nil { - return err - } - if !manDirInfo.IsDir() { - return fmt.Errorf("%s must be a directory", c.ManifestsDir) - } - - if c.Version == "" { - return errors.New("operator version must be set") - } - - return c.OperatorCmd.validate() -} - -func (c *PackageManifestsCmd) initialize() { - c.OperatorCmd.initialize() -} - -func (c *PackageManifestsCmd) Run() error { - c.initialize() - if err := c.validate(); err != nil { - return fmt.Errorf("validation error: %w", err) - } - m, err := c.newManager() - if err != nil { - return fmt.Errorf("error initializing operator manager: %w", err) - } - ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) - defer cancel() - return m.run(ctx) -} diff --git a/internal/olm/operator/packagemanifests/install.go b/internal/olm/operator/packagemanifests/install.go new file mode 100644 index 0000000000..259f9a2ea0 --- /dev/null +++ b/internal/olm/operator/packagemanifests/install.go @@ -0,0 +1,127 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package packagemanifests + +import ( + "context" + "errors" + "fmt" + + apimanifests "github.com/operator-framework/api/pkg/manifests" + "github.com/operator-framework/api/pkg/operators/v1alpha1" + "github.com/spf13/pflag" + + "github.com/operator-framework/operator-sdk/internal/olm/operator" + "github.com/operator-framework/operator-sdk/internal/olm/operator/registry" +) + +type Install struct { + PackageManifestsDirectory string + Version string + + *registry.ConfigMapCatalogCreator + *registry.OperatorInstaller + + cfg *operator.Configuration +} + +func NewInstall(cfg *operator.Configuration) Install { + i := Install{ + ConfigMapCatalogCreator: registry.NewConfigMapCatalogCreator(cfg), + OperatorInstaller: registry.NewOperatorInstaller(cfg), + cfg: cfg, + } + i.OperatorInstaller.CatalogCreator = i.ConfigMapCatalogCreator + return i +} + +func (i *Install) BindFlags(fs *pflag.FlagSet) { + fs.Var(&i.InstallMode, "install-mode", "install mode") + fs.StringVar(&i.Version, "version", "", "Packaged version of the operator to deploy") +} + +func (i Install) Run(ctx context.Context) (*v1alpha1.ClusterServiceVersion, error) { + if err := i.setup(); err != nil { + return nil, err + } + return i.InstallOperator(ctx) +} + +func (i *Install) setup() error { + pkg, bundles, err := loadPackageManifests(i.PackageManifestsDirectory) + if err != nil { + return fmt.Errorf("load package manifests: %v", err) + } + bundle, err := getPackageForVersion(bundles, i.Version) + if err != nil { + return err + } + + if i.InstallMode.IsEmpty() { + i.InstallMode.InstallModeType = v1alpha1.InstallModeTypeAllNamespaces + } + if err := i.InstallMode.CheckCompatibility(bundle.CSV, i.cfg.Namespace); err != nil { + return err + } + + i.OperatorInstaller.PackageName = pkg.PackageName + i.OperatorInstaller.CatalogSourceName = fmt.Sprintf("%s-catalog", i.OperatorInstaller.PackageName) + i.OperatorInstaller.StartingCSV = bundle.CSV.GetName() + i.OperatorInstaller.Channel, err = getChannelForCSVName(pkg, i.OperatorInstaller.StartingCSV) + if err != nil { + return err + } + + i.ConfigMapCatalogCreator.Package = pkg + i.ConfigMapCatalogCreator.Bundles = bundles + + return nil +} + +func loadPackageManifests(rootDir string) (*apimanifests.PackageManifest, []*apimanifests.Bundle, error) { + // Operator bundles and metadata. + pkg, bundles, err := apimanifests.GetManifestsDir(rootDir) + if err != nil { + return nil, nil, err + } + if len(bundles) == 0 { + return nil, nil, errors.New("no packages found") + } + if pkg == nil || pkg.PackageName == "" { + return nil, nil, errors.New("no package manifest found") + } + return pkg, bundles, nil +} + +func getPackageForVersion(bundles []*apimanifests.Bundle, version string) (*apimanifests.Bundle, error) { + versions := []string{} + for _, bundle := range bundles { + verStr := bundle.CSV.Spec.Version.String() + if verStr == version { + return bundle, nil + } + versions = append(versions, verStr) + } + return nil, fmt.Errorf("no package found for version %s; valid versions: %+q", version, versions) +} + +func getChannelForCSVName(pkg *apimanifests.PackageManifest, csvName string) (string, error) { + for _, c := range pkg.Channels { + if c.CurrentCSVName == csvName { + return c.Name, nil + } + } + return "", fmt.Errorf("no channel in package manifest %s exists for CSV %s", pkg.PackageName, csvName) +} diff --git a/internal/olm/operator/packagemanifests_manager.go b/internal/olm/operator/packagemanifests_manager.go deleted file mode 100644 index 38e30eebc1..0000000000 --- a/internal/olm/operator/packagemanifests_manager.go +++ /dev/null @@ -1,234 +0,0 @@ -// Copyright 2019 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package olm - -import ( - "context" - "errors" - "fmt" - - apimanifests "github.com/operator-framework/api/pkg/manifests" - operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" - log "github.com/sirupsen/logrus" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/util/retry" - - internalregistry "github.com/operator-framework/operator-sdk/internal/olm/operator/internal" -) - -type packageManifestsManager struct { - *operatorManager - - version string - forceRegistry bool - pkg *apimanifests.PackageManifest - bundles []*apimanifests.Bundle -} - -func (c *PackageManifestsCmd) newManager() (m *packageManifestsManager, err error) { - m = &packageManifestsManager{ - version: c.Version, - forceRegistry: c.ForceRegistry, - } - if m.operatorManager, err = c.OperatorCmd.newManager(); err != nil { - return nil, err - } - - // Operator bundles and metadata. - m.pkg, m.bundles, err = apimanifests.GetManifestsDir(c.ManifestsDir) - if err != nil { - return nil, err - } - if len(m.bundles) == 0 { - return nil, errors.New("no packages found") - } - if m.pkg == nil || m.pkg.PackageName == "" { - return nil, errors.New("no package manifest found") - } - - // Handle installModes. - if c.InstallMode == "" { - // Default to AllNamespaces. - m.installMode = operatorsv1alpha1.InstallModeTypeAllNamespaces - m.targetNamespaces = []string{} - } else { - m.installMode, m.targetNamespaces, err = parseInstallModeKV(c.InstallMode, m.namespace) - if err != nil { - return nil, err - } - } - - // Ensure CSV supports installMode. - bundle, err := getPackageForVersion(m.bundles, m.version) - if err != nil { - return nil, err - } - if err := installModeCompatible(bundle.CSV, m.installMode, m.namespace, m.targetNamespaces); err != nil { - return nil, err - } - - return m, nil -} - -func (m *packageManifestsManager) run(ctx context.Context) (err error) { - // TODO: ensure OLM is installed by checking OLM CRDs. - - pkgName := m.pkg.PackageName - bundle, err := getPackageForVersion(m.bundles, m.version) - if err != nil { - return fmt.Errorf("error getting package for version %s: %w", m.version, err) - } - csv := bundle.CSV - - // Only check CSV here, since other deployed operators/versions may be - // running with shared CRDs. - obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(csv) - if err != nil { - return fmt.Errorf("error converting CSV to unstructured: %w", err) - } - u := unstructured.Unstructured{Object: obj} - status := m.status(ctx, &u) - if installed, err := status.HasInstalledResources(); installed { - return fmt.Errorf("an operator with name %q is already running\n%s", pkgName, status) - } else if err != nil { - return fmt.Errorf("an operator with name %q is present and has resource errors\n%s", pkgName, status) - } - - log.Info("Creating resources") - // Create OperatorGroup first to ensure no conflicts exist in m.namespace. - if err := m.createOperatorGroup(ctx, pkgName); err != nil { - return err - } - - // New CatalogSource. - catsrc := newCatalogSource(pkgName, m.namespace) - if err = m.client.DoCreate(ctx, catsrc); err != nil { - return fmt.Errorf("error creating catalog source: %w", err) - } - - if err = m.registryUp(ctx, catsrc, m.namespace); err != nil { - return fmt.Errorf("error creating registry resources: %w", err) - } - - if err := m.updateCatalogSource(ctx, pkgName, catsrc); err != nil { - return fmt.Errorf("error updating catalog source: %w", err) - } - - // New Subscription. - channel, err := getChannelForCSVName(m.pkg, csv.GetName()) - if err != nil { - return err - } - sub := newSubscription(csv.GetName(), m.namespace, - withPackageChannel(pkgName, channel), - withCatalogSource(getCatalogSourceName(pkgName), m.namespace)) - if err = m.client.DoCreate(ctx, sub); err != nil { - return fmt.Errorf("error creating operator resources: %w", err) - } - - // BUG(estroz): if namespace is not contained in targetNamespaces, - // DoCSVWait will fail because the CSV is not deployed in namespace. - nn := types.NamespacedName{ - Name: csv.GetName(), - Namespace: m.namespace, - } - log.Printf("Waiting for ClusterServiceVersion %q to reach 'Succeeded' phase", nn) - if err = m.client.DoCSVWait(ctx, nn); err != nil { - return fmt.Errorf("error waiting for CSV to install: %w", err) - } - - status = m.status(ctx, bundle.Objects...) - if installed, err := status.HasInstalledResources(); !installed { - return fmt.Errorf("operator %s did not install successfully\n%s", pkgName, status) - } else if err != nil { - return fmt.Errorf("operator %q has resource errors\n%s", pkgName, status) - } - log.Infof("OLM has successfully installed %q", csv.GetName()) - fmt.Print(status) - - return nil -} - -func (m packageManifestsManager) registryUp(ctx context.Context, catsrc *operatorsv1alpha1.CatalogSource, namespace string) error { - rr := internalregistry.RegistryResources{ - Client: m.client, - Pkg: m.pkg, - Bundles: m.bundles, - } - - if exists, err := rr.IsRegistryExist(ctx, namespace); err != nil { - return fmt.Errorf("error checking registry existence: %v", err) - } else if exists { - if isRegistryStale, err := rr.IsRegistryDataStale(ctx, namespace); err == nil { - if !isRegistryStale { - log.Infof("%s registry data is current", m.pkg.PackageName) - return nil - } - log.Infof("A stale %s registry exists, deleting", m.pkg.PackageName) - if err = rr.DeletePackageManifestsRegistry(ctx, namespace); err != nil { - return fmt.Errorf("error deleting registered package: %w", err) - } - } else if !apierrors.IsNotFound(err) { - return fmt.Errorf("error checking registry data: %w", err) - } - } - log.Infof("Creating %s registry", m.pkg.PackageName) - if err := rr.CreatePackageManifestsRegistry(ctx, catsrc, namespace); err != nil { - return fmt.Errorf("error registering package: %w", err) - } - - return nil -} - -func getPackageForVersion(bundles []*apimanifests.Bundle, version string) (*apimanifests.Bundle, error) { - versions := []string{} - for _, bundle := range bundles { - verStr := bundle.CSV.Spec.Version.String() - if verStr == version { - return bundle, nil - } - versions = append(versions, verStr) - } - return nil, fmt.Errorf("no package found for version %s; valid versions: %+q", version, versions) -} - -// updateCatalogSource gets the registry address of the newly created -// ephemeral packagemanifest index pod and updates the catalog source -// with the necessary address and source type fields to enable the -// catalog source to connect to the registry. -func (m *packageManifestsManager) updateCatalogSource(ctx context.Context, pkgName string, catsrc *operatorsv1alpha1.CatalogSource) error { - registryGRPCAddr := internalregistry.GetRegistryServiceAddr(pkgName, m.namespace) - catsrcKey := types.NamespacedName{ - Namespace: catsrc.Namespace, - Name: catsrc.Name, - } - if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { - if err := m.client.KubeClient.Get(ctx, catsrcKey, catsrc); err != nil { - return err - } - catsrc.Spec.Address = registryGRPCAddr - catsrc.Spec.SourceType = operatorsv1alpha1.SourceTypeGrpc - if err := m.client.KubeClient.Update(ctx, catsrc); err != nil { - return err - } - return nil - }); err != nil { - return fmt.Errorf("error setting grpc address on catalog source: %v", err) - } - return nil -} diff --git a/internal/operator/internal/catalog.go b/internal/olm/operator/registry/catalog.go similarity index 85% rename from internal/operator/internal/catalog.go rename to internal/olm/operator/registry/catalog.go index c5428973b7..06b91c341a 100644 --- a/internal/operator/internal/catalog.go +++ b/internal/olm/operator/registry/catalog.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package internal +package registry import ( "context" @@ -23,3 +23,8 @@ import ( type CatalogCreator interface { CreateCatalog(ctx context.Context, name string) (*v1alpha1.CatalogSource, error) } + +// TODO: modify this as necessary. +type InstallPlanApprover interface { + Approve(ctx context.Context, name string) error +} diff --git a/internal/olm/operator/registry/configmap.go b/internal/olm/operator/registry/configmap.go new file mode 100644 index 0000000000..c5f322716a --- /dev/null +++ b/internal/olm/operator/registry/configmap.go @@ -0,0 +1,121 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package registry + +import ( + "context" + "fmt" + + apimanifests "github.com/operator-framework/api/pkg/manifests" + "github.com/operator-framework/api/pkg/operators/v1alpha1" + log "github.com/sirupsen/logrus" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/retry" + + olmclient "github.com/operator-framework/operator-sdk/internal/olm/client" + "github.com/operator-framework/operator-sdk/internal/olm/operator" + "github.com/operator-framework/operator-sdk/internal/olm/operator/registry/configmap" +) + +type ConfigMapCatalogCreator struct { + Package *apimanifests.PackageManifest + Bundles []*apimanifests.Bundle + + cfg *operator.Configuration +} + +func NewConfigMapCatalogCreator(cfg *operator.Configuration) *ConfigMapCatalogCreator { + return &ConfigMapCatalogCreator{ + cfg: cfg, + } +} + +func (c ConfigMapCatalogCreator) CreateCatalog(ctx context.Context, name string) (*v1alpha1.CatalogSource, error) { + cs := newCatalogSource(name, c.cfg.Namespace, + withSDKPublisher(c.Package.PackageName)) + if err := c.cfg.Client.Create(ctx, cs); err != nil { + return nil, fmt.Errorf("error creating catalog source: %w", err) + } + + if err := c.registryUp(ctx, cs); err != nil { + return nil, fmt.Errorf("error creating registry resources: %w", err) + } + + if err := c.updateCatalogSource(ctx, cs); err != nil { + return nil, fmt.Errorf("error updating catalog source: %w", err) + } + + return cs, nil +} + +func (c ConfigMapCatalogCreator) registryUp(ctx context.Context, cs *v1alpha1.CatalogSource) (err error) { + rr := configmap.RegistryResources{ + Pkg: c.Package, + Bundles: c.Bundles, + } + if rr.Client, err = olmclient.NewClientForConfig(c.cfg.RESTConfig); err != nil { + return err + } + + if exists, err := rr.IsRegistryExist(ctx, c.cfg.Namespace); err != nil { + return fmt.Errorf("error checking registry existence: %v", err) + } else if exists { + if isRegistryStale, err := rr.IsRegistryDataStale(ctx, c.cfg.Namespace); err == nil { + if !isRegistryStale { + log.Infof("%s registry data is current", c.Package.PackageName) + return nil + } + log.Infof("A stale %s registry exists, deleting", c.Package.PackageName) + if err = rr.DeletePackageManifestsRegistry(ctx, c.cfg.Namespace); err != nil { + return fmt.Errorf("error deleting registered package: %w", err) + } + } else if !apierrors.IsNotFound(err) { + return fmt.Errorf("error checking registry data: %w", err) + } + } + log.Infof("Creating %s registry", c.Package.PackageName) + if err := rr.CreatePackageManifestsRegistry(ctx, cs, c.cfg.Namespace); err != nil { + return fmt.Errorf("error registering package: %w", err) + } + + return nil +} + +// updateCatalogSource gets the registry address of the newly created +// ephemeral packagemanifest index pod and updates the catalog source +// with the necessary address and source type fields to enable the +// catalog source to connect to the registry. +func (c *ConfigMapCatalogCreator) updateCatalogSource(ctx context.Context, cs *v1alpha1.CatalogSource) error { + registryGRPCAddr := configmap.GetRegistryServiceAddr(c.Package.PackageName, c.cfg.Namespace) + catsrcKey := types.NamespacedName{ + Namespace: c.cfg.Namespace, + Name: cs.GetName(), + } + if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { + if err := c.cfg.Client.Get(ctx, catsrcKey, cs); err != nil { + return err + } + cs.Spec.Address = registryGRPCAddr + cs.Spec.SourceType = v1alpha1.SourceTypeGrpc + if err := c.cfg.Client.Update(ctx, cs); err != nil { + return err + } + return nil + }); err != nil { + return fmt.Errorf("error setting grpc address on catalog source: %v", err) + } + return nil +} diff --git a/internal/olm/operator/internal/configmap.go b/internal/olm/operator/registry/configmap/configmap.go similarity index 99% rename from internal/olm/operator/internal/configmap.go rename to internal/olm/operator/registry/configmap/configmap.go index 714b3cddb3..21590987f4 100644 --- a/internal/olm/operator/internal/configmap.go +++ b/internal/olm/operator/registry/configmap/configmap.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package olm +package configmap import ( "context" diff --git a/internal/olm/operator/internal/deployment.go b/internal/olm/operator/registry/configmap/deployment.go similarity index 99% rename from internal/olm/operator/internal/deployment.go rename to internal/olm/operator/registry/configmap/deployment.go index 3538be6bc2..ebca08875a 100644 --- a/internal/olm/operator/internal/deployment.go +++ b/internal/olm/operator/registry/configmap/deployment.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package olm +package configmap import ( "fmt" diff --git a/internal/olm/operator/internal/registry.go b/internal/olm/operator/registry/configmap/registry.go similarity index 99% rename from internal/olm/operator/internal/registry.go rename to internal/olm/operator/registry/configmap/registry.go index 4bf3c836ad..5f8bba2f53 100644 --- a/internal/olm/operator/internal/registry.go +++ b/internal/olm/operator/registry/configmap/registry.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package olm +package configmap import ( "context" diff --git a/internal/olm/operator/internal/service.go b/internal/olm/operator/registry/configmap/service.go similarity index 99% rename from internal/olm/operator/internal/service.go rename to internal/olm/operator/registry/configmap/service.go index 064815a2f3..66570fb755 100644 --- a/internal/olm/operator/internal/service.go +++ b/internal/olm/operator/registry/configmap/service.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package olm +package configmap import ( corev1 "k8s.io/api/core/v1" diff --git a/internal/operator/internal/registry_pod.go b/internal/olm/operator/registry/index/registry_pod.go similarity index 98% rename from internal/operator/internal/registry_pod.go rename to internal/olm/operator/registry/index/registry_pod.go index e846edf511..f536094dbf 100644 --- a/internal/operator/internal/registry_pod.go +++ b/internal/olm/operator/registry/index/registry_pod.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package internal +package index import ( "bytes" @@ -213,6 +213,10 @@ func (rp *RegistryPod) validate() error { return nil } +func GetRegistryPodHost(ipStr string) string { + return fmt.Sprintf("%s:%d", ipStr, defaultGRPCPort) +} + // getPodName will return a string constructed from the bundle Image name func getPodName(bundleImage string) string { // todo(rashmigottipati): need to come up with human-readable references diff --git a/internal/operator/internal/registry_pod_test.go b/internal/olm/operator/registry/index/registry_pod_test.go similarity index 99% rename from internal/operator/internal/registry_pod_test.go rename to internal/olm/operator/registry/index/registry_pod_test.go index 0e4b5bff2b..3077930ffe 100644 --- a/internal/operator/internal/registry_pod_test.go +++ b/internal/olm/operator/registry/index/registry_pod_test.go @@ -10,8 +10,9 @@ // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and +// limitations under the License. -package internal +package index import ( "context" diff --git a/internal/operator/internal/index_image.go b/internal/olm/operator/registry/index_image.go similarity index 67% rename from internal/operator/internal/index_image.go rename to internal/olm/operator/registry/index_image.go index c4f5fbc795..e880885f8c 100644 --- a/internal/operator/internal/index_image.go +++ b/internal/olm/operator/registry/index_image.go @@ -12,31 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. -package internal +package registry import ( "context" "encoding/json" "fmt" "strings" - "time" "github.com/operator-framework/api/pkg/operators/v1alpha1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/wait" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "github.com/operator-framework/operator-sdk/internal/operator" + "github.com/operator-framework/operator-sdk/internal/olm/operator" + "github.com/operator-framework/operator-sdk/internal/olm/operator/registry/index" registryutil "github.com/operator-framework/operator-sdk/internal/registry" - "github.com/operator-framework/operator-sdk/internal/util/k8sutil" -) - -const ( - defaultSourceType = "grpc" ) type IndexImageCatalogCreator struct { + PackageName string IndexImage string InjectBundles []string InjectBundleMode string @@ -63,7 +56,8 @@ func (c IndexImageCatalogCreator) CreateCatalog(ctx context.Context, name string fmt.Printf("IndexImageCatalogCreator.InjectBundleMode: %q\n", c.InjectBundleMode) // create a basic catalog source type - cs := newCatalogSource(name, c.cfg.Namespace) + cs := newCatalogSource(name, c.cfg.Namespace, + withSDKPublisher(c.PackageName)) // initialize and create the registry pod with provided index image registryPod, err := c.createRegistryPod(ctx, dbPath) @@ -92,32 +86,9 @@ func (c IndexImageCatalogCreator) CreateCatalog(ctx context.Context, name string return nil, fmt.Errorf("error in updating catalog source: %v", err) } - // wait for catalog source to be ready - if err := c.waitForCatalogSource(ctx, cs); err != nil { - return nil, err - } - return cs, nil } -// newCatalogSource creates a new catalog source with name and namespace -func newCatalogSource(name, namespace string) *v1alpha1.CatalogSource { - return &v1alpha1.CatalogSource{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-cs", k8sutil.FormatOperatorNameDNS1123(name)), - Namespace: namespace, - }, - Spec: v1alpha1.CatalogSourceSpec{ - DisplayName: "CatalogSource", - Publisher: "operator-sdk", - }, - TypeMeta: metav1.TypeMeta{ - APIVersion: v1alpha1.SchemeGroupVersion.String(), - Kind: v1alpha1.CatalogSourceKind, - }, - } -} - const defaultDBPath = "/database/index.db" func (c IndexImageCatalogCreator) getDBPath(ctx context.Context) (string, error) { @@ -131,9 +102,9 @@ func (c IndexImageCatalogCreator) getDBPath(ctx context.Context) (string, error) return defaultDBPath, nil } -func (c IndexImageCatalogCreator) createRegistryPod(ctx context.Context, dbPath string) (*RegistryPod, error) { +func (c IndexImageCatalogCreator) createRegistryPod(ctx context.Context, dbPath string) (*index.RegistryPod, error) { // Create registry pod, assigning its owner as the catalog source - registryPod, err := NewRegistryPod(c.cfg.Client, dbPath, c.BundleImage, c.cfg.Namespace) + registryPod, err := index.NewRegistryPod(c.cfg.Client, dbPath, c.BundleImage, c.cfg.Namespace) if err != nil { return nil, fmt.Errorf("error in initializing registry pod") } @@ -147,8 +118,8 @@ func (c IndexImageCatalogCreator) createRegistryPod(ctx context.Context, dbPath func (c IndexImageCatalogCreator) updateCatalogSource(podAddr string, cs *v1alpha1.CatalogSource) error { // Update catalog source with source type as grpc and address to point to the pod IP - cs.Spec.SourceType = defaultSourceType - cs.Spec.Address = fmt.Sprintf("%s:%v", podAddr, defaultGRPCPort) + cs.Spec.SourceType = v1alpha1.SourceTypeGrpc + cs.Spec.Address = index.GetRegistryPodHost(podAddr) // Update catalog source with annotations for index image, // injected bundle, and registry add mode @@ -164,29 +135,3 @@ func (c IndexImageCatalogCreator) updateCatalogSource(podAddr string, cs *v1alph return nil } - -func (c IndexImageCatalogCreator) waitForCatalogSource(ctx context.Context, cs *v1alpha1.CatalogSource) error { - catSrcKey, err := client.ObjectKeyFromObject(cs) - if err != nil { - return fmt.Errorf("error in getting catalog source key: %v", err) - } - - // verify that catalog source connection status is READY - catSrcCheck := wait.ConditionFunc(func() (done bool, err error) { - if err := c.cfg.Client.Get(ctx, catSrcKey, cs); err != nil { - return false, err - } - if cs.Status.GRPCConnectionState != nil { - if cs.Status.GRPCConnectionState.LastObservedState == "READY" { - return true, nil - } - } - return false, nil - }) - - if err := wait.PollImmediateUntil(200*time.Millisecond, catSrcCheck, ctx.Done()); err != nil { - return fmt.Errorf("catalog source connection is not ready: %v", err) - } - - return nil -} diff --git a/internal/olm/operator/registry/olm_resources.go b/internal/olm/operator/registry/olm_resources.go new file mode 100644 index 0000000000..4a1f439058 --- /dev/null +++ b/internal/olm/operator/registry/olm_resources.go @@ -0,0 +1,110 @@ +// Copyright 2019 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package registry + +import ( + "fmt" + + v1 "github.com/operator-framework/api/pkg/operators/v1" + "github.com/operator-framework/api/pkg/operators/v1alpha1" + + "github.com/operator-framework/operator-sdk/internal/olm/operator" + "github.com/operator-framework/operator-sdk/internal/util/k8sutil" +) + +func getSubscriptionName(csvName string) string { + name := k8sutil.FormatOperatorNameDNS1123(csvName) + return fmt.Sprintf("%s-sub", name) +} + +// withCatalogSource returns a function that sets the Subscription argument's +// target CatalogSource's name and namespace. +func withCatalogSource(csName, csNamespace string) func(*v1alpha1.Subscription) { + return func(sub *v1alpha1.Subscription) { + sub.Spec.CatalogSource = csName + sub.Spec.CatalogSourceNamespace = csNamespace + } +} + +// withPackageChannel returns a function that sets the Subscription argument's +// target package, channel, and starting CSV to those in channel. +func withPackageChannel(pkgName, channelName, startingCSV string) func(*v1alpha1.Subscription) { + return func(sub *v1alpha1.Subscription) { + sub.Spec.Package = pkgName + sub.Spec.Channel = channelName + sub.Spec.StartingCSV = startingCSV + } +} + +// newSubscription creates a new Subscription for a CSV with a name derived +// from csvName, the CSV's objectmeta.name, in namespace. opts will be applied +// to the Subscription object. +func newSubscription(csvName, namespace string, opts ...func(*v1alpha1.Subscription)) *v1alpha1.Subscription { + sub := &v1alpha1.Subscription{} + sub.SetGroupVersionKind(v1alpha1.SchemeGroupVersion.WithKind(v1alpha1.SubscriptionKind)) + sub.SetName(getSubscriptionName(csvName)) + sub.SetNamespace(namespace) + sub.Spec = &v1alpha1.SubscriptionSpec{} + for _, opt := range opts { + opt(sub) + } + return sub +} + +func withSDKPublisher(pkgName string) func(*v1alpha1.CatalogSource) { + return func(cs *v1alpha1.CatalogSource) { + cs.Spec.DisplayName = pkgName + cs.Spec.Publisher = "operator-sdk" + } +} + +// newCatalogSource creates a new CatalogSource with a name derived from +// pkgName, the package manifest's packageName, in namespace. opts will +// be applied to the CatalogSource object. +func newCatalogSource(name, namespace string, opts ...func(*v1alpha1.CatalogSource)) *v1alpha1.CatalogSource { + cs := &v1alpha1.CatalogSource{} + cs.SetGroupVersionKind(v1alpha1.SchemeGroupVersion.WithKind(v1alpha1.CatalogSourceKind)) + cs.SetName(name) + cs.SetNamespace(namespace) + for _, opt := range opts { + opt(cs) + } + return cs +} + +// withTargetNamespaces returns a function that sets the OperatorGroup argument's targetNamespaces to namespaces. +// namespaces can be length 0..N; if namespaces length is 0, targetNamespaces is unset, indicating a global scope. +func withTargetNamespaces(namespaces ...string) func(*v1.OperatorGroup) { + return func(og *v1.OperatorGroup) { + if len(namespaces) != 0 && namespaces[0] != "" { + og.Spec.TargetNamespaces = namespaces + } + } +} + +// newSDKOperatorGroup creates a new OperatorGroup with name +// sdkOperatorGroupName in namespace. opts will be applied to the +// OperatorGroup object. Note that the default OperatorGroup has a global +// scope. +func newSDKOperatorGroup(namespace string, opts ...func(*v1.OperatorGroup)) *v1.OperatorGroup { + og := &v1.OperatorGroup{} + og.SetGroupVersionKind(v1.SchemeGroupVersion.WithKind(v1.OperatorGroupKind)) + og.SetName(operator.SDKOperatorGroupName) + og.SetNamespace(namespace) + for _, opt := range opts { + opt(og) + } + return og +} diff --git a/internal/olm/operator/registry/operator_installer.go b/internal/olm/operator/registry/operator_installer.go new file mode 100644 index 0000000000..f77571f81a --- /dev/null +++ b/internal/olm/operator/registry/operator_installer.go @@ -0,0 +1,228 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package registry + +import ( + "context" + "fmt" + "reflect" + "sort" + "time" + + v1 "github.com/operator-framework/api/pkg/operators/v1" + "github.com/operator-framework/api/pkg/operators/v1alpha1" + log "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + "sigs.k8s.io/controller-runtime/pkg/client" + + olmclient "github.com/operator-framework/operator-sdk/internal/olm/client" + "github.com/operator-framework/operator-sdk/internal/olm/operator" +) + +type OperatorInstaller struct { + CatalogSourceName string + PackageName string + StartingCSV string + Channel string + InstallMode operator.InstallMode + CatalogCreator CatalogCreator + + cfg *operator.Configuration +} + +func NewOperatorInstaller(cfg *operator.Configuration) *OperatorInstaller { + return &OperatorInstaller{cfg: cfg} +} + +func (o OperatorInstaller) InstallOperator(ctx context.Context) (*v1alpha1.ClusterServiceVersion, error) { + log.Info("Creating CatalogSource") + cs, err := o.CatalogCreator.CreateCatalog(ctx, o.CatalogSourceName) + if err != nil { + return nil, fmt.Errorf("create catalog: %v", err) + } + + // TODO: OLM doesn't appear to propagate the "READY" connection status to the catalogsource in a timely manner + // even though its catalog-operator reports a connection almost immediately. This condition either needs to + // be propagated more quickly by OLM or we need to find a different resource to probe for readiness. + // + // if err := o.waitForCatalogSource(ctx, cs); err != nil { + // return nil, err + // } + + log.Infof("OperatorInstaller.CatalogSourceName: %q\n", o.CatalogSourceName) + log.Infof("OperatorInstaller.PackageName: %q\n", o.PackageName) + log.Infof("OperatorInstaller.StartingCSV: %q\n", o.StartingCSV) + log.Infof("OperatorInstaller.Channel: %q\n", o.Channel) + log.Infof("OperatorInstaller.InstallMode: %q\n", o.InstallMode) + + // Ensure Operator Group + if err = o.createOperatorGroup(ctx); err != nil { + return nil, err + } + + // Create Subscription + if err = o.createSubscription(ctx, cs); err != nil { + return nil, err + } + + // Approve Install Plan (if necessary) + if approver, ok := o.CatalogCreator.(InstallPlanApprover); ok { + if err = approver.Approve(ctx, o.PackageName); err != nil { + return nil, err + } + } + + // Wait for successfully installed CSV + csv, err := o.getInstalledCSV(ctx) + if err != nil { + return nil, err + } + + log.Infof("OLM has successfully installed %q", o.StartingCSV) + + return csv, nil +} + +//nolint:unused +func (o OperatorInstaller) waitForCatalogSource(ctx context.Context, cs *v1alpha1.CatalogSource) error { + catSrcKey, err := client.ObjectKeyFromObject(cs) + if err != nil { + return fmt.Errorf("error in getting catalog source key: %v", err) + } + + // verify that catalog source connection status is READY + catSrcCheck := wait.ConditionFunc(func() (done bool, err error) { + if err := o.cfg.Client.Get(ctx, catSrcKey, cs); err != nil { + return false, err + } + if cs.Status.GRPCConnectionState != nil { + fmt.Println("grpc connection state:", cs.Status.GRPCConnectionState.LastObservedState) + if cs.Status.GRPCConnectionState.LastObservedState == "READY" { + return true, nil + } + } else { + fmt.Println("grpc connection state: ") + } + return false, nil + }) + + if err := wait.PollImmediateUntil(200*time.Millisecond, catSrcCheck, ctx.Done()); err != nil { + return fmt.Errorf("catalog source connection is not ready: %v", err) + } + + return nil +} + +// createOperatorGroup creates an OperatorGroup using package name if an OperatorGroup does not exist. +// If one exists in the desired namespace and it's target namespaces do not match the desired set, +// createOperatorGroup will return an error. +func (o OperatorInstaller) createOperatorGroup(ctx context.Context) error { + targetNamespaces := make([]string, len(o.InstallMode.TargetNamespaces), cap(o.InstallMode.TargetNamespaces)) + copy(targetNamespaces, o.InstallMode.TargetNamespaces) + // Check OperatorGroup existence, since we cannot create a second OperatorGroup in namespace. + og, ogFound, err := o.getOperatorGroup(ctx) + if err != nil { + return err + } + // TODO: we may need to poll for status updates, since status.namespaces may not be updated immediately. + if ogFound { + // targetNamespaces will always be initialized, but the operator group's namespaces may not be + // (required for comparison). + if og.Status.Namespaces == nil { + og.Status.Namespaces = []string{} + } + // Simple check for OperatorGroup compatibility: if namespaces are not an exact match, + // the user must manage the resource themselves. + sort.Strings(og.Status.Namespaces) + sort.Strings(targetNamespaces) + if !reflect.DeepEqual(og.Status.Namespaces, targetNamespaces) { + msg := fmt.Sprintf("namespaces %+q do not match desired namespaces %+q", og.Status.Namespaces, targetNamespaces) + if og.GetName() == operator.SDKOperatorGroupName { + return fmt.Errorf("existing SDK-managed operator group's %s, "+ + "please clean up existing operators `operator-sdk cleanup` before running package %q", msg, o.PackageName) + } + return fmt.Errorf("existing operator group %q's %s, "+ + "please ensure it has the exact namespace set before running package %q", og.GetName(), msg, o.PackageName) + } + log.Infof("Using existing operator group %q", og.GetName()) + } else { + // New SDK-managed OperatorGroup. + og = newSDKOperatorGroup(o.cfg.Namespace, + withTargetNamespaces(targetNamespaces...)) + log.Info("Creating OperatorGroup") + if err = o.cfg.Client.Create(ctx, og); err != nil { + return fmt.Errorf("error creating OperatorGroup: %w", err) + } + } + return nil +} + +// getOperatorGroup returns true if an OperatorGroup in the desired namespace was found. +// If more than one operator group exists in namespace, this function will return an error +// since CSVs in namespace will have an error status in that case. +func (o OperatorInstaller) getOperatorGroup(ctx context.Context) (*v1.OperatorGroup, bool, error) { + ogList := &v1.OperatorGroupList{} + if err := o.cfg.Client.List(ctx, ogList, client.InNamespace(o.cfg.Namespace)); err != nil { + return nil, false, err + } + if len(ogList.Items) == 0 { + return nil, false, nil + } + if len(ogList.Items) != 1 { + var names []string + for _, og := range ogList.Items { + names = append(names, og.GetName()) + } + return nil, true, fmt.Errorf("more than one operator group in namespace %s: %+q", o.cfg.Namespace, names) + } + return &ogList.Items[0], true, nil +} + +func (o OperatorInstaller) createSubscription(ctx context.Context, cs *v1alpha1.CatalogSource) error { + sub := newSubscription(o.StartingCSV, o.cfg.Namespace, + withPackageChannel(o.PackageName, o.Channel, o.StartingCSV), + withCatalogSource(cs.GetName(), o.cfg.Namespace)) + log.Info("Creating Subscription") + if err := o.cfg.Client.Create(ctx, sub); err != nil { + return fmt.Errorf("error creating OperatorGroup: %w", err) + } + return nil +} + +func (o OperatorInstaller) getInstalledCSV(ctx context.Context) (*v1alpha1.ClusterServiceVersion, error) { + c, err := olmclient.NewClientForConfig(o.cfg.RESTConfig) + if err != nil { + return nil, err + } + + // BUG(estroz): if namespace is not contained in targetNamespaces, + // DoCSVWait will fail because the CSV is not deployed in namespace. + nn := types.NamespacedName{ + Name: o.StartingCSV, + Namespace: o.cfg.Namespace, + } + log.Printf("Waiting for ClusterServiceVersion %q to reach 'Succeeded' phase", nn) + if err = c.DoCSVWait(ctx, nn); err != nil { + return nil, fmt.Errorf("error waiting for CSV to install: %w", err) + } + + // TODO: check status of all resources in the desired bundle/package. + csv := &v1alpha1.ClusterServiceVersion{} + if err = o.cfg.Client.Get(ctx, nn, csv); err != nil { + return nil, fmt.Errorf("error getting installed CSV: %w", err) + } + return csv, nil +} diff --git a/internal/olm/operator/tenancy_test.go b/internal/olm/operator/registry/operatorgroup_test.go similarity index 55% rename from internal/olm/operator/tenancy_test.go rename to internal/olm/operator/registry/operatorgroup_test.go index bd32c8add4..1a7aa8403d 100644 --- a/internal/olm/operator/tenancy_test.go +++ b/internal/olm/operator/registry/operatorgroup_test.go @@ -12,25 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. -package olm +package registry import ( "context" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" + v1 "github.com/operator-framework/api/pkg/operators/v1" + "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - olmclient "github.com/operator-framework/operator-sdk/internal/olm/client" - "github.com/operator-framework/operator-sdk/internal/operator" + "github.com/operator-framework/operator-sdk/internal/olm/operator" ) var _ = Describe("Tenancy", func() { Describe("createOperatorGroup", func() { var ( - m *packageManifestsManager + o *OperatorInstaller ctx context.Context err error @@ -40,10 +40,14 @@ var _ = Describe("Tenancy", func() { ) BeforeEach(func() { - m = &packageManifestsManager{ - operatorManager: &operatorManager{ - namespace: namespace, - client: &olmclient.Client{KubeClient: fake.NewFakeClient()}, + sch := runtime.NewScheme() + Expect(v1.AddToScheme(sch)).To(Succeed()) + o = &OperatorInstaller{ + PackageName: packageName, + cfg: &operator.Configuration{ + Scheme: sch, + Namespace: namespace, + Client: fake.NewFakeClientWithScheme(sch), }, } ctx = context.TODO() @@ -51,9 +55,8 @@ var _ = Describe("Tenancy", func() { Context("with no existing OperatorGroup", func() { It("creates one successfully", func() { - err = m.createOperatorGroup(ctx, packageName) - Expect(err).To(BeNil()) - og, ogExists, err := getOperatorGroup(ctx, m.client, m.namespace) + Expect(o.createOperatorGroup(ctx)).To(Succeed()) + og, ogExists, err := o.getOperatorGroup(ctx) Expect(err).To(BeNil()) Expect(ogExists).To(BeTrue()) Expect(og.GetName()).To(Equal(operator.SDKOperatorGroupName)) @@ -61,30 +64,37 @@ var _ = Describe("Tenancy", func() { }) Context("with an existing, valid OperatorGroup", func() { - It("returns no error and the existing SDK OperatorGroup is unchanged", func() { - existingOG := createOperatorGroupHelper(ctx, m.client.KubeClient, operator.SDKOperatorGroupName, namespace) - err = m.createOperatorGroup(ctx, packageName) + It("returns no error and the existing SDK OperatorGroup with no target namespaces is unchanged", func() { + existingOG := createOperatorGroupHelper(ctx, o.cfg.Client, operator.SDKOperatorGroupName, namespace) + Expect(o.createOperatorGroup(ctx)).To(Succeed()) + og, ogExists, err := o.getOperatorGroup(ctx) Expect(err).To(BeNil()) - og, ogExists, err := getOperatorGroup(ctx, m.client, m.namespace) + Expect(ogExists).To(BeTrue()) + Expect(og.GetName()).To(Equal(existingOG.GetName())) + }) + It("returns no error and the existing SDK OperatorGroup with the same set of target namespaces is unchanged", func() { + targetNamespaces := []string{"foo", "bar"} + o.InstallMode.TargetNamespaces = targetNamespaces + existingOG := createOperatorGroupHelper(ctx, o.cfg.Client, operator.SDKOperatorGroupName, namespace, targetNamespaces...) + Expect(o.createOperatorGroup(ctx)).To(Succeed()) + og, ogExists, err := o.getOperatorGroup(ctx) Expect(err).To(BeNil()) Expect(ogExists).To(BeTrue()) Expect(og.GetName()).To(Equal(existingOG.GetName())) }) It("returns no error and the existing non-SDK OperatorGroup is unchanged", func() { - existingOG := createOperatorGroupHelper(ctx, m.client.KubeClient, nonSDKOperatorGroupName, namespace) - err = m.createOperatorGroup(ctx, packageName) - Expect(err).To(BeNil()) - og, ogExists, err := getOperatorGroup(ctx, m.client, m.namespace) + existingOG := createOperatorGroupHelper(ctx, o.cfg.Client, nonSDKOperatorGroupName, namespace) + Expect(o.createOperatorGroup(ctx)).To(Succeed()) + og, ogExists, err := o.getOperatorGroup(ctx) Expect(err).To(BeNil()) Expect(ogExists).To(BeTrue()) Expect(og.GetName()).To(Equal(existingOG.GetName())) }) It("returns no error and the existing OperatorGroup in another namespace is unchanged", func() { otherNS := "my-ns" - existingOG := createOperatorGroupHelper(ctx, m.client.KubeClient, operator.SDKOperatorGroupName, otherNS) - err = m.createOperatorGroup(ctx, packageName) - Expect(err).To(BeNil()) - og, ogExists, err := getOperatorGroup(ctx, m.client, m.namespace) + existingOG := createOperatorGroupHelper(ctx, o.cfg.Client, operator.SDKOperatorGroupName, otherNS) + Expect(o.createOperatorGroup(ctx)).To(Succeed()) + og, ogExists, err := o.getOperatorGroup(ctx) Expect(err).To(BeNil()) Expect(ogExists).To(BeTrue()) Expect(og.GetName()).To(Equal(existingOG.GetName())) @@ -94,13 +104,13 @@ var _ = Describe("Tenancy", func() { Context("with an existing, invalid OperatorGroup", func() { It("returns an error for an SDK OperatorGroup", func() { - _ = createOperatorGroupHelper(ctx, m.client.KubeClient, operator.SDKOperatorGroupName, namespace, "foo") - err = m.createOperatorGroup(ctx, packageName) + _ = createOperatorGroupHelper(ctx, o.cfg.Client, operator.SDKOperatorGroupName, namespace, "foo") + err = o.createOperatorGroup(ctx) Expect(err.Error()).To(ContainSubstring(`existing SDK-managed operator group's namespaces ["foo"] do not match desired namespaces []`)) }) It("returns an error for a non-SDK OperatorGroup", func() { - _ = createOperatorGroupHelper(ctx, m.client.KubeClient, nonSDKOperatorGroupName, namespace, "foo") - err = m.createOperatorGroup(ctx, packageName) + _ = createOperatorGroupHelper(ctx, o.cfg.Client, nonSDKOperatorGroupName, namespace, "foo") + err = o.createOperatorGroup(ctx) Expect(err.Error()).To(ContainSubstring(`existing operator group "my-og"'s namespaces ["foo"] do not match desired namespaces []`)) }) }) @@ -108,8 +118,8 @@ var _ = Describe("Tenancy", func() { }) -func createOperatorGroupHelper(ctx context.Context, c client.Client, name, namespace string, targetNamespaces ...string) (og operatorsv1.OperatorGroup) { - og.SetGroupVersionKind(operatorsv1.SchemeGroupVersion.WithKind("OperatorGroup")) +func createOperatorGroupHelper(ctx context.Context, c client.Client, name, namespace string, targetNamespaces ...string) (og v1.OperatorGroup) { + og.SetGroupVersionKind(v1.SchemeGroupVersion.WithKind("OperatorGroup")) og.SetName(name) og.SetNamespace(namespace) og.Status.Namespaces = targetNamespaces diff --git a/internal/olm/operator/operator_suite_test.go b/internal/olm/operator/registry/registry_suite_test.go similarity index 89% rename from internal/olm/operator/operator_suite_test.go rename to internal/olm/operator/registry/registry_suite_test.go index 7315c06185..45f64dc504 100644 --- a/internal/olm/operator/operator_suite_test.go +++ b/internal/olm/operator/registry/registry_suite_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package olm +package registry import ( "testing" @@ -21,7 +21,7 @@ import ( . "github.com/onsi/gomega" ) -func TestOperator(t *testing.T) { +func TestRegistry(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "Operator Suite") + RunSpecs(t, "Registry Suite") } diff --git a/internal/olm/operator/tenancy.go b/internal/olm/operator/tenancy.go deleted file mode 100644 index c184bcba43..0000000000 --- a/internal/olm/operator/tenancy.go +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright 2019 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package olm - -import ( - "context" - "fmt" - "reflect" - "sort" - "strings" - - operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" - operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" - log "github.com/sirupsen/logrus" - "sigs.k8s.io/controller-runtime/pkg/client" - - olmclient "github.com/operator-framework/operator-sdk/internal/olm/client" - "github.com/operator-framework/operator-sdk/internal/operator" -) - -// Mapping of installMode string values to types, for validation. -var installModeStrings = map[string]operatorsv1alpha1.InstallModeType{ - string(operatorsv1alpha1.InstallModeTypeOwnNamespace): operatorsv1alpha1.InstallModeTypeOwnNamespace, - string(operatorsv1alpha1.InstallModeTypeSingleNamespace): operatorsv1alpha1.InstallModeTypeSingleNamespace, - string(operatorsv1alpha1.InstallModeTypeMultiNamespace): operatorsv1alpha1.InstallModeTypeMultiNamespace, - string(operatorsv1alpha1.InstallModeTypeAllNamespaces): operatorsv1alpha1.InstallModeTypeAllNamespaces, -} - -// installModeCompatible ensures installMode is compatible with the namespaces -// and CSV's installModes being used. -func installModeCompatible(csv *operatorsv1alpha1.ClusterServiceVersion, installMode operatorsv1alpha1.InstallModeType, - operatorNamespace string, targetNamespaces []string) error { - - err := validateInstallModeForNamespaces(installMode, targetNamespaces) - if err != nil { - return err - } - if installMode == operatorsv1alpha1.InstallModeTypeOwnNamespace { - if ns := targetNamespaces[0]; ns != operatorNamespace { - return fmt.Errorf("installMode %s namespace %q must match namespace %q", - installMode, ns, operatorNamespace) - } - } - for _, mode := range csv.Spec.InstallModes { - if mode.Type == installMode && !mode.Supported { - return fmt.Errorf("installMode %s not supported in CSV %q", installMode, csv.GetName()) - } - } - return nil -} - -// parseInstallModeKV parses an installMode string of the format -// installModeFormat. -func parseInstallModeKV(raw, operatorNs string) (operatorsv1alpha1.InstallModeType, []string, error) { - modeSplit := strings.Split(raw, "=") - if allNs := string(operatorsv1alpha1.InstallModeTypeAllNamespaces); raw == allNs || modeSplit[0] == allNs { - return operatorsv1alpha1.InstallModeTypeAllNamespaces, nil, nil - } - if ownNs := string(operatorsv1alpha1.InstallModeTypeOwnNamespace); raw == ownNs || modeSplit[0] == ownNs { - return operatorsv1alpha1.InstallModeTypeOwnNamespace, []string{operatorNs}, nil - } - if len(modeSplit) != 2 { - return "", nil, fmt.Errorf("installMode string %q is malformatted, must be: %s", raw, installModeFormat) - } - modeStr, namespaceList := modeSplit[0], modeSplit[1] - mode, ok := installModeStrings[modeStr] - if !ok { - return "", nil, fmt.Errorf("installMode type string %q is not a valid installMode type", modeStr) - } - namespaces := []string{} - namespaces = append(namespaces, strings.Split(strings.Trim(namespaceList, ","), ",")...) - return mode, namespaces, nil -} - -// validateInstallModeForNamespaces ensures namespaces are valid given mode. -func validateInstallModeForNamespaces(mode operatorsv1alpha1.InstallModeType, namespaces []string) error { - switch mode { - case operatorsv1alpha1.InstallModeTypeOwnNamespace, operatorsv1alpha1.InstallModeTypeSingleNamespace: - if len(namespaces) != 1 || namespaces[0] == "" { - return fmt.Errorf("installMode %s must be passed with exactly one non-empty namespace, have: %+q", - mode, namespaces) - } - case operatorsv1alpha1.InstallModeTypeMultiNamespace: - if len(namespaces) < 2 { - return fmt.Errorf("installMode %s must be passed with more than one non-empty namespaces, have: %+q", - mode, namespaces) - } - case operatorsv1alpha1.InstallModeTypeAllNamespaces: - if len(namespaces) != 0 && namespaces[0] != "" { - return fmt.Errorf("installMode %s must be passed with no namespaces, have: %+q", - mode, namespaces) - } - default: - return fmt.Errorf("installMode %q is not a valid installMode type", mode) - } - return nil -} - -// createOperatorGroup creates an OperatorGroup using pkgName if an OperatorGroup does not exist. -// If one exists in the desired namespace and it's target namespaces do not match the desired set, -// createOperatorGroup will return an error. -func (m *packageManifestsManager) createOperatorGroup(ctx context.Context, pkgName string) error { - // Check OperatorGroup existence, since we cannot create a second OperatorGroup in namespace. - og, ogFound, err := getOperatorGroup(ctx, m.client, m.namespace) - if err != nil { - return err - } - if ogFound { - // Simple check for OperatorGroup compatibility: if namespaces are not an exact match, - // the user must manage the resource themselves. - sort.Strings(og.Status.Namespaces) - sort.Strings(m.targetNamespaces) - if !reflect.DeepEqual(og.Status.Namespaces, m.targetNamespaces) { - msg := fmt.Sprintf("namespaces %+q do not match desired namespaces %+q", og.Status.Namespaces, m.targetNamespaces) - if og.GetName() == operator.SDKOperatorGroupName { - return fmt.Errorf("existing SDK-managed operator group's %s, "+ - "please clean up existing operators `operator-sdk cleanup` before running package %q", msg, pkgName) - } - return fmt.Errorf("existing operator group %q's %s, "+ - "please ensure it has the exact namespace set before running package %q", og.GetName(), msg, pkgName) - } - log.Infof(" Using existing operator group %q", og.GetName()) - } else { - // New SDK-managed OperatorGroup. - og = newSDKOperatorGroup(m.namespace, withTargetNamespaces(m.targetNamespaces...)) - if err = m.client.DoCreate(ctx, og); err != nil { - return fmt.Errorf("error creating operator resources: %w", err) - } - } - return nil -} - -// getOperatorGroup returns true if an operator group in namespace was found and that operator group. -// If more than one operator group exists in namespace, this function will return an error -// since CSVs in namespace will have an error status in that case. -func getOperatorGroup(ctx context.Context, c *olmclient.Client, namespace string) (*operatorsv1.OperatorGroup, bool, error) { - ogList := &operatorsv1.OperatorGroupList{} - if err := c.KubeClient.List(ctx, ogList, client.InNamespace(namespace)); err != nil { - return nil, false, err - } - if len(ogList.Items) == 0 { - return nil, false, nil - } - if len(ogList.Items) != 1 { - var names []string - for _, og := range ogList.Items { - names = append(names, og.GetName()) - } - return nil, true, fmt.Errorf("more than one operator group in namespace %s: %+q", namespace, names) - } - return &ogList.Items[0], true, nil -} diff --git a/internal/operator/uninstall.go b/internal/olm/operator/uninstall.go similarity index 100% rename from internal/operator/uninstall.go rename to internal/olm/operator/uninstall.go diff --git a/internal/operator/internal/operator_installer.go b/internal/operator/internal/operator_installer.go deleted file mode 100644 index 7126615933..0000000000 --- a/internal/operator/internal/operator_installer.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2020 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package internal - -import ( - "context" - "fmt" - - "github.com/operator-framework/api/pkg/operators/v1alpha1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/operator-framework/operator-sdk/internal/operator" -) - -type OperatorInstaller struct { - CatalogSourceName string - PackageName string - StartingCSV string - Channel string - InstallMode InstallMode - CatalogCreator CatalogCreator - - cfg *operator.Configuration -} - -func NewOperatorInstaller(cfg *operator.Configuration) *OperatorInstaller { - return &OperatorInstaller{cfg: cfg} -} - -func (o OperatorInstaller) InstallOperator(ctx context.Context) (*v1alpha1.ClusterServiceVersion, error) { - cs, err := o.CatalogCreator.CreateCatalog(ctx, o.CatalogSourceName) - if err != nil { - return nil, fmt.Errorf("create catalog: %v", err) - } - _ = cs - - fmt.Printf("OperatorInstaller.CatalogSourceName: %q\n", o.CatalogSourceName) - fmt.Printf("OperatorInstaller.PackageName: %q\n", o.PackageName) - fmt.Printf("OperatorInstaller.StartingCSV: %q\n", o.StartingCSV) - fmt.Printf("OperatorInstaller.Channel: %q\n", o.Channel) - fmt.Printf("OperatorInstaller.InstallMode: %q\n", o.InstallMode) - todo := &v1alpha1.ClusterServiceVersion{ - ObjectMeta: metav1.ObjectMeta{Name: o.StartingCSV}, - } - - // Ensure Operator Group - // Create Subscription - // Approve Install Plan (if necessary) - // Wait for successfully installed CSV - - return todo, nil -} diff --git a/test/integration/operator_olm_test.go b/test/integration/operator_olm_test.go index 4d9bdeb4c6..2c6085db34 100644 --- a/test/integration/operator_olm_test.go +++ b/test/integration/operator_olm_test.go @@ -31,8 +31,8 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "sigs.k8s.io/controller-runtime/pkg/client" - operator "github.com/operator-framework/operator-sdk/internal/olm/operator" - operator2 "github.com/operator-framework/operator-sdk/internal/operator" + "github.com/operator-framework/operator-sdk/internal/olm/operator" + "github.com/operator-framework/operator-sdk/internal/olm/operator/packagemanifests" "github.com/operator-framework/operator-sdk/internal/util/k8sutil" ) @@ -100,24 +100,26 @@ func PackageManifestsOwnNamespace(t *testing.T) { os.RemoveAll(tmp) t.Fatal(err) } - opcmd := operator.PackageManifestsCmd{ - OperatorCmd: operator.OperatorCmd{ - KubeconfigPath: kubeconfigPath, - Timeout: defaultTimeout, - InstallMode: string(operatorsv1alpha1.InstallModeTypeOwnNamespace), - }, - ManifestsDir: manifestsDir, - Version: defaultOperatorVersion, + + cfg := &operator.Configuration{KubeconfigPath: kubeconfigPath} + assert.NoError(t, cfg.Load()) + i := packagemanifests.NewInstall(cfg) + i.PackageManifestsDirectory = manifestsDir + i.Version = defaultOperatorVersion + i.InstallMode = operator.InstallMode{ + InstallModeType: operatorsv1alpha1.InstallModeTypeOwnNamespace, + TargetNamespaces: []string{"default"}, } + // Cleanup. defer func() { - if err := doUninstall(t, kubeconfigPath, opcmd.Timeout); err != nil { + if err := doUninstall(t, kubeconfigPath); err != nil { t.Fatal(err) } }() // Deploy operator. - assert.NoError(t, opcmd.Run()) + assert.NoError(t, doInstall(i)) } func PackageManifestsBasic(t *testing.T) { @@ -161,27 +163,24 @@ func PackageManifestsBasic(t *testing.T) { os.RemoveAll(tmp) t.Fatal(err) } - opcmd := operator.PackageManifestsCmd{ - OperatorCmd: operator.OperatorCmd{ - KubeconfigPath: kubeconfigPath, - Timeout: defaultTimeout, - }, - ManifestsDir: manifestsDir, - Version: defaultOperatorVersion, - } + cfg := &operator.Configuration{KubeconfigPath: kubeconfigPath} + assert.NoError(t, cfg.Load()) + i := packagemanifests.NewInstall(cfg) + i.PackageManifestsDirectory = manifestsDir + i.Version = defaultOperatorVersion - // "Remove operator before deploy" - assert.Error(t, doUninstall(t, kubeconfigPath, opcmd.Timeout)) + // Remove operator before deploy + assert.Error(t, doUninstall(t, kubeconfigPath)) - // "Deploy operator" - assert.NoError(t, opcmd.Run()) - // "Fail to deploy operator after deploy" - assert.Error(t, opcmd.Run()) + // Deploy operator + assert.NoError(t, doInstall(i)) + // Fail to deploy operator after deploy + assert.Error(t, doInstall(i)) - // "Remove operator after deploy" - assert.NoError(t, doUninstall(t, kubeconfigPath, opcmd.Timeout)) - // "Remove operator after removal" - assert.Error(t, doUninstall(t, kubeconfigPath, opcmd.Timeout)) + // Remove operator after deploy + assert.NoError(t, doUninstall(t, kubeconfigPath)) + // Remove operator after removal + assert.Error(t, doUninstall(t, kubeconfigPath)) } func PackageManifestsMultiplePackages(t *testing.T) { @@ -256,31 +255,28 @@ func PackageManifestsMultiplePackages(t *testing.T) { os.RemoveAll(tmp) t.Fatal(err) } - opcmd := operator.PackageManifestsCmd{ - OperatorCmd: operator.OperatorCmd{ - KubeconfigPath: kubeconfigPath, - Timeout: defaultTimeout, - }, - ManifestsDir: manifestsDir, - Version: operatorVersion2, - } + cfg := &operator.Configuration{KubeconfigPath: kubeconfigPath} + assert.NoError(t, cfg.Load()) + i := packagemanifests.NewInstall(cfg) + i.PackageManifestsDirectory = manifestsDir + i.Version = operatorVersion2 - // "Deploy operator" - assert.NoError(t, opcmd.Run()) - // "Remove operator after deploy" - assert.NoError(t, doUninstall(t, kubeconfigPath, opcmd.Timeout)) + // Deploy operator + assert.NoError(t, doInstall(i)) + // Remove operator after deploy + assert.NoError(t, doUninstall(t, kubeconfigPath)) } -func doUninstall(t *testing.T, kubeconfigPath string, timeout time.Duration) error { - cfg := &operator2.Configuration{KubeconfigPath: kubeconfigPath} +func doUninstall(t *testing.T, kubeconfigPath string) error { + cfg := &operator.Configuration{KubeconfigPath: kubeconfigPath} assert.NoError(t, cfg.Load()) - uninstall := operator2.NewUninstall(cfg) + uninstall := operator.NewUninstall(cfg) uninstall.DeleteAll = true - uninstall.DeleteOperatorGroupNames = []string{operator2.SDKOperatorGroupName} + uninstall.DeleteOperatorGroupNames = []string{operator.SDKOperatorGroupName} uninstall.Package = defaultOperatorName uninstall.Logf = logrus.Infof - ctx, cancel := context.WithTimeout(context.Background(), timeout) + ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) defer cancel() if err := uninstall.Run(ctx); err != nil { return err @@ -288,7 +284,19 @@ func doUninstall(t *testing.T, kubeconfigPath string, timeout time.Duration) err return waitForPackageManifestConfigMapDeletion(ctx, cfg, defaultOperatorName) } -func waitForPackageManifestConfigMapDeletion(ctx context.Context, cfg *operator2.Configuration, packageName string) error { +type installer interface { + Run(context.Context) (*operatorsv1alpha1.ClusterServiceVersion, error) +} + +func doInstall(i installer) error { + ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) + defer cancel() + + _, err := i.Run(ctx) + return err +} + +func waitForPackageManifestConfigMapDeletion(ctx context.Context, cfg *operator.Configuration, packageName string) error { cfgmaps := corev1.ConfigMapList{} opts := []client.ListOption{ client.InNamespace(cfg.Namespace), diff --git a/website/content/en/docs/cli/operator-sdk_run_packagemanifests.md b/website/content/en/docs/cli/operator-sdk_run_packagemanifests.md index fcad1d6432..c21257dcda 100644 --- a/website/content/en/docs/cli/operator-sdk_run_packagemanifests.md +++ b/website/content/en/docs/cli/operator-sdk_run_packagemanifests.md @@ -8,21 +8,22 @@ Deploy an Operator in the package manifests format with OLM ### Synopsis 'run packagemanifests' deploys an Operator's package manifests with OLM. The command's argument -must be set to a valid package manifests root directory, ex. '<project-root>/packagemanifests'. +will default to './packagemanifests' if unset; if set, the argument must be a package manifests root directory, +ex. '<project-root>/packagemanifests'. ``` -operator-sdk run packagemanifests [flags] +operator-sdk run packagemanifests [packagemanifests-root-dir] [flags] ``` ### Options ``` - -h, --help help for packagemanifests - --install-mode string InstallMode to create OperatorGroup with. Format: InstallModeType[=ns1,ns2[, ...]] - --kubeconfig string The file path to kubernetes configuration file. Defaults to location specified by $KUBECONFIG, or to default file rules if not set - --namespace string The namespace where operator resources are created. It must already exist in the cluster - --timeout duration Time to wait for the command to complete before failing (default 2m0s) - --version string Packaged version of the operator to deploy + --install-mode InstallModeValue install mode + --version string Packaged version of the operator to deploy + --timeout duration install timeout (default 2m0s) + --kubeconfig string Path to the kubeconfig file to use for CLI requests. + -n, --namespace string If present, namespace scope for this CLI request + -h, --help help for packagemanifests ``` ### Options inherited from parent commands