diff --git a/pkg/kudoctl/cmd/install.go b/pkg/kudoctl/cmd/install.go index 94207ea52..f00189331 100644 --- a/pkg/kudoctl/cmd/install.go +++ b/pkg/kudoctl/cmd/install.go @@ -92,7 +92,6 @@ func newInstallCmd() *cobra.Command { SilenceUsage: true, } - installCmd.Flags().BoolVar(&options.AutoApprove, "auto-approve", false, "Skip interactive approval when existing version found. (default \"false\")") installCmd.Flags().StringVar(&options.KubeConfigPath, "kubeconfig", "", "The file path to Kubernetes configuration file. (default \"$HOME/.kube/config\")") installCmd.Flags().StringVar(&options.InstanceName, "instance", "", "The instance name. (default to Operator name)") installCmd.Flags().StringVar(&options.Namespace, "namespace", "default", "The namespace used for the package installation. (default \"default\"") diff --git a/pkg/kudoctl/cmd/install/install.go b/pkg/kudoctl/cmd/install/install.go index 0b70ec027..81d0d372e 100644 --- a/pkg/kudoctl/cmd/install/install.go +++ b/pkg/kudoctl/cmd/install/install.go @@ -6,7 +6,6 @@ import ( "github.com/kudobuilder/kudo/pkg/apis/kudo/v1alpha1" "github.com/kudobuilder/kudo/pkg/kudoctl/util/check" - "github.com/kudobuilder/kudo/pkg/kudoctl/util/helpers" "github.com/kudobuilder/kudo/pkg/kudoctl/util/kudo" "github.com/kudobuilder/kudo/pkg/kudoctl/util/repo" "github.com/pkg/errors" @@ -16,7 +15,6 @@ import ( // Options defines configuration options for the install command type Options struct { - AutoApprove bool InstanceName string KubeConfigPath string Namespace string @@ -115,10 +113,12 @@ func installOperator(operatorArgument string, options *Options) error { return errors.Wrapf(err, "failed to resolve package CRDs for operator: %s", operatorArgument) } + operatorName := crds.Operator.ObjectMeta.Name + operatorVersion := crds.OperatorVersion.Spec.Version + // Operator part // Check if Operator exists - operatorName := crds.Operator.ObjectMeta.Name if !kc.OperatorExistsInCluster(crds.Operator.ObjectMeta.Name, options.Namespace) { if err := installSingleOperatorToCluster(operatorName, options.Namespace, crds.Operator, kc); err != nil { return errors.Wrap(err, "installing single Operator") @@ -127,34 +127,17 @@ func installOperator(operatorArgument string, options *Options) error { // OperatorVersion part - // Check if AnyOperatorVersion for Operator exists - if !kc.AnyOperatorVersionExistsInCluster(crds.Operator.ObjectMeta.Name, options.Namespace) { - // OperatorVersion CRD for Operator does not exist + versionsInstalled, err := kc.OperatorVersionsInstalled(operatorName, options.Namespace) + if err != nil { + return errors.Wrap(err, "retrieving existing operator versions") + } + if !versionExists(versionsInstalled, operatorVersion) { + // this version does not exist in the cluster if err := installSingleOperatorVersionToCluster(operatorName, options.Namespace, kc, crds.OperatorVersion); err != nil { return errors.Wrapf(err, "installing OperatorVersion CRD for operator: %s", operatorName) } } - // Check if OperatorVersion is out of sync with official OperatorVersion for this Operator - if !kc.OperatorVersionInClusterOutOfSync(operatorName, crds.OperatorVersion.Spec.Version, options.Namespace) { - // This happens when the given OperatorVersion is not existing. E.g. - // when a version has been installed that is not part of the official kudobuilder/operators repo. - if !options.AutoApprove { - fmt.Printf("No official OperatorVersion has been found for \"%s\". "+ - "Do you want to install one? (Yes/no) ", operatorName) - if helpers.AskForConfirmation() { - if err := installSingleOperatorVersionToCluster(operatorName, options.Namespace, kc, crds.OperatorVersion); err != nil { - return errors.Wrapf(err, "installing OperatorVersion CRD for operator %s", operatorName) - } - } - } else { - if err := installSingleOperatorVersionToCluster(operatorName, options.Namespace, kc, crds.OperatorVersion); err != nil { - return errors.Wrapf(err, "installing OperatorVersion CRD for operator %s", operatorName) - } - } - - } - // Instances part // it creates the Instances object just after Operator and // OperatorVersion objects are created to ensure Instances can be created. @@ -176,21 +159,9 @@ func installOperator(operatorArgument string, options *Options) error { } if !instanceExists { - // This happens when the given OperatorVersion is not existing. E.g. - // when a version has been installed that is not part of the official kudobuilder/operators repo. - if !options.AutoApprove { - fmt.Printf("No instance named '%s' tied to this '%s' version has been found. "+ - "Do you want to create one? (Yes/no) ", instanceName, operatorName) - if helpers.AskForConfirmation() { - if err := installSingleInstanceToCluster(operatorName, crds.Instance, kc, options); err != nil { - return errors.Wrap(err, "installing single instance") - } - } - } else { - if err := installSingleInstanceToCluster(operatorName, crds.Instance, kc, options); err != nil { - return errors.Wrap(err, "installing single instance") - - } + if err := installSingleInstanceToCluster(operatorName, crds.Instance, kc, options); err != nil { + return errors.Wrap(err, "installing single instance") + } } else { @@ -200,6 +171,15 @@ func installOperator(operatorArgument string, options *Options) error { return nil } +func versionExists(versions []string, currentVersion string) bool { + for _, v := range versions { + if v == currentVersion { + return true + } + } + return false +} + // installSingleOperatorToCluster installs a given Operator to the cluster // TODO: needs testing func installSingleOperatorToCluster(name, namespace string, f *v1alpha1.Operator, kc *kudo.Client) error { diff --git a/pkg/kudoctl/util/kudo/kudo.go b/pkg/kudoctl/util/kudo/kudo.go index 42b011cdf..d4f56741a 100644 --- a/pkg/kudoctl/util/kudo/kudo.go +++ b/pkg/kudoctl/util/kudo/kudo.go @@ -57,27 +57,6 @@ func NewClient(namespace, kubeConfigPath string) (*Client, error) { }, nil } -// CRDsInstalled checks for essential CRDs of KUDO to be installed -func (c *Client) CRDsInstalled(namespace string) error { - _, err := c.clientset.KudoV1alpha1().Operators(namespace).List(v1.ListOptions{}) - if err != nil { - return errors.WithMessage(err, "operators") - } - _, err = c.clientset.KudoV1alpha1().OperatorVersions(namespace).List(v1.ListOptions{}) - if err != nil { - return errors.WithMessage(err, "operatorversions") - } - _, err = c.clientset.KudoV1alpha1().Instances(namespace).List(v1.ListOptions{}) - if err != nil { - return errors.WithMessage(err, "instances") - } - _, err = c.clientset.KudoV1alpha1().PlanExecutions(namespace).List(v1.ListOptions{}) - if err != nil { - return errors.WithMessage(err, "planexecutions") - } - return nil -} - // OperatorExistsInCluster checks if a given Operator object is installed on the current k8s cluster func (c *Client) OperatorExistsInCluster(name, namespace string) bool { operator, err := c.clientset.KudoV1alpha1().Operators(namespace).Get(name, v1.GetOptions{}) @@ -88,30 +67,6 @@ func (c *Client) OperatorExistsInCluster(name, namespace string) bool { return true } -// AnyOperatorVersionExistsInCluster checks if any OperatorVersion object matches to the given Operator name -// in the cluster -func (c *Client) AnyOperatorVersionExistsInCluster(operator string, namespace string) bool { - fv, err := c.clientset.KudoV1alpha1().OperatorVersions(namespace).List(v1.ListOptions{}) - if err != nil { - return false - } - if len(fv.Items) < 1 { - return false - } - - var i int - for _, v := range fv.Items { - if strings.HasPrefix(v.Name, operator) { - i++ - } - } - if i < 1 { - return false - } - fmt.Printf("operatorversion.kudo.k8s.io/%s unchanged\n", operator) - return true -} - // InstanceExistsInCluster checks if any OperatorVersion object matches to the given Operator name // in the cluster. // An Instance has two identifiers: @@ -151,27 +106,20 @@ func (c *Client) InstanceExistsInCluster(name, namespace, version, instanceName return true, nil } -// OperatorVersionInClusterOutOfSync checks if any OperatorVersion object matches a given Operator name and -// if not it returns false. False means that for the given Operator the most recent official OperatorVersion -// is not installed in the cluster or an error occurred. -func (c *Client) OperatorVersionInClusterOutOfSync(operator, mostRecentVersion, namespace string) bool { +// OperatorVersionsInstalled lists all the versions of given operator installed in the cluster in given ns +func (c *Client) OperatorVersionsInstalled(operatorName, namespace string) ([]string, error) { fv, err := c.clientset.KudoV1alpha1().OperatorVersions(namespace).List(v1.ListOptions{}) if err != nil { - return false - } - if len(fv.Items) < 1 { - return false + return nil, err } + existingVersions := []string{} - var i int for _, v := range fv.Items { - if strings.HasPrefix(v.Name, operator) { - if v.Spec.Version == mostRecentVersion { - i++ - } + if strings.HasPrefix(v.Name, operatorName) { + existingVersions = append(existingVersions, v.Spec.Version) } } - return !(i < 1) + return existingVersions, nil } // InstallOperatorObjToCluster expects a valid Operator obj to install diff --git a/pkg/kudoctl/util/kudo/kudo_test.go b/pkg/kudoctl/util/kudo/kudo_test.go index 135e19bad..2407cc2a7 100644 --- a/pkg/kudoctl/util/kudo/kudo_test.go +++ b/pkg/kudoctl/util/kudo/kudo_test.go @@ -1,6 +1,8 @@ package kudo import ( + "fmt" + "reflect" "testing" "github.com/kudobuilder/kudo/pkg/apis/kudo/v1alpha1" @@ -31,14 +33,6 @@ func TestNewK2oClient(t *testing.T) { } } -func TestK2oClient_CRDsInstalled(t *testing.T) { - k2o := newTestSimpleK2o() - err := k2o.CRDsInstalled("default") - if err != nil { - t.Errorf("\nexpected: \n got: %v", err) - } -} - func TestK2oClient_OperatorExistsInCluster(t *testing.T) { obj := v1alpha1.Operator{ @@ -89,49 +83,6 @@ func TestK2oClient_OperatorExistsInCluster(t *testing.T) { } } -func TestK2oClient_AnyOperatorVersionExistsInCluster(t *testing.T) { - obj := v1alpha1.OperatorVersion{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "kudo.k8s.io/v1alpha1", - Kind: "OperatorVersion", - }, - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "controller-tools.k8s.io": "1.0", - }, - Name: "test", - }, - } - - tests := []struct { - bool bool - err string - createns string - getns string - obj *v1alpha1.OperatorVersion - }{ - {false, "", "", "", nil}, // 1 - {false, "", "default", "default", nil}, // 2 - {true, "", "", "", &obj}, // 3 - {false, "", "", "qa", &obj}, // 4 - {true, "", "default", "", &obj}, // 5 - } - - for i, tt := range tests { - i := i - k2o := newTestSimpleK2o() - - // create OperatorVersion - k2o.clientset.KudoV1alpha1().OperatorVersions(tt.createns).Create(tt.obj) - - // test if OperatorVersion exists in namespace - exist := k2o.AnyOperatorVersionExistsInCluster("test", tt.getns) - if tt.bool != exist { - t.Errorf("%d:\nexpected: %v\n got: %v", i+1, tt.bool, exist) - } - } -} - func TestK2oClient_InstanceExistsInCluster(t *testing.T) { obj := v1alpha1.Instance{ TypeMeta: metav1.TypeMeta{ @@ -206,7 +157,8 @@ func TestK2oClient_InstanceExistsInCluster(t *testing.T) { } } -func TestK2oClient_OperatorVersionInClusterOutOfSync(t *testing.T) { +func TestK2oClient_OperatorVersionsInstalled(t *testing.T) { + operatorName := "test" obj := v1alpha1.OperatorVersion{ TypeMeta: metav1.TypeMeta{ APIVersion: "kudo.k8s.io/v1alpha1", @@ -216,56 +168,40 @@ func TestK2oClient_OperatorVersionInClusterOutOfSync(t *testing.T) { Labels: map[string]string{ "controller-tools.k8s.io": "1.0", }, - Name: "test-1.0", + Name: fmt.Sprintf("%s-1.0", operatorName), }, Spec: v1alpha1.OperatorVersionSpec{ Version: "1.0", }, } - outdatedObj := v1alpha1.OperatorVersion{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "kudo.k8s.io/v1alpha1", - Kind: "OperatorVersion", - }, - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "controller-tools.k8s.io": "1.0", - }, - Name: "test-0.9", - }, - Spec: v1alpha1.OperatorVersionSpec{ - Version: "0.9", - }, - } - + installNamespace := "default" tests := []struct { - bool bool - err string - createns string - getns string - obj *v1alpha1.OperatorVersion + name string + expectedVersions []string + namespace string + obj *v1alpha1.OperatorVersion }{ - {false, "", "", "", nil}, // 1 - {false, "", "default", "default", nil}, // 2 - {true, "", "", "", &obj}, // 3 - {true, "", "", "", &obj}, // 4 - {false, "", "", "qa", &obj}, // 5 - {true, "", "qa", "qa", &obj}, // 6 - {false, "", "kudo", "kudo", &outdatedObj}, // 7 + {"no operator version defined", []string{}, installNamespace, nil}, + {"operator version exists in the same namespace", []string{obj.Spec.Version}, installNamespace, &obj}, + {"operator version exists in different namespace", []string{}, "otherns", &obj}, } - for i, tt := range tests { - i := i + for _, tt := range tests { k2o := newTestSimpleK2o() // create Instance - k2o.clientset.KudoV1alpha1().OperatorVersions(tt.createns).Create(tt.obj) + if tt.obj != nil { + _, err := k2o.clientset.KudoV1alpha1().OperatorVersions(installNamespace).Create(tt.obj) + if err != nil { + t.Errorf("Error creating operator version in tests setup for %s", tt.name) + } + } // test if OperatorVersion exists in namespace - exist := k2o.OperatorVersionInClusterOutOfSync("test", "1.0", tt.getns) - if tt.bool != exist { - t.Errorf("%d:\nexpected: %v\n got: %v", i+1, tt.bool, exist) + existingVersions, _ := k2o.OperatorVersionsInstalled(operatorName, tt.namespace) + if !reflect.DeepEqual(tt.expectedVersions, existingVersions) { + t.Errorf("%s:\nexpected: %v\n got: %v", tt.name, tt.expectedVersions, existingVersions) } } }