mirror of https://github.com/kubernetes/kops.git
159 lines
4.6 KiB
Go
159 lines
4.6 KiB
Go
/*
|
|
Copyright 2019 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package fi
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"k8s.io/klog/v2"
|
|
"k8s.io/kops/util/pkg/reflectutils"
|
|
)
|
|
|
|
type HasDependencies[T SubContext] interface {
|
|
GetDependencies(tasks map[string]Task[T]) []Task[T]
|
|
}
|
|
|
|
type CloudupHasDependencies = HasDependencies[CloudupSubContext]
|
|
type InstallHasDependencies = HasDependencies[InstallSubContext]
|
|
type NodeupHasDependencies = HasDependencies[NodeupSubContext]
|
|
|
|
// NotADependency is a marker type to prevent FindTaskDependencies() from considering it a potential dependency.
|
|
type NotADependency[T SubContext] struct{}
|
|
|
|
type NodeupNotADependency = NotADependency[NodeupSubContext]
|
|
type CloudupNotADependency = NotADependency[CloudupSubContext]
|
|
|
|
var _ CloudupHasDependencies = &CloudupNotADependency{}
|
|
var _ NodeupHasDependencies = &NodeupNotADependency{}
|
|
|
|
func (NotADependency[T]) GetDependencies(map[string]Task[T]) []Task[T] {
|
|
return nil
|
|
}
|
|
|
|
// FindTaskDependencies returns a map from each task's key to the discovered list of dependencies
|
|
func FindTaskDependencies[T SubContext](tasks map[string]Task[T]) map[string][]string {
|
|
taskToId := make(map[interface{}]string)
|
|
for k, t := range tasks {
|
|
taskToId[t] = k
|
|
}
|
|
|
|
edges := make(map[string][]string)
|
|
|
|
for k, t := range tasks {
|
|
task := t
|
|
|
|
var dependencies []Task[T]
|
|
if hd, ok := task.(HasDependencies[T]); ok {
|
|
dependencies = hd.GetDependencies(tasks)
|
|
} else {
|
|
dependencies = reflectForDependencies(tasks, task)
|
|
}
|
|
|
|
var dependencyKeys []string
|
|
for _, dep := range dependencies {
|
|
// Skip nils, including interface nils
|
|
if dep == nil || reflect.ValueOf(dep).IsNil() {
|
|
continue
|
|
}
|
|
dependencyKey, found := taskToId[dep]
|
|
if !found {
|
|
klog.Fatalf("dependency for task %T:%q not found: %v", t, k, dep)
|
|
}
|
|
dependencyKeys = append(dependencyKeys, dependencyKey)
|
|
}
|
|
|
|
edges[k] = dependencyKeys
|
|
}
|
|
|
|
klog.V(4).Infof("Dependencies:")
|
|
for k, v := range edges {
|
|
klog.V(4).Infof("\t%s:\t%v", k, v)
|
|
}
|
|
|
|
return edges
|
|
}
|
|
|
|
func reflectForDependencies[T SubContext](tasks map[string]Task[T], task Task[T]) []Task[T] {
|
|
v := reflect.ValueOf(task).Elem()
|
|
return getDependencies(tasks, v)
|
|
}
|
|
|
|
// FindDependencies will try to infer dependencies for an arbitrary object
|
|
func FindDependencies[T SubContext](tasks map[string]Task[T], o interface{}) []Task[T] {
|
|
if hd, ok := o.(HasDependencies[T]); ok {
|
|
return hd.GetDependencies(tasks)
|
|
}
|
|
|
|
v := reflect.ValueOf(o).Elem()
|
|
return getDependencies(tasks, v)
|
|
}
|
|
|
|
func getDependencies[T SubContext](tasks map[string]Task[T], v reflect.Value) []Task[T] {
|
|
var dependencies []Task[T]
|
|
|
|
visitor := func(path *reflectutils.FieldPath, f *reflect.StructField, v reflect.Value) error {
|
|
if reflectutils.IsPrimitiveValue(v) {
|
|
return nil
|
|
}
|
|
|
|
switch v.Kind() {
|
|
case reflect.String:
|
|
return nil
|
|
|
|
case reflect.Interface, reflect.Ptr, reflect.Slice, reflect.Map:
|
|
// The recursive walk will descend into this; we can ignore here
|
|
return nil
|
|
|
|
case reflect.Struct:
|
|
if path.IsEmpty() {
|
|
// Ignore self - we are a struct, but not our own dependency!
|
|
return nil
|
|
}
|
|
|
|
// TODO: Can we / should we use a type-switch statement
|
|
intf := v.Addr().Interface()
|
|
if hd, ok := intf.(HasDependencies[T]); ok {
|
|
deps := hd.GetDependencies(tasks)
|
|
dependencies = append(dependencies, deps...)
|
|
// Add the direct dependency if it's a task as well
|
|
if dep, ok := intf.(Task[T]); ok {
|
|
dependencies = append(dependencies, dep)
|
|
}
|
|
} else if dep, ok := intf.(Task[T]); ok {
|
|
dependencies = append(dependencies, dep)
|
|
} else if _, ok := intf.(Resource); ok {
|
|
// Ignore: not a dependency, unless we explicitly implement HasDependencies (e.g. TaskDependentResource)
|
|
} else {
|
|
return fmt.Errorf("Unhandled type for %q: %T", path, v.Interface())
|
|
}
|
|
return reflectutils.SkipReflection
|
|
|
|
default:
|
|
klog.Infof("Unhandled kind for %q: %T", path, v.Interface())
|
|
return fmt.Errorf("Unhandled kind for %q: %v", path, v.Kind())
|
|
}
|
|
}
|
|
|
|
err := reflectutils.ReflectRecursive(v, visitor, &reflectutils.ReflectOptions{DeprecatedDoubleVisit: true})
|
|
if err != nil {
|
|
klog.Fatalf("unexpected error finding dependencies %v", err)
|
|
}
|
|
|
|
return dependencies
|
|
}
|