Skip to content

Commit

Permalink
Remove interactive install, more install refactoring (#500)
Browse files Browse the repository at this point in the history
* `--auto-approve` option from `kudo install` was removed
  • Loading branch information
alenkacz authored and kensipe committed Jul 3, 2019
1 parent ebe82db commit 45a8bed
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 188 deletions.
1 change: 0 additions & 1 deletion pkg/kudoctl/cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -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\"")
Expand Down
62 changes: 21 additions & 41 deletions pkg/kudoctl/cmd/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -16,7 +15,6 @@ import (

// Options defines configuration options for the install command
type Options struct {
AutoApprove bool
InstanceName string
KubeConfigPath string
Namespace string
Expand Down Expand Up @@ -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")
Expand All @@ -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.
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down
66 changes: 7 additions & 59 deletions pkg/kudoctl/util/kudo/kudo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{})
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down
110 changes: 23 additions & 87 deletions pkg/kudoctl/util/kudo/kudo_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package kudo

import (
"fmt"
"reflect"
"testing"

"github.com/kudobuilder/kudo/pkg/apis/kudo/v1alpha1"
Expand Down Expand Up @@ -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: <nil>\n got: %v", err)
}
}

func TestK2oClient_OperatorExistsInCluster(t *testing.T) {

obj := v1alpha1.Operator{
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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",
Expand All @@ -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)
}
}
}
Expand Down

0 comments on commit 45a8bed

Please sign in to comment.