Skip to content

Commit

Permalink
Set operator name of 'KudoOperator' tasks for dependencies (#1560)
Browse files Browse the repository at this point in the history
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 <jan@d2iq.com>

Co-authored-by: Aleksey Dukhovniy <adukhovniy@mesosphere.io>
  • Loading branch information
Jan Schlicht and Aleksey Dukhovniy committed Jun 18, 2020
1 parent 2b4e0dd commit 9db03a9
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 22 deletions.
46 changes: 28 additions & 18 deletions pkg/kudoctl/packages/install/dependencies.go
Expand Up @@ -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,
Expand All @@ -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()
Expand All @@ -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
}
}
Expand All @@ -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
}
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/kudoctl/packages/install/dependencies_test.go
Expand Up @@ -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)
}
})
Expand Down
39 changes: 38 additions & 1 deletion pkg/kudoctl/packages/install/package.go
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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
}
12 changes: 12 additions & 0 deletions 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
Expand Down
Expand Up @@ -11,7 +11,6 @@ tasks:
kind: KudoOperator
spec:
package: "./child-operator"
operatorName: child
operatorVersion: 0.0.1

plans:
Expand Down

0 comments on commit 9db03a9

Please sign in to comment.