From 9db03a9f2be3d7bfe457ebc000881ad4433a27d4 Mon Sep 17 00:00:00 2001 From: Jan Schlicht Date: Thu, 18 Jun 2020 14:21:17 +0200 Subject: [PATCH] Set operator name of 'KudoOperator' tasks for dependencies (#1560) Package dependencies can reference operator packages from local files or arbitrary URLs. KUDO's controller doesn't know about the origin of packages once they have been resolved. But it needs to resolve instances from 'KudoOperator' tasks. To solve this, the 'Package' and 'OperatorName' of these tasks are updated with the operator name before they are applied on a cluster. Signed-off-by: Jan Schlicht Co-authored-by: Aleksey Dukhovniy --- pkg/kudoctl/packages/install/dependencies.go | 46 +++++++++++-------- .../packages/install/dependencies_test.go | 4 +- pkg/kudoctl/packages/install/package.go | 39 +++++++++++++++- .../operator-with-dependencies/01-assert.yaml | 12 +++++ .../parent-operator/operator.yaml | 1 - 5 files changed, 80 insertions(+), 22 deletions(-) diff --git a/pkg/kudoctl/packages/install/dependencies.go b/pkg/kudoctl/packages/install/dependencies.go index 113de875a..c016fb67c 100644 --- a/pkg/kudoctl/packages/install/dependencies.go +++ b/pkg/kudoctl/packages/install/dependencies.go @@ -43,31 +43,35 @@ func (g *dependencyGraph) Visit(v int, do func(w int, c int64) bool) bool { return false } +type Dependency struct { + packages.Resources + + PackageName string +} + // gatherDependencies resolved all dependencies of a package. // Dependencies are resolved recursively. // Cyclic dependencies are detected and result in an error. -func gatherDependencies(root packages.Resources, resolver pkgresolver.Resolver) ([]packages.Resources, error) { - pkgs := []packages.Resources{ - root, +func gatherDependencies(root packages.Resources, resolver pkgresolver.Resolver) ([]Dependency, error) { + dependencies := []Dependency{ + {Resources: root}, } - // Each vertex in 'g' matches an index in 'pkgs'. + // Each vertex in 'g' matches an index in 'dependencies'. g := dependencyGraph{ edges: []map[int]struct{}{{}}, } - if err := dependencyWalk(&pkgs, &g, root, 0, resolver); err != nil { + if err := dependencyWalk(&dependencies, &g, root, 0, resolver); err != nil { return nil, err } // Remove 'root' from the list of dependencies. - pkgs = funk.Drop(pkgs, 1).([]packages.Resources) //nolint:errcheck - - return pkgs, nil + return dependencies[1:], nil } func dependencyWalk( - pkgs *[]packages.Resources, + dependencies *[]Dependency, g *dependencyGraph, parent packages.Resources, parentIndex int, @@ -87,14 +91,19 @@ func dependencyWalk( "failed to resolve package %s, dependency of package %s: %v", fullyQualifiedName(childTask.Spec.KudoOperatorTaskSpec), parent.OperatorVersion.FullyQualifiedName(), err) } + childDependency := Dependency{ + Resources: *childPkg.Resources, + PackageName: childTask.Spec.KudoOperatorTaskSpec.Package, + } + newPackage := false - childIndex := indexOf(pkgs, childPkg.Resources) + childIndex := indexOf(dependencies, &childDependency) if childIndex == -1 { clog.Printf("Adding new dependency %s", childPkg.Resources.OperatorVersion.FullyQualifiedName()) newPackage = true - *pkgs = append(*pkgs, *childPkg.Resources) - childIndex = len(*pkgs) - 1 + *dependencies = append(*dependencies, childDependency) + childIndex = len(*dependencies) - 1 // The number of vertices in 'g' has to match the number of packages we're tracking. g.AddVertex() @@ -110,7 +119,7 @@ func dependencyWalk( // We only need to walk the dependencies if the package is new if newPackage { - if err := dependencyWalk(pkgs, g, *childPkg.Resources, childIndex, resolver); err != nil { + if err := dependencyWalk(dependencies, g, *childPkg.Resources, childIndex, resolver); err != nil { return err } } @@ -119,11 +128,12 @@ func dependencyWalk( return nil } -// indexOf method searches for the pkg in pkgs that has the same OperatorVersion/AppVersion (using -// EqualOperatorVersion method) and returns its index or -1 if not found. -func indexOf(pkgs *[]packages.Resources, pkg *packages.Resources) int { - for i, p := range *pkgs { - if p.OperatorVersion.EqualOperatorVersion(pkg.OperatorVersion) { +// indexOf method searches for the dependency in dependencies that has the same +// OperatorVersion/AppVersion (using EqualOperatorVersion method) and returns +// its index or -1 if not found. +func indexOf(dependencies *[]Dependency, dependency *Dependency) int { + for i, d := range *dependencies { + if d.OperatorVersion.EqualOperatorVersion(dependency.OperatorVersion) { return i } } diff --git a/pkg/kudoctl/packages/install/dependencies_test.go b/pkg/kudoctl/packages/install/dependencies_test.go index b881d84da..bb9ddec37 100644 --- a/pkg/kudoctl/packages/install/dependencies_test.go +++ b/pkg/kudoctl/packages/install/dependencies_test.go @@ -187,8 +187,8 @@ func TestGatherDependencies(t *testing.T) { for _, operatorName := range tt.want { operatorName := operatorName - assert.NotNil(t, funk.Find(got, func(p packages.Resources) bool { - return p.Operator.Name == operatorName + assert.NotNil(t, funk.Find(got, func(dep Dependency) bool { + return dep.Operator.Name == operatorName }), tt.name) } }) diff --git a/pkg/kudoctl/packages/install/package.go b/pkg/kudoctl/packages/install/package.go index 4ec934efc..77f9ddc28 100644 --- a/pkg/kudoctl/packages/install/package.go +++ b/pkg/kudoctl/packages/install/package.go @@ -5,6 +5,7 @@ import ( "time" "github.com/kudobuilder/kudo/pkg/apis/kudo/v1beta1" + engtask "github.com/kudobuilder/kudo/pkg/engine/task" "github.com/kudobuilder/kudo/pkg/kudoctl/clog" "github.com/kudobuilder/kudo/pkg/kudoctl/packages" "github.com/kudobuilder/kudo/pkg/kudoctl/packages/resolver" @@ -64,11 +65,25 @@ func Package( return err } + // The KUDO controller will create Instances for the dependencies. For this + // it needs to resolve the dependencies again from 'KudoOperatorTaskSpec'. + // But it cannot resolve packages like the CLI, because it may + // not have access to the referenced local files or URLs. + // It can however resolve the OperatorVersion from the name of the operator + // dependency. For this, we overwrite the 'Package' field describing + // dependencies in 'KudoOperatorTaskSpec' with the operator name of the + // dependency. This has to be done for the operator to install as well as in + // all of its dependencies. + + updateKudoOperatorTaskPackageNames(dependencies, resources.OperatorVersion) + for _, dependency := range dependencies { dependency.Operator.SetNamespace(namespace) dependency.OperatorVersion.SetNamespace(namespace) - if err := installOperatorAndOperatorVersion(client, dependency); err != nil { + updateKudoOperatorTaskPackageNames(dependencies, dependency.OperatorVersion) + + if err := installOperatorAndOperatorVersion(client, dependency.Resources); err != nil { return err } } @@ -139,3 +154,25 @@ func validateParameters(instance v1beta1.Instance, parameters []v1beta1.Paramete return nil } + +// updateKudoOperatorTaskPackageNames sets the 'Package' and 'OperatorName' +// fields of the 'KudoOperatorTaskSpec' of an 'OperatorVersion' to the operator name +// initially referenced in the 'Package' field. +func updateKudoOperatorTaskPackageNames(pkgs []Dependency, operatorVersion *v1beta1.OperatorVersion) { + tasks := operatorVersion.Spec.Tasks + + for i := range tasks { + if tasks[i].Kind == engtask.KudoOperatorTaskKind { + for _, pkg := range pkgs { + if tasks[i].Spec.KudoOperatorTaskSpec.Package == pkg.PackageName { + // TODO: remove 'OperatorName' once the controller uses 'Package' + tasks[i].Spec.KudoOperatorTaskSpec.Package = pkg.Operator.Name + tasks[i].Spec.KudoOperatorTaskSpec.OperatorName = pkg.Operator.Name + break + } + } + } + } + + operatorVersion.Spec.Tasks = tasks +} diff --git a/test/integration/operator-with-dependencies/01-assert.yaml b/test/integration/operator-with-dependencies/01-assert.yaml index 7cae89075..b4bf5d762 100644 --- a/test/integration/operator-with-dependencies/01-assert.yaml +++ b/test/integration/operator-with-dependencies/01-assert.yaml @@ -1,4 +1,16 @@ apiVersion: kudo.dev/v1beta1 +kind: OperatorVersion +metadata: + name: parent-0.1.0 +spec: + tasks: + - name: deploy-child + kind: KudoOperator + spec: + package: child + operatorName: child +--- +apiVersion: kudo.dev/v1beta1 kind: Instance metadata: name: dummy-instance diff --git a/test/integration/operator-with-dependencies/parent-operator/operator.yaml b/test/integration/operator-with-dependencies/parent-operator/operator.yaml index 52469f3dc..068da09a8 100644 --- a/test/integration/operator-with-dependencies/parent-operator/operator.yaml +++ b/test/integration/operator-with-dependencies/parent-operator/operator.yaml @@ -11,7 +11,6 @@ tasks: kind: KudoOperator spec: package: "./child-operator" - operatorName: child operatorVersion: 0.0.1 plans: