Skip to content

Commit

Permalink
Refactor operator package installation
Browse files Browse the repository at this point in the history
The old 'InstallPackage' function has been extracted into a separate package. It's functionality has been split up into multiple functions handling different installation resources. The function signature of 'install.Package' introduces variadic option parameters to provide a backwards-compatible API.

Signed-off-by: Jan Schlicht <jan@d2iq.com>
  • Loading branch information
Jan Schlicht committed Jun 2, 2020
1 parent 03edc91 commit de69d7f
Show file tree
Hide file tree
Showing 14 changed files with 395 additions and 220 deletions.
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/mitchellh/copystructure v1.0.0 // indirect
github.com/onsi/ginkgo v1.12.0
github.com/onsi/gomega v1.9.0
github.com/pkg/errors v0.9.1 // indirect
github.com/spf13/afero v1.2.2
github.com/spf13/cobra v1.0.0
github.com/spf13/pflag v1.0.5
Expand All @@ -24,6 +25,8 @@ require (
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
golang.org/x/sys v0.0.0-20200408040146-ea54a3c99b9b // indirect
google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24 // indirect
google.golang.org/grpc v1.26.0 // indirect
gopkg.in/yaml.v2 v2.2.8
gotest.tools v2.2.0+incompatible
k8s.io/api v0.17.3
Expand Down
14 changes: 14 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/blang/semver v3.5.0+incompatible h1:CGxCgetQ64DKk7rdZ++Vfnb1+ogGNnB17OJKJXD2Cfs=
github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 h1:7aWHqerlJ41y6FOsEUvknqgXnGmJyJSbjhAWq5pO4F8=
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw=
Expand Down Expand Up @@ -121,6 +122,8 @@ github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkg
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk=
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
Expand Down Expand Up @@ -415,6 +418,8 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.0 h1:J8lpUdobwIeCI7OiSxHqEwJUKvJwicL5+3v1oe2Yb4k=
github.com/pkg/errors v0.9.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand All @@ -426,6 +431,8 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw=
Expand Down Expand Up @@ -491,6 +498,7 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
Expand Down Expand Up @@ -652,10 +660,16 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7 h1:ZUjXAXmrAyrmmCP
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 h1:nfPFGzJkUDX6uBmpN/pSw7MbOAWegH5QDQuoXFHedLg=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24 h1:wDju+RU97qa0FZT0QnZDg9Uc2dH0Ql513kFvHocz+WM=
google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1 h1:q4XQuHFC6I28BKZpo6IYyb3mNO+l7lSOxRuYTCiDfXk=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
24 changes: 22 additions & 2 deletions pkg/kudoctl/cmd/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (

"github.com/kudobuilder/kudo/pkg/kudoctl/clog"
"github.com/kudobuilder/kudo/pkg/kudoctl/env"
"github.com/kudobuilder/kudo/pkg/kudoctl/packages/install"
pkgresolver "github.com/kudobuilder/kudo/pkg/kudoctl/packages/resolver"
"github.com/kudobuilder/kudo/pkg/kudoctl/util/kudo"
"github.com/kudobuilder/kudo/pkg/kudoctl/util/repo"
)

Expand Down Expand Up @@ -79,5 +79,25 @@ func installOperator(operatorArgument string, options *Options, fs afero.Fs, set
return fmt.Errorf("failed to resolve operator package for: %s %w", operatorArgument, err)
}

return kudo.InstallPackage(kc, pkg.Resources, options.SkipInstance, options.InstanceName, settings.Namespace, options.Parameters, options.Wait, options.CreateNameSpace, time.Duration(options.WaitTime)*time.Second)
installOpts := []install.Option{}

if options.SkipInstance {
installOpts = append(installOpts, install.SkipInstance())
}

if options.CreateNameSpace {
installOpts = append(installOpts, install.CreateNamespace())
}

if options.Wait {
installOpts = append(installOpts, install.WaitForInstance(time.Duration(options.WaitTime)*time.Second))
}

return install.Package(
kc,
options.InstanceName,
settings.Namespace,
*pkg.Resources,
options.Parameters,
installOpts...)
}
144 changes: 144 additions & 0 deletions pkg/kudoctl/packages/install/install.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Package install provides function to install package resources
// on a Kubernetes cluster.
package install

import (
"strings"
"time"

"github.com/kudobuilder/kudo/pkg/apis/kudo/v1beta1"
"github.com/kudobuilder/kudo/pkg/kudoctl/clog"
"github.com/kudobuilder/kudo/pkg/kudoctl/packages"
"github.com/kudobuilder/kudo/pkg/kudoctl/util/kudo"
)

type Options struct {
skipInstance bool
wait *time.Duration
createNamespace bool
}

type Option func(*Options)

// SkipInstance installs only Operator and OperatorVersion
// of an operator package.
func SkipInstance() Option {
return func(o *Options) {
o.skipInstance = true
}
}

// WaitForInstance waits an amount of time for the instance
// to complete installation.
func WaitForInstance(duration time.Duration) Option {
return func(o *Options) {
o.wait = &duration
}
}

// CreateNamespace creates the specified namespace before installation.
// If available, a namespace manifest in the operator package is
// rendered using the installation parameters.
func CreateNamespace() Option {
return func(o *Options) {
o.createNamespace = true
}
}

// Package installs an operator package with parameters into a namespace.
// Instance name, namespace and operator parameters are applied to the
// operator package resources. These rendered resources are then created
// on the Kubernetes cluster.
func Package(
client *kudo.Client,
instanceName string,
namespace string,
resources packages.Resources,
parameters map[string]string,
opts ...Option) error {
clog.V(3).Printf("operator name: %v", resources.Operator.Name)
clog.V(3).Printf("operator version: %v", resources.OperatorVersion.Spec.Version)

options := Options{}
for _, o := range opts {
o(&options)
}

applyOverrides(&resources, instanceName, namespace, parameters)

if err := client.ValidateServerForOperator(resources.Operator); err != nil {
return err
}

if options.createNamespace {
if err := installNamespace(client, resources, parameters); err != nil {
return err
}
}

if err := installOperatorAndOperatorVersion(client, resources); err != nil {
return err
}

if options.skipInstance {
return nil
}

if err := validateParameters(
*resources.Instance,
resources.OperatorVersion.Spec.Parameters); err != nil {
return err
}

if err := installInstance(client, resources.Instance); err != nil {
return err
}

if options.wait != nil {
if err := waitForInstance(client, resources.Instance, *options.wait); err != nil {
return err
}
}

return nil
}

func applyOverrides(
resources *packages.Resources,
instanceName string,
namespace string,
parameters map[string]string) {
resources.Operator.SetNamespace(namespace)
resources.OperatorVersion.SetNamespace(namespace)
resources.Instance.SetNamespace(namespace)

if instanceName != "" {
resources.Instance.SetName(instanceName)
clog.V(3).Printf("instance name: %v", instanceName)
}
if parameters != nil {
resources.Instance.Spec.Parameters = parameters
clog.V(3).Printf("parameters in use: %v", parameters)
}
}

func validateParameters(instance v1beta1.Instance, parameters []v1beta1.Parameter) error {
missingParameters := []string{}

for _, p := range parameters {
if *p.Required && p.Default == nil {
_, ok := instance.Spec.Parameters[p.Name]
if !ok {
missingParameters = append(missingParameters, p.Name)
}
}
}

if len(missingParameters) > 0 {
return clog.Errorf(
"missing required parameters during installation: %s",
strings.Join(missingParameters, ","))
}

return nil
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
package kudo
package install

import (
"flag"
"fmt"
"io/ioutil"
"path/filepath"
"testing"

tassert "github.com/stretchr/testify/assert"
"gotest.tools/assert"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/version"
Expand All @@ -18,6 +15,7 @@ import (
"github.com/kudobuilder/kudo/pkg/apis/kudo/v1beta1"
"github.com/kudobuilder/kudo/pkg/client/clientset/versioned/fake"
"github.com/kudobuilder/kudo/pkg/kudoctl/packages"
"github.com/kudobuilder/kudo/pkg/kudoctl/util/kudo"
"github.com/kudobuilder/kudo/pkg/util/convert"
)

Expand Down Expand Up @@ -84,7 +82,7 @@ func Test_InstallPackage(t *testing.T) {

for _, tt := range tests {
client := fake.NewSimpleClientset()
kc := NewClientFromK8s(client, kubefake.NewSimpleClientset())
kc := kudo.NewClientFromK8s(client, kubefake.NewSimpleClientset())

fakeDiscovery, ok := client.Discovery().(*fakediscovery.FakeDiscovery)
if !ok {
Expand All @@ -96,58 +94,40 @@ func Test_InstallPackage(t *testing.T) {

testResources := resources
testResources.OperatorVersion.Spec.Parameters = tt.parameters
namespace := "default" //nolint:goconst

err := InstallPackage(kc, &testResources, tt.skipInstance, "", namespace, tt.installParameters, false, false, 0)
if tt.err != "" {
assert.ErrorContains(t, err, tt.err)
}
}
}

func TestNamespaceManifestRendering(t *testing.T) {

namespaceFile := "testdata/namespace.yaml"

ns, err := ioutil.ReadFile(namespaceFile)
tassert.NoError(t, err)

params := make(map[string]string)
params["foo"] = "bar-param"

rendered, err := render("namespace", string(ns), testResources(), "foo-bar", "namespace-name", params)
tassert.NoError(t, err)

file := "rendered-namespace.yaml"
gf := filepath.Join("testdata", file+".golden")
const namespace = "default"

if *update {
t.Log("update golden file")
if err := ioutil.WriteFile(gf, []byte(rendered), 0644); err != nil {
t.Fatalf("failed to update golden file: %s", err)
options := []Option{}
if tt.skipInstance {
options = append(options, SkipInstance())
}
}

golden, err := ioutil.ReadFile(gf)
if err != nil {
t.Fatalf("failed reading .golden: %s", err)
err := Package(kc, "", namespace, testResources, tt.installParameters, options...)
if tt.err != "" {
assert.EqualError(t, err, tt.err)
}
}

assert.Equal(t, string(golden), rendered, "for golden file: %s", gf)
}

func testResources() *packages.Resources {
result := &packages.Resources{
func testResources() packages.Resources {
return packages.Resources{
Operator: &v1beta1.Operator{
ObjectMeta: metav1.ObjectMeta{Name: "InstanceName"},
ObjectMeta: metav1.ObjectMeta{
Name: "OperatorName",
Namespace: "default",
},
},
OperatorVersion: &v1beta1.OperatorVersion{
Spec: v1beta1.OperatorVersionSpec{
AppVersion: "1.0",
Version: "2.0",
},
},
Instance: nil,
Instance: &v1beta1.Instance{
ObjectMeta: metav1.ObjectMeta{
Name: "InstanceName",
Namespace: "default",
},
},
}
return result
}
Loading

0 comments on commit de69d7f

Please sign in to comment.