This is an automated email from the ASF dual-hosted git repository. astefanutti pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel-k.git
commit 6b8e113c4ce5a4cd7d6d91551033b12b0f375f6e Author: Nicola Ferraro <[email protected]> AuthorDate: Sat Feb 8 00:47:35 2020 +0100 Fix #1223: add OLM install options --- go.mod | 1 + go.sum | 3 + ...v1alpha1.go => addtoscheme_knative_eventing.go} | 0 ..._v1alpha1.go => addtoscheme_knative_serving.go} | 0 ...tive_serving_v1alpha1.go => addtoscheme_olm.go} | 4 +- pkg/client/fastmapper.go | 1 + pkg/cmd/install.go | 73 ++++++-- pkg/util/olm/available.go | 59 ++++++ pkg/util/olm/operator.go | 201 +++++++++++++++++++++ 9 files changed, 328 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 9d88d7a..52f4532 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/mitchellh/mapstructure v1.1.2 github.com/onsi/gomega v1.7.0 github.com/openshift/api v3.9.1-0.20190927182313-d4a64ec2cbd8+incompatible + github.com/operator-framework/operator-lifecycle-manager v0.0.0-20191115003340-16619cd27fa5 github.com/operator-framework/operator-sdk v0.15.0 github.com/pkg/errors v0.8.1 github.com/radovskyb/watcher v1.0.6 diff --git a/go.sum b/go.sum index 5a419be..a3675c8 100644 --- a/go.sum +++ b/go.sum @@ -103,6 +103,7 @@ github.com/bifurcation/mint v0.0.0-20180715133206-93c51c6ce115/go.mod h1:zVt7zX3 github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= @@ -588,7 +589,9 @@ github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFSt github.com/openzipkin/zipkin-go v0.1.6 h1:yXiysv1CSK7Q5yjGy1710zZGnsbMUIjluWBxtLXHPBo= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/operator-framework/api v0.0.0-20200120235816-80fd2f1a09c9/go.mod h1:S5IdlJvmKkF84K2tBvsrqJbI2FVy03P88R75snpRxJo= +github.com/operator-framework/operator-lifecycle-manager v0.0.0-20191115003340-16619cd27fa5 h1:rjaihxY50c5C+kbQIK4s36R8zxByATYrgRbua4eiG6o= github.com/operator-framework/operator-lifecycle-manager v0.0.0-20191115003340-16619cd27fa5/go.mod h1:zL34MNy92LPutBH5gQK+gGhtgTUlZZX03I2G12vWHF4= +github.com/operator-framework/operator-lifecycle-manager v3.11.0+incompatible h1:Po8C8RVLRWq7pNQ5pKonM9CXpC/osoBWbmsuf+HJnSI= github.com/operator-framework/operator-registry v1.5.1/go.mod h1:agrQlkWOo1q8U1SAaLSS2WQ+Z9vswNT2M2HFib9iuLY= github.com/operator-framework/operator-registry v1.5.3/go.mod h1:agrQlkWOo1q8U1SAaLSS2WQ+Z9vswNT2M2HFib9iuLY= github.com/operator-framework/operator-registry v1.5.7-0.20200121213444-d8e2ec52c19a/go.mod h1:ekexcV4O8YMxdQuPb+Xco7MHfVmRIq7Jvj5e6NU7dHI= diff --git a/pkg/apis/addtoscheme_knative_eventing_v1alpha1.go b/pkg/apis/addtoscheme_knative_eventing.go similarity index 100% rename from pkg/apis/addtoscheme_knative_eventing_v1alpha1.go rename to pkg/apis/addtoscheme_knative_eventing.go diff --git a/pkg/apis/addtoscheme_knative_serving_v1alpha1.go b/pkg/apis/addtoscheme_knative_serving.go similarity index 100% copy from pkg/apis/addtoscheme_knative_serving_v1alpha1.go copy to pkg/apis/addtoscheme_knative_serving.go diff --git a/pkg/apis/addtoscheme_knative_serving_v1alpha1.go b/pkg/apis/addtoscheme_olm.go similarity index 84% rename from pkg/apis/addtoscheme_knative_serving_v1alpha1.go rename to pkg/apis/addtoscheme_olm.go index f270608..a69aa84 100644 --- a/pkg/apis/addtoscheme_knative_serving_v1alpha1.go +++ b/pkg/apis/addtoscheme_olm.go @@ -18,10 +18,10 @@ limitations under the License. package apis import ( - serving "knative.dev/serving/pkg/apis/serving/v1" + olmv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" ) func init() { // Register the types with the Scheme so the components can map objects to GroupVersionKinds and back - AddToSchemes = append(AddToSchemes, serving.AddToScheme) + AddToSchemes = append(AddToSchemes, olmv1alpha1.AddToScheme) } diff --git a/pkg/client/fastmapper.go b/pkg/client/fastmapper.go index 346603d..82b33b8 100644 --- a/pkg/client/fastmapper.go +++ b/pkg/client/fastmapper.go @@ -38,6 +38,7 @@ var FastMapperAllowedAPIGroups = map[string]bool{ "camel.apache.org": true, "rbac.authorization.k8s.io": true, "console.openshift.io": true, // OpenShift console resources + "operators.coreos.com": true, // Operator SDK OLM } // newFastDiscoveryRESTMapper comes from https://github.com/kubernetes-sigs/controller-runtime/pull/592. diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index f9a3481..8e9c669 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -23,6 +23,7 @@ import ( "strings" "time" + "github.com/apache/camel-k/pkg/util/olm" "github.com/apache/camel-k/pkg/util/registry" "go.uber.org/multierr" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -93,6 +94,16 @@ func newCmdInstall(rootCmdOptions *RootCmdOptions) (*cobra.Command, *installCmdO cmd.Flags().String("http-proxy-secret", "", "Configure the source of the secret holding HTTP proxy server details "+ "(HTTP_PROXY|HTTPS_PROXY|NO_PROXY)") + // olm + cmd.Flags().Bool("olm", true, "Try to install everything via OLM (Operator Lifecycle Manager) if available") + cmd.Flags().String("olm-operator-name", olm.DefaultOperatorName, "Name of the Camel K operator in the OLM source or marketplace") + cmd.Flags().String("olm-package", olm.DefaultPackage, "Name of the Camel K package in the OLM source or marketplace") + cmd.Flags().String("olm-channel", olm.DefaultChannel, "Name of the Camel K channel in the OLM source or marketplace") + cmd.Flags().String("olm-source", olm.DefaultSource, "Name of the OLM source providing the Camel K package (defaults to the standard Operator Hub source)") + cmd.Flags().String("olm-source-namespace", olm.DefaultSourceNamespace, "Namespace where the OLM source is available") + cmd.Flags().String("olm-starting-csv", olm.DefaultStartingCSV, "Allow to install a specific version from the operator source instead of latest available from the channel") + cmd.Flags().String("olm-global-namespace", olm.DefaultGlobalNamespace, "A namespace containing an OperatorGroup that defines global scope for the operator (used in combination with the --global flag)") + // maven settings cmd.Flags().String("local-repository", "", "Location of the local maven repository") cmd.Flags().String("maven-settings", "", "Configure the source of the maven settings (configmap|secret:name[/key])") @@ -123,6 +134,7 @@ type installCmdOptions struct { Global bool `mapstructure:"global"` KanikoBuildCache bool `mapstructure:"kaniko-build-cache"` Save bool `mapstructure:"save"` + Olm bool `mapstructure:"olm"` ClusterType string `mapstructure:"cluster-type"` OutputFormat string `mapstructure:"output"` RuntimeVersion string `mapstructure:"runtime-version"` @@ -140,6 +152,7 @@ type installCmdOptions struct { registry v1.IntegrationPlatformRegistrySpec registryAuth registry.Auth + olmOptions olm.Options } // nolint: gocyclo @@ -149,13 +162,36 @@ func (o *installCmdOptions) install(cobraCmd *cobra.Command, _ []string) error { collection = kubernetes.NewCollection() } - if !o.SkipClusterSetup { - // Let's use a client provider during cluster installation, to eliminate the problem of CRD object caching - clientProvider := client.Provider{Get: o.NewCmdClient} + // Let's use a client provider during cluster installation, to eliminate the problem of CRD object caching + clientProvider := client.Provider{Get: o.NewCmdClient} + + installViaOLM := false + if o.Olm { + var err error + var olmClient client.Client + if olmClient, err = clientProvider.Get(); err != nil { + return err + } + if installViaOLM, err = olm.IsAvailable(o.Context, olmClient); err != nil { + return errors.Wrap(err, "error while checking OLM availability. Run with '--olm=false' to skip this check") + } + if installViaOLM { + fmt.Fprintln(cobraCmd.OutOrStdout(), "OLM is available in the cluster"); + if err = olm.Install(o.Context, olmClient, o.Namespace, o.Global, o.olmOptions, collection); err != nil { + return err + } + } + + if err = install.WaitForAllCRDInstallation(o.Context, clientProvider, 90 * time.Second); err != nil { + return err + } + } + + if !o.SkipClusterSetup && !installViaOLM { err := install.SetupClusterWideResourcesOrCollect(o.Context, clientProvider, collection) if err != nil && k8serrors.IsForbidden(err) { - fmt.Println("Current user is not authorized to create cluster-wide objects like custom resource definitions or cluster roles: ", err) + fmt.Fprintln(cobraCmd.OutOrStdout(), "Current user is not authorized to create cluster-wide objects like custom resource definitions or cluster roles: ", err) meg := `please login as cluster-admin and execute "kamel install --cluster-setup" to install cluster-wide resources (one-time operation)` return errors.New(meg) @@ -166,7 +202,7 @@ func (o *installCmdOptions) install(cobraCmd *cobra.Command, _ []string) error { if o.ClusterSetupOnly { if collection == nil { - fmt.Println("Camel K cluster setup completed successfully") + fmt.Fprintln(cobraCmd.OutOrStdout(),"Camel K cluster setup completed successfully") } } else { c, err := o.GetCmdClient() @@ -176,7 +212,7 @@ func (o *installCmdOptions) install(cobraCmd *cobra.Command, _ []string) error { namespace := o.Namespace - if !o.SkipOperatorSetup { + if !o.SkipOperatorSetup && !installViaOLM { cfg := install.OperatorConfiguration{ CustomImage: o.OperatorImage, Namespace: namespace, @@ -187,8 +223,8 @@ func (o *installCmdOptions) install(cobraCmd *cobra.Command, _ []string) error { if err != nil { return err } - } else { - fmt.Println("Camel K operator installation skipped") + } else if o.SkipOperatorSetup { + fmt.Fprintln(cobraCmd.OutOrStdout(), "Camel K operator installation skipped") } generatedSecretName := "" @@ -288,8 +324,9 @@ func (o *installCmdOptions) install(cobraCmd *cobra.Command, _ []string) error { platform.Spec.Resources.Kits = o.Kits // Do not create an integration platform in global mode as platforms are expected - // to be created in other namespaces - if !o.Global { + // to be created in other namespaces. + // In OLM mode, the operator is installed in an external namespace, so it's ok to install the platform locally. + if !o.Global || installViaOLM { err = install.RuntimeObjectOrCollect(o.Context, c, namespace, collection, platform) if err != nil { return err @@ -311,10 +348,14 @@ func (o *installCmdOptions) install(cobraCmd *cobra.Command, _ []string) error { } } + strategy := "" + if installViaOLM { + strategy = "via OLM subscription" + } if o.Global { - fmt.Println("Camel K installed in namespace", namespace, "(global mode)") + fmt.Println("Camel K installed in namespace", namespace, strategy, "(global mode)") } else { - fmt.Println("Camel K installed in namespace", namespace) + fmt.Println("Camel K installed in namespace", namespace, strategy) } } } @@ -389,6 +430,14 @@ func (o *installCmdOptions) decode(cmd *cobra.Command, _ []string) error { o.registryAuth.Password = viper.GetString(path + ".registry-auth-password") o.registryAuth.Server = viper.GetString(path + ".registry-auth-server") + o.olmOptions.OperatorName = viper.GetString(path + ".olm-operator-name") + o.olmOptions.Package = viper.GetString(path + ".olm-package") + o.olmOptions.Channel = viper.GetString(path + ".olm-channel") + o.olmOptions.Source = viper.GetString(path + ".olm-source") + o.olmOptions.SourceNamespace = viper.GetString(path + ".olm-source-namespace") + o.olmOptions.StartingCSV = viper.GetString(path + ".olm-starting-csv") + o.olmOptions.GlobalNamespace = viper.GetString(path + ".olm-global-namespace") + return nil } diff --git a/pkg/util/olm/available.go b/pkg/util/olm/available.go new file mode 100644 index 0000000..2d7636f --- /dev/null +++ b/pkg/util/olm/available.go @@ -0,0 +1,59 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You 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" + + kubernetesutils "github.com/apache/camel-k/pkg/util/kubernetes" + olmv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/kubernetes" +) + +// IsAvailable returns true if we are connected to a cluster with OLM installed +// +// This method should not be called from the operator, as it might require permissions that are not available. +func IsAvailable(ctx context.Context, c kubernetes.Interface) (bool, error) { + // check some Knative APIs + for _, api := range getOLMGroupVersions() { + if installed, err := isAvailable(c, api); err != nil { + return false, err + } else if installed { + return true, nil + } + } + return false, nil +} + +func isAvailable(c kubernetes.Interface, api schema.GroupVersion) (bool, error) { + _, err := c.Discovery().ServerResourcesForGroupVersion(api.String()) + if err != nil && (k8serrors.IsNotFound(err) || kubernetesutils.IsUnknownAPIError(err)) { + return false, nil + } else if err != nil { + return false, err + } + return true, nil +} + +func getOLMGroupVersions() []schema.GroupVersion { + return []schema.GroupVersion{ + olmv1alpha1.SchemeGroupVersion, + } +} diff --git a/pkg/util/olm/operator.go b/pkg/util/olm/operator.go new file mode 100644 index 0000000..c5bda90 --- /dev/null +++ b/pkg/util/olm/operator.go @@ -0,0 +1,201 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You 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" + "strings" + + "github.com/apache/camel-k/pkg/client" + "github.com/apache/camel-k/pkg/util/kubernetes" + olmv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "sigs.k8s.io/controller-runtime/pkg/client" +) + +// The following properties can be overridden at build time via ldflags + +// DefaultOperatorName is the Camel K operator name in OLM +var DefaultOperatorName = "camel-k-operator" + +// DefaultPackage is the Camel K package in OLM +var DefaultPackage = "camel-k" + +// DefaultChannel is the distribution channel in Operator Hub +var DefaultChannel = "alpha" + +// DefaultSource is the name of the operator source where the operator is published +var DefaultSource = "community-operators" + +// DefaultSourceNamespace is the namespace of the operator source +var DefaultSourceNamespace = "openshift-marketplace" + +// DefaultStartingCSV contains the specific version to install +var DefaultStartingCSV = "" + +// DefaultGlobalNamespace indicates a namespace containing an OperatorGroup that enables the operator to watch all namespaces. +// It will be used in global installation mode. +var DefaultGlobalNamespace = "openshift-operators" + +// Options contains information about an operator in OLM +type Options struct { + OperatorName string + Package string + Channel string + Source string + SourceNamespace string + StartingCSV string + GlobalNamespace string +} + +// IsOperatorInstalled tells if a OLM CSV or a Subscription is already installed in the namespace +func IsOperatorInstalled(ctx context.Context, client client.Client, namespace string, global bool, options Options) (bool, error) { + options = fillDefaults(options) + // CSV is present in current namespace for both local and global installation modes + if csv, err := findCSV(ctx, client, namespace, options); err != nil { + return false, err + } else if csv != nil { + return true, nil + } + // A subscription may indicate an in-progress installation + if sub, err := findSubscription(ctx, client, namespace, global, options); err != nil { + return false, err + } else if sub != nil { + return true, nil + } + + return false, nil +} + +// Install creates a subscription for the OLM package +func Install(ctx context.Context, client client.Client, namespace string, global bool, options Options, collection *kubernetes.Collection) error { + options = fillDefaults(options) + if installed, err := IsOperatorInstalled(ctx, client, namespace, global, options); err != nil { + return err + } else if installed { + // Already installed + return nil + } + + targetNamespace := namespace + if global { + targetNamespace = options.GlobalNamespace + } + + sub := olmv1alpha1.Subscription{ + ObjectMeta: v1.ObjectMeta{ + Name: options.Package, + Namespace: targetNamespace, + }, + Spec: &olmv1alpha1.SubscriptionSpec{ + CatalogSource: options.Source, + CatalogSourceNamespace: options.SourceNamespace, + Package: options.Package, + Channel: options.Channel, + StartingCSV: options.StartingCSV, + InstallPlanApproval: olmv1alpha1.ApprovalAutomatic, + }, + } + if collection != nil { + collection.Add(&sub) + return nil + } + return client.Create(ctx, &sub) +} + +// Uninstall removes CSV and subscription from the namespace +func Uninstall(ctx context.Context, client client.Client, namespace string, global bool, options Options) error { + sub, err := findSubscription(ctx, client, namespace, global, options) + if err != nil { + return err + } + if sub != nil { + if err := client.Delete(ctx, sub); err != nil { + return err + } + } + + csv, err := findCSV(ctx, client, namespace, options) + if err != nil { + return err + } + if csv != nil { + if err := client.Delete(ctx, csv); err != nil { + return err + } + } + return nil +} + +func findSubscription(ctx context.Context, client client.Client, namespace string, global bool, options Options) (*olmv1alpha1.Subscription, error) { + subNamespace := namespace + if global { + // In case of global installation, global subscription must be removed + subNamespace = options.GlobalNamespace + } + subscriptionList := olmv1alpha1.SubscriptionList{} + if err := client.List(ctx, &subscriptionList, runtime.InNamespace(subNamespace)); err != nil { + return nil, err + } + + for _, item := range subscriptionList.Items { + if item.Spec.Package == options.Package { + return &item, nil + } + } + return nil, nil +} + +func findCSV(ctx context.Context, client client.Client, namespace string, options Options) (*olmv1alpha1.ClusterServiceVersion, error) { + csvList := olmv1alpha1.ClusterServiceVersionList{} + if err := client.List(ctx, &csvList, runtime.InNamespace(namespace)); err != nil { + return nil, err + } + + for _, item := range csvList.Items { + if strings.HasPrefix(item.Name, options.OperatorName) { + return &item, nil + } + } + return nil, nil +} + +func fillDefaults(o Options) Options { + if o.OperatorName == "" { + o.OperatorName = DefaultOperatorName + } + if o.Package == "" { + o.Package = DefaultPackage + } + if o.Channel == "" { + o.Channel = DefaultChannel + } + if o.Source == "" { + o.Source = DefaultSource + } + if o.SourceNamespace == "" { + o.SourceNamespace = DefaultSourceNamespace + } + if o.StartingCSV == "" { + o.StartingCSV = DefaultStartingCSV + } + if o.GlobalNamespace == "" { + o.GlobalNamespace = DefaultGlobalNamespace + } + return o +}
