diff --git a/.github/mergify.yml b/.github/mergify.yml index d7d503f79..407f64882 100644 --- a/.github/mergify.yml +++ b/.github/mergify.yml @@ -6,7 +6,6 @@ queue_rules: - check-success~=docker-images.*tink-cli - check-success~=docker-images.*tink-server - check-success~=docker-images.*tink-worker - - check-success=validation pull_request_rules: - name: Automatic merge on approval @@ -19,7 +18,7 @@ pull_request_rules: - check-success~=docker-images.*tink-cli - check-success~=docker-images.*tink-server - check-success~=docker-images.*tink-worker - - check-success=validation + - check-success=crosscompile - label!=do-not-merge - label=ready-to-merge actions: diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c5748065c..fb94ef08f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -41,6 +41,9 @@ jobs: - name: Generate run: nix-shell --run 'make generate' + - name: e2etest + run: make e2etest-setup + - name: go test run: make test @@ -63,21 +66,12 @@ jobs: - run: make bin/gofumpt - run: PATH=$PWD/bin/:$PATH ./ci-checks.sh - validation: + crosscompile: runs-on: ubuntu-latest needs: - ci-checks - - test - - verify - steps: - - name: fake - run: echo ":+1:" - crosscompile: - runs-on: ubuntu-latest - needs: - - validation steps: - name: Checkout code uses: actions/checkout@v2 diff --git a/.gitignore b/.gitignore index 9e665553f..2bd4849d9 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ out/ .*.swp hack/tools + +# test worker files +tests/worker diff --git a/Makefile b/Makefile index 34473971c..56ea3183a 100644 --- a/Makefile +++ b/Makefile @@ -11,8 +11,8 @@ crosscompile: $(crossbinaries) ## Build all binaries for Linux and all supported images: tink-cli-image tink-server-image tink-worker-image virtual-worker-image ## Build all docker images run: crosscompile run-stack ## Builds and runs the Tink stack (tink, db, cli) via docker-compose -test: ## Run tests - go test -coverprofile=coverage.txt ./... +test: e2etest-setup ## Run tests + source <(setup-envtest use -p env) && go test -coverprofile=coverage.txt ./... verify: lint check-generated # Verify code style, is lint free, freshness ... gofumpt -s -d . diff --git a/cmd/tink-controller/main.go b/cmd/tink-controller/main.go index 77294fe67..ee1c2302c 100644 --- a/cmd/tink-controller/main.go +++ b/cmd/tink-controller/main.go @@ -13,6 +13,7 @@ import ( "github.com/tinkerbell/tink/pkg/controllers" wfctrl "github.com/tinkerbell/tink/pkg/controllers/workflow" "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" ) // version is set at build time. @@ -58,12 +59,22 @@ func NewRootCommand(config *DaemonConfig, logger log.Logger) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { logger.Info("starting controller version " + version) - config, err := clientcmd.BuildConfigFromFlags(config.K8sAPI, config.Kubeconfig) + ccfg := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + &clientcmd.ClientConfigLoadingRules{ExplicitPath: config.Kubeconfig}, + &clientcmd.ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: config.K8sAPI}}) + + cfg, err := ccfg.ClientConfig() if err != nil { return err } - manager, err := controllers.NewManager(config, controllers.GetControllerOptions()) + namespace, _, err := ccfg.Namespace() + if err != nil { + return err + } + options := controllers.GetControllerOptions() + options.LeaderElectionNamespace = namespace + manager, err := controllers.NewManager(cfg, options) if err != nil { return err } diff --git a/cmd/tink-server/main.go b/cmd/tink-server/main.go index ae89193ae..9e9065f2a 100644 --- a/cmd/tink-server/main.go +++ b/cmd/tink-server/main.go @@ -40,19 +40,35 @@ type DaemonConfig struct { CertDir string HTTPAuthority string TLS bool + Backend string + + KubeconfigPath string + KubeAPI string +} + +const ( + backendPostgres = "postgres" + backendKubernetes = "kubernetes" +) + +func backends() []string { + return []string{backendPostgres, backendKubernetes} } func (c *DaemonConfig) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&c.Facility, "facility", "deprecated", "This is temporary. It will be removed") - fs.StringVar(&c.PGDatabase, "postgres-database", "tinkerbell", "The Postgres database name") - fs.StringVar(&c.PGUSer, "postgres-user", "tinkerbell", "The Postgres database username") - fs.StringVar(&c.PGPassword, "postgres-password", "tinkerbell", "The Postgres database password") - fs.StringVar(&c.PGSSLMode, "postgres-sslmode", "disable", "Enable or disable SSL mode in postgres") - fs.BoolVar(&c.OnlyMigration, "only-migration", false, "When enabled the server applies the migration to postgres database and it exits") + fs.StringVar(&c.PGDatabase, "postgres-database", "tinkerbell", "The Postgres database name. Only takes effect if `--backend=postgres`") + fs.StringVar(&c.PGUSer, "postgres-user", "tinkerbell", "The Postgres database username. Only takes effect if `--backend=postgres`") + fs.StringVar(&c.PGPassword, "postgres-password", "tinkerbell", "The Postgres database password. Only takes effect if `--backend=postgres`") + fs.StringVar(&c.PGSSLMode, "postgres-sslmode", "disable", "Enable or disable SSL mode in postgres. Only takes effect if `--backend=postgres`") + fs.BoolVar(&c.OnlyMigration, "only-migration", false, "When enabled the server applies the migration to postgres database and it exits. Only takes effect if `--backend=postgres`") fs.StringVar(&c.GRPCAuthority, "grpc-authority", ":42113", "The address used to expose the gRPC server") fs.StringVar(&c.CertDir, "cert-dir", "", "") fs.StringVar(&c.HTTPAuthority, "http-authority", ":42114", "The address used to expose the HTTP server") fs.BoolVar(&c.TLS, "tls", true, "Run in tls protected mode (disabling should only be done for development or if behind TLS terminating proxy)") + fs.StringVar(&c.Backend, "backend", backendPostgres, fmt.Sprintf("The backend datastore to use. Must be one of %s", strings.Join(backends(), ", "))) + fs.StringVar(&c.KubeconfigPath, "kubeconfig", "", "The path to the Kubeconfig. Only takes effect if `--backend=kubernetes`") + fs.StringVar(&c.KubeAPI, "kube-api", "", "The Kubernetes API endpoint. Only takes effect if `--backend=kubernetes`") } func (c *DaemonConfig) PopulateFromLegacyEnvVar() { @@ -119,26 +135,11 @@ func NewRootCommand(config *DaemonConfig, logger log.Logger) *cobra.Command { // graceful shutdown and error management but I want to // figure this out in another PR errCh := make(chan error, 2) - - // TODO(gianarb): I moved this up because we need to be sure that both - // connection, the one used for the resources and the one used for - // listening to events and notification are coming in the same way. - // BUT we should be using the right flags - connInfo := fmt.Sprintf("dbname=%s user=%s password=%s sslmode=%s", - config.PGDatabase, - config.PGUSer, - config.PGPassword, - config.PGSSLMode, + var ( + registrar grpcserver.Registrar + grpcOpts []grpc.ServerOption + err error ) - database, err := internal.SetupPostgres(connInfo, config.OnlyMigration, logger) - if err != nil { - return err - } - if config.OnlyMigration { - return nil - } - - var grpcOpts []grpc.ServerOption if config.TLS { certDir := config.CertDir if certDir == "" { @@ -150,16 +151,46 @@ func NewRootCommand(config *DaemonConfig, logger log.Logger) *cobra.Command { } grpcOpts = append(grpcOpts, grpc.Creds(credentials.NewServerTLSFromCert(cert))) } + switch config.Backend { + case backendKubernetes: + var err error + registrar, err = server.NewKubeBackedServer(logger, config.KubeconfigPath, config.KubeAPI) + if err != nil { + return err + } + case backendPostgres: + // TODO(gianarb): I moved this up because we need to be sure that both + // connection, the one used for the resources and the one used for + // listening to events and notification are coming in the same way. + // BUT we should be using the right flags + connInfo := fmt.Sprintf("dbname=%s user=%s password=%s sslmode=%s", + config.PGDatabase, + config.PGUSer, + config.PGPassword, + config.PGSSLMode, + ) + database, err := internal.SetupPostgres(connInfo, config.OnlyMigration, logger) + if err != nil { + return err + } + if config.OnlyMigration { + return nil + } - tinkAPI, err := server.NewDBServer(logger, database) - if err != nil { - return err + registrar, err = server.NewDBServer( + logger, + database, + ) + if err != nil { + return err + } + default: + return fmt.Errorf("invalid backend: %s", config.Backend) } - // Start the gRPC server in the background addr, err := grpcserver.SetupGRPC( ctx, - tinkAPI, + registrar, config.GRPCAuthority, grpcOpts, errCh) diff --git a/cmd/tink-worker/worker/worker.go b/cmd/tink-worker/worker/worker.go index 01af0dbb9..f7452aba1 100644 --- a/cmd/tink-worker/worker/worker.go +++ b/cmd/tink-worker/worker/worker.go @@ -208,10 +208,8 @@ func (w *Worker) execute(ctx context.Context, wfID string, action *pb.WorkflowAc return st, errors.Wrap(err, "wait container") } - l.With("status", st.String()).Info("container removed") - if st == pb.State_STATE_SUCCESS { - l.With("status", st).Info("action container exited with success", st) + l.With("status", st).Info("action container exited with success") return st, nil } @@ -299,15 +297,6 @@ func (w *Worker) ProcessWorkflowActions(ctx context.Context) error { nextAction = actions.GetActionList()[wfContext.GetCurrentActionIndex()] actionIndex = int(wfContext.GetCurrentActionIndex()) } - l := l.With( - "currentWorker", wfContext.GetCurrentWorker(), - "currentTask", wfContext.GetCurrentTask(), - "currentAction", wfContext.GetCurrentAction(), - "currentActionIndex", strconv.FormatInt(wfContext.GetCurrentActionIndex(), 10), - "currentActionState", wfContext.GetCurrentActionState(), - "totalNumberOfActions", wfContext.GetTotalNumberOfActions(), - ) - l.Info("current context") if nextAction.GetWorkerId() == w.workerID { turn = true } @@ -338,12 +327,13 @@ func (w *Worker) ProcessWorkflowActions(ctx context.Context) error { os.Exit(1) } } - l.Info("starting with action") + l.Info("starting action") } for turn { action := actions.GetActionList()[actionIndex] - l := l.With("actionName", action.GetName(), + l := l.With( + "actionName", action.GetName(), "taskName", action.GetTaskName(), ) ctx := context.WithValue(ctx, loggingContextKey, &l) @@ -361,7 +351,7 @@ func (w *Worker) ProcessWorkflowActions(ctx context.Context) error { if err != nil { exitWithGrpcError(err, l) } - l.With("duration", strconv.FormatInt(actionStatus.Seconds, 10)).Info("sent action status") + l.With("status", actionStatus.ActionStatus, "duration", strconv.FormatInt(actionStatus.Seconds, 10)).Info("sent action status") } // get workflow data @@ -445,6 +435,7 @@ func (w *Worker) reportActionStatus(ctx context.Context, actionStatus *pb.Workfl "workerID", actionStatus.GetWorkerId(), "actionName", actionStatus.GetActionName(), "taskName", actionStatus.GetTaskName(), + "status", actionStatus.ActionStatus, ) var err error for r := 1; r <= w.retries; r++ { diff --git a/cmd/virtual-worker/cmd/root.go b/cmd/virtual-worker/cmd/root.go index 41d8116df..118c3edb8 100644 --- a/cmd/virtual-worker/cmd/root.go +++ b/cmd/virtual-worker/cmd/root.go @@ -60,6 +60,7 @@ func NewRootCommand(version string, logger log.Logger) *cobra.Command { logger, tinkWorker.WithMaxFileSize(maxFileSize), tinkWorker.WithRetries(retryInterval, retries), + tinkWorker.WithDataDir("./worker"), tinkWorker.WithLogCapture(captureActionLogs)) err = w.ProcessWorkflowActions(cmd.Context()) diff --git a/cmd/virtual-worker/worker/container_manager.go b/cmd/virtual-worker/worker/container_manager.go index 0360ac59c..3be3b7fa9 100644 --- a/cmd/virtual-worker/worker/container_manager.go +++ b/cmd/virtual-worker/worker/container_manager.go @@ -36,6 +36,12 @@ func (m *fakeManager) sleep() { // NewFakeContainerManager returns a fake worker.ContainerManager that will sleep for Docker API calls. func NewFakeContainerManager(l log.Logger, sleepMinimum, sleepJitter time.Duration) worker.ContainerManager { + if sleepMinimum <= 0 { + sleepMinimum = 1 + } + if sleepJitter <= 0 { + sleepJitter = 1 + } return &fakeManager{ sleepMinimum: sleepMinimum, sleepJitter: sleepJitter, diff --git a/go.mod b/go.mod index 13139bb19..314b7772d 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,7 @@ require ( github.com/docker/distribution v2.8.1+incompatible github.com/docker/docker v20.10.7+incompatible github.com/equinix-labs/otel-init-go v0.0.1 - github.com/go-logr/zapr v0.4.0 - github.com/go-openapi/strfmt v0.19.3 // indirect + github.com/go-logr/zapr v1.2.0 github.com/golang/protobuf v1.5.2 github.com/google/go-cmp v0.5.6 github.com/google/uuid v1.3.0 @@ -18,7 +17,8 @@ require ( github.com/ktr0731/evans v0.10.0 github.com/lib/pq v1.10.1 github.com/matryer/moq v0.2.3 - github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect + github.com/onsi/ginkgo/v2 v2.1.3 + github.com/onsi/gomega v1.18.1 github.com/opencontainers/image-spec v1.0.2 github.com/packethost/pkg v0.0.0-20200903155310-0433e0605550 github.com/pkg/errors v0.9.1 @@ -29,7 +29,6 @@ require ( github.com/spf13/viper v1.8.1 github.com/stretchr/testify v1.7.0 github.com/testcontainers/testcontainers-go v0.11.1 - go.mongodb.org/mongo-driver v1.1.2 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.22.0 go.uber.org/multierr v1.7.0 google.golang.org/genproto v0.0.0-20211021150943-2b146023228c @@ -37,12 +36,20 @@ require ( google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 google.golang.org/protobuf v1.27.1 gopkg.in/yaml.v2 v2.4.0 - k8s.io/apimachinery v0.22.2 - k8s.io/client-go v0.22.2 + k8s.io/apimachinery v0.23.0 + k8s.io/client-go v0.23.0 knative.dev/pkg v0.0.0-20211119170723-a99300deff34 mvdan.cc/gofumpt v0.1.1 - sigs.k8s.io/controller-runtime v0.10.1 - sigs.k8s.io/controller-tools v0.7.0 + sigs.k8s.io/controller-runtime v0.11.1 + sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20220304125252-9ee63fc65a97 + sigs.k8s.io/controller-tools v0.8.0 + sigs.k8s.io/yaml v1.3.0 +) + +require ( + github.com/go-openapi/strfmt v0.19.3 // indirect + github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect + go.mongodb.org/mongo-driver v1.1.2 // indirect ) replace github.com/stormcat24/protodep => github.com/ackintosh/protodep v0.0.0-20200728152107-abf8eb579d6c diff --git a/go.sum b/go.sum index 7ec5d8c3d..b6a527c8c 100644 --- a/go.sum +++ b/go.sum @@ -166,6 +166,7 @@ github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= @@ -437,8 +438,8 @@ github.com/esimonov/ifshort v1.0.2/go.mod h1:yZqNJUrNn20K8Q9n2CrjTKYyVEmX209Hgu+ github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs= -github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= @@ -455,12 +456,14 @@ github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVB github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= github.com/fullstorydev/grpcurl v1.6.0/go.mod h1:ZQ+ayqbKMJNhzLmbpCiurTVlaK2M/3nqZCxaQ2Ze/sM= github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -487,10 +490,11 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/zapr v0.4.0 h1:uc1uML3hRYL9/ZZPdgHS/n8Nzo+eaYL/Efxkkamf7OM= -github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= +github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/zapr v1.2.0 h1:n4JnPI1T3Qq1SFEi/F8rwLrZERp2bso19PJZDB9dayk= +github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-openapi/errors v0.19.2 h1:a2kIyV3w+OS3S97zxUndRVD46+FhGOUBDFY7nmu4CsY= github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= @@ -574,6 +578,7 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -634,6 +639,8 @@ github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b/go.mod h1:Z4GIJBJO3Wa4g github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/cel-go v0.9.0/go.mod h1:U7ayypeSkw23szu4GaQTPJGx66c20mx8JklMSxrmI1w= +github.com/google/cel-spec v0.6.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA= github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/certificate-transparency-go v1.1.1/go.mod h1:FDKqPvSXawb2ecErVRrD+nfy23RCzyl7eqVCEmlT1Zs= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -678,6 +685,7 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= @@ -845,8 +853,9 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= @@ -1024,8 +1033,9 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/moricho/tparallel v0.2.1/go.mod h1:fXEIZxG2vdfl0ZF8b42f5a78EhjjD5mX8qUplsoSU4k= github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE= @@ -1078,6 +1088,9 @@ github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvw github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -1086,10 +1099,10 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg= -github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= -github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= -github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -1484,8 +1497,9 @@ go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q= go.uber.org/goleak v0.10.0/go.mod h1:VCZuO8V8mFPlL0F5J5GK1rtHV3DrFcQ1R8ryq7FK0aI= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.1.11-0.20210813005559-691160354723 h1:sHOAIxRGBp443oHZIPB+HsUGaksVCXVQENPxwTfQdH4= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= +go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= @@ -1530,6 +1544,7 @@ golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210920023735-84f357641f63 h1:kETrAMYZq6WVGPa8IIixL0CaEcIUNi+1WX7grUoi3y8= golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1637,9 +1652,9 @@ golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210505214959-0714010a04ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 h1:VrJZAjbekhoRn7n5FBujY31gboH+iB3pdLxn3gE9FjU= golang.org/x/net v0.0.0-20211101193420-4a448f8816b3/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -1793,16 +1808,19 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210921065528-437939a70204/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 h1:2B5p2L5IfGiD7+b9BOoRMC6DgObAVZV+Fsp050NqXik= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210916214954-140adaaadfaf h1:Ihq/mm/suC88gF8WFcVwk+OV6Tq+wyA1O0E5UEvDglI= golang.org/x/term v0.0.0-20210916214954-140adaaadfaf/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1934,6 +1952,7 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM= golang.org/x/tools v0.1.6/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= @@ -2042,6 +2061,7 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201102152239-715cce707fb0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -2198,32 +2218,33 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.2.0/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.21.4/go.mod h1:fTVGP+M4D8+00FN2cMnJqk/eb/GH53bvmNs2SVTmpFk= -k8s.io/api v0.22.2 h1:M8ZzAD0V6725Fjg53fKeTJxGsJvRbk4TEm/fexHMtfw= -k8s.io/api v0.22.2/go.mod h1:y3ydYpLJAaDI+BbSe2xmGcqxiWHmWjkEeIbiwHvnPR8= +k8s.io/api v0.23.0 h1:WrL1gb73VSC8obi8cuYETJGXEoFNEh3LU0Pt+Sokgro= +k8s.io/api v0.23.0/go.mod h1:8wmDdLBHBNxtOIytwLstXt5E9PddnZb0GaMcqsvDBpg= k8s.io/apiextensions-apiserver v0.21.4/go.mod h1:OoC8LhI9LnV+wKjZkXIBbLUwtnOGJiTRE33qctH5CIk= -k8s.io/apiextensions-apiserver v0.22.2 h1:zK7qI8Ery7j2CaN23UCFaC1hj7dMiI87n01+nKuewd4= -k8s.io/apiextensions-apiserver v0.22.2/go.mod h1:2E0Ve/isxNl7tWLSUDgi6+cmwHi5fQRdwGVCxbC+KFA= +k8s.io/apiextensions-apiserver v0.23.0 h1:uii8BYmHYiT2ZTAJxmvc3X8UhNYMxl2A0z0Xq3Pm+WY= +k8s.io/apiextensions-apiserver v0.23.0/go.mod h1:xIFAEEDlAZgpVBl/1VSjGDmLoXAWRG40+GsWhKhAxY4= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.21.4/go.mod h1:H/IM+5vH9kZRNJ4l3x/fXP/5bOPJaVP/guptnZPeCFI= -k8s.io/apimachinery v0.22.2 h1:ejz6y/zNma8clPVfNDLnPbleBo6MpoFy/HBiBqCouVk= -k8s.io/apimachinery v0.22.2/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= +k8s.io/apimachinery v0.23.0 h1:mIfWRMjBuMdolAWJ3Fd+aPTMv3X9z+waiARMpvvb0HQ= +k8s.io/apimachinery v0.23.0/go.mod h1:fFCTTBKvKcwTPFzjlcxp91uPFZr+JA0FubU4fLzzFYc= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.21.4/go.mod h1:SErUuFBBPZUcD2nsUU8hItxoYheqyYr2o/pCINEPW8g= -k8s.io/apiserver v0.22.2/go.mod h1:vrpMmbyjWrgdyOvZTSpsusQq5iigKNWv9o9KlDAbBHI= +k8s.io/apiserver v0.23.0/go.mod h1:Cec35u/9zAepDPPFyT+UMrgqOCjgJ5qtfVJDxjZYmt4= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/client-go v0.21.4/go.mod h1:t0/eMKyUAq/DoQ7vW8NVVA00/nomlwC+eInsS8PxSew= -k8s.io/client-go v0.22.2 h1:DaSQgs02aCC1QcwUdkKZWOeaVsQjYvWv8ZazcZ6JcHc= -k8s.io/client-go v0.22.2/go.mod h1:sAlhrkVDf50ZHx6z4K0S40wISNTarf1r800F+RlCF6U= +k8s.io/client-go v0.23.0 h1:vcsOqyPq7XV3QmQRCBH/t9BICJM9Q1M18qahjv+rebY= +k8s.io/client-go v0.23.0/go.mod h1:hrDnpnK1mSr65lHHcUuIZIXDgEbzc7/683c6hyG4jTA= k8s.io/code-generator v0.21.4/go.mod h1:K3y0Bv9Cz2cOW2vXUrNZlFbflhuPvuadW6JdnN6gGKo= -k8s.io/code-generator v0.22.2/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o= +k8s.io/code-generator v0.23.0/go.mod h1:vQvOhDXhuzqiVfM/YHp+dmg10WDZCchJVObc9MvowsE= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.21.4/go.mod h1:ZKG0eHVX+tUDcaoIGpU3Vtk4TIjMddN9uhEWDmW6Nyg= -k8s.io/component-base v0.22.2 h1:vNIvE0AIrLhjX8drH0BgCNJcR4QZxMXcJzBsDplDx9M= -k8s.io/component-base v0.22.2/go.mod h1:5Br2QhI9OTe79p+TzPe9JKNQYvEKbq9rTJDWllunGug= +k8s.io/component-base v0.23.0 h1:UAnyzjvVZ2ZR1lF35YwtNY6VMN94WtOnArcXBu34es8= +k8s.io/component-base v0.23.0/go.mod h1:DHH5uiFvLC1edCpvcTDV++NKULdYYU6pR9Tt3HIKMKI= k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20210915205010-39e73c8a59cd/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= @@ -2231,16 +2252,17 @@ k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM= -k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw= +k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= +k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 h1:E3J9oCLlaobFUqsjG9DfKbP2BmgwBL2p7pn0A3dG9W4= +k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a h1:8dYfu/Fc9Gz2rNJKB9IQRGgQOh2clmRzNIPPY1xLY5g= -k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b h1:wxEMGetGMur3J1xuGLQY7GEQYg9bZxKn3tKo5k/eYcs= +k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= knative.dev/hack v0.0.0-20211112192837-128cf0150a69/go.mod h1:PHt8x8yX5Z9pPquBEfIj0X66f8iWkWfR0S/sarACJrI= knative.dev/pkg v0.0.0-20211119170723-a99300deff34 h1:3PTfphK/gjxA+XJIwyDZ1g1KxRaADsI/BdhFlRAtdEE= knative.dev/pkg v0.0.0-20211119170723-a99300deff34/go.mod h1:VqUp1KWJqpTDNoiSI/heaX3uMdubImslJE2tBkP+Bbw= @@ -2257,13 +2279,19 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/controller-runtime v0.10.1 h1:+eLHgY/VrJWnfg6iXUqhCUqNXgPH1NZeP9drNAAgWlg= -sigs.k8s.io/controller-runtime v0.10.1/go.mod h1:CQp8eyUQZ/Q7PJvnIrB6/hgfTC1kBkGylwsLgOQi1WY= -sigs.k8s.io/controller-tools v0.7.0 h1:iZIz1vEcavyEfxjcTLs1WH/MPf4vhPCtTKhoHqV8/G0= -sigs.k8s.io/controller-tools v0.7.0/go.mod h1:bpBAo0VcSDDLuWt47evLhMLPxRPxMDInTEH/YbdeMK0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.25/go.mod h1:Mlj9PNLmG9bZ6BHFwFKDo5afkpWyUISkb9Me0GnK66I= +sigs.k8s.io/controller-runtime v0.11.1 h1:7YIHT2QnHJArj/dk9aUkYhfqfK5cIxPOX5gPECfdZLU= +sigs.k8s.io/controller-runtime v0.11.1/go.mod h1:KKwLiTooNGu+JmLZGn9Sl3Gjmfj66eMbCQznLP5zcqA= +sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20220304125252-9ee63fc65a97 h1:ntDPPFop48UjjtWl7kGwx3FNdnP/CZAEzjZMPXR6nlE= +sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20220304125252-9ee63fc65a97/go.mod h1:nLkMD2WB4Jcix1qfVuJeOF4j5y/VfyeOIlTxG5Wj9co= +sigs.k8s.io/controller-tools v0.8.0 h1:uUkfTGEwrguqYYfcI2RRGUnC8mYdCFDqfwPKUcNJh1o= +sigs.k8s.io/controller-tools v0.8.0/go.mod h1:qE2DXhVOiEq5ijmINcFbqi9GZrrUjzB1TuJU0xa6eoY= +sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= +sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/structured-merge-diff/v4 v4.2.0 h1:kDvPBbnPk+qYmkHmSo8vKGp438IASWofnbbUKDE/bv0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.0/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= diff --git a/internal/tests/errors.go b/internal/tests/errors.go new file mode 100644 index 000000000..d9e91b064 --- /dev/null +++ b/internal/tests/errors.go @@ -0,0 +1,19 @@ +package tests + +import "testing" + +// CompareErrors is a helper function for comparing an error value and a desired error. +func CompareErrors(t *testing.T, got, want error) { + t.Helper() + if got != nil { + if want == nil { + t.Fatalf(`Got unexpected error: %v"`, got) + } else if got.Error() != want.Error() { + t.Fatalf(`Got unexpected error: got "%v" wanted "%v"`, got, want) + } + return + } + if got == nil && want != nil { + t.Fatalf("Missing expected error: %v", want) + } +} diff --git a/internal/tests/frozen_time.go b/internal/tests/frozen_time.go index 391bfa61b..f60703a85 100644 --- a/internal/tests/frozen_time.go +++ b/internal/tests/frozen_time.go @@ -3,62 +3,109 @@ package tests import ( "time" + "github.com/golang/protobuf/ptypes/timestamp" timestamppb "google.golang.org/protobuf/types/known/timestamppb" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// FrozenTime is an interface for testing out fake times with additional -// methods for protobuf and Kubernetes time formats. -type FrozenTime interface { - // Now never changes - Now() time.Time - Before(time.Duration) time.Time - After(time.Duration) time.Time +type TimeFunc func() time.Time - // Before Now() by int64 seconds - BeforeSec(int64) time.Time - // After Now() by int64 seconds - AfterSec(int64) time.Time +type MetaV1TimeFunc func() *metav1.Time - MetaV1Now() *metav1.Time - MetaV1Before(time.Duration) *metav1.Time - MetaV1After(time.Duration) *metav1.Time - MetaV1BeforeSec(int64) *metav1.Time - MetaV1AfterSec(int64) *metav1.Time +type ProtobufTimeFunc func() *timestamppb.Timestamp - PbNow() *timestamppb.Timestamp - PbBefore(time.Duration) *timestamppb.Timestamp - PbAfter(time.Duration) *timestamppb.Timestamp - PbBeforeSec(int64) *timestamppb.Timestamp - PbAfterSec(int64) *timestamppb.Timestamp +// NewFrozenTime returns a FrozenTime for a given unix second. +func NewFrozenTimeUnix(unix int64) *FrozenTime { + return &FrozenTime{t: time.Unix(unix, 0).UTC()} } -func NewFrozenTimeUnix(unix int64) FrozenTime { - return &ft{t: time.Unix(unix, 0)} +// NewFrozenTime returns a FrozenTime for a given time.Time. +func NewFrozenTime(t time.Time) *FrozenTime { + return &FrozenTime{t.UTC()} } -func NewFrozenTime(t time.Time) FrozenTime { - return &ft{t} +// FrozenTime is a type for testing out fake times. +type FrozenTime struct { + t time.Time } -type ft struct { - t time.Time +// Now never changes. +func (f *FrozenTime) Now() time.Time { return f.t } + +// Before returns a time before FrozenTime.Now() by a given duration. +func (f *FrozenTime) Before(d time.Duration) time.Time { return f.Now().Add(-d) } + +// After returns a time after FrozenTime.Now() by a given duration. +func (f *FrozenTime) After(d time.Duration) time.Time { return f.Now().Add(d) } + +// Before Now() by int64 seconds. +func (f *FrozenTime) BeforeSec(s int64) time.Time { + return f.Now().Add(time.Duration(-s) * time.Second) +} + +// After Now() by int64 seconds. +func (f *FrozenTime) AfterSec(s int64) time.Time { return f.Now().Add(time.Duration(s) * time.Second) } + +// BeforeFunc returns a TimeFunc where the return value is a time before FrozenTime.Now() by a given duration. +func (f *FrozenTime) BeforeFunc(d time.Duration) TimeFunc { + return func() time.Time { return f.Before(d) } } -func (f *ft) Now() time.Time { return f.t } -func (f *ft) Before(d time.Duration) time.Time { return f.Now().Add(d) } -func (f *ft) After(d time.Duration) time.Time { return f.Now().Add(-d) } -func (f *ft) BeforeSec(s int64) time.Time { return f.Now().Add(time.Duration(-s) * time.Second) } -func (f *ft) AfterSec(s int64) time.Time { return f.Now().Add(time.Duration(s) * time.Second) } +// AfterFunc returns a TimeFunc where the return value is a time after FrozenTime.Now() by a given duration. +func (f *FrozenTime) AfterFunc(d time.Duration) TimeFunc { + return func() time.Time { return f.After(d) } +} + +func (f *FrozenTime) MetaV1Now() *metav1.Time { t := metav1.NewTime(f.Now()); return &t } +func (f *FrozenTime) MetaV1Before(d time.Duration) *metav1.Time { + t := metav1.NewTime(f.Before(d)) + return &t +} + +func (f *FrozenTime) MetaV1After(d time.Duration) *metav1.Time { + t := metav1.NewTime(f.After(d)) + return &t +} + +func (f *FrozenTime) MetaV1BeforeSec(s int64) *metav1.Time { + t := metav1.NewTime(f.BeforeSec(s)) + return &t +} -func (f *ft) MetaV1Now() *metav1.Time { t := metav1.NewTime(f.Now()); return &t } -func (f *ft) MetaV1Before(d time.Duration) *metav1.Time { t := metav1.NewTime(f.Before(d)); return &t } -func (f *ft) MetaV1After(d time.Duration) *metav1.Time { t := metav1.NewTime(f.After(d)); return &t } -func (f *ft) MetaV1BeforeSec(s int64) *metav1.Time { t := metav1.NewTime(f.BeforeSec(s)); return &t } -func (f *ft) MetaV1AfterSec(s int64) *metav1.Time { t := metav1.NewTime(f.AfterSec(s)); return &t } +func (f *FrozenTime) MetaV1AfterSec(s int64) *metav1.Time { + t := metav1.NewTime(f.AfterSec(s)) + return &t +} -func (f *ft) PbNow() *timestamppb.Timestamp { return timestamppb.New(f.Now()) } -func (f *ft) PbBefore(d time.Duration) *timestamppb.Timestamp { return timestamppb.New(f.Before(d)) } -func (f *ft) PbAfter(d time.Duration) *timestamppb.Timestamp { return timestamppb.New(f.After(d)) } -func (f *ft) PbBeforeSec(s int64) *timestamppb.Timestamp { return timestamppb.New(f.BeforeSec(s)) } -func (f *ft) PbAfterSec(s int64) *timestamppb.Timestamp { return timestamppb.New(f.AfterSec(s)) } +func (f *FrozenTime) MetaV1BeforeFunc(d time.Duration) MetaV1TimeFunc { + return func() *metav1.Time { return f.MetaV1Before(d) } +} + +func (f *FrozenTime) MetaV1AfterFunc(d time.Duration) MetaV1TimeFunc { + return func() *metav1.Time { return f.MetaV1After(d) } +} + +func (f *FrozenTime) PbNow() *timestamppb.Timestamp { return timestamppb.New(f.Now()) } +func (f *FrozenTime) PbBefore(d time.Duration) *timestamppb.Timestamp { + return timestamppb.New(f.Before(d)) +} + +func (f *FrozenTime) PbAfter(d time.Duration) *timestamppb.Timestamp { + return timestamppb.New(f.After(d)) +} + +func (f *FrozenTime) PbBeforeSec(s int64) *timestamppb.Timestamp { + return timestamppb.New(f.BeforeSec(s)) +} + +func (f *FrozenTime) PbAfterSec(s int64) *timestamppb.Timestamp { + return timestamppb.New(f.AfterSec(s)) +} + +func (f *FrozenTime) PbBeforeFunc(d time.Duration) ProtobufTimeFunc { + return func() *timestamp.Timestamp { return f.PbBefore(d) } +} + +func (f *FrozenTime) PbAfterFunc(d time.Duration) ProtobufTimeFunc { + return func() *timestamp.Timestamp { return f.PbAfter(d) } +} diff --git a/internal/tests/frozen_time_test.go b/internal/tests/frozen_time_test.go new file mode 100644 index 000000000..156a49640 --- /dev/null +++ b/internal/tests/frozen_time_test.go @@ -0,0 +1,53 @@ +package tests + +import ( + "testing" + "time" +) + +func TestFrozenTime(t *testing.T) { + cases := []struct { + name string + beginTime int64 + timeOffsetSec int64 + now time.Time + before time.Time + after time.Time + }{ + { + name: "a new hope premier", + beginTime: 233391600, + timeOffsetSec: 7260, // 121 minuets + now: time.Unix(233391600, 0), + before: time.Unix(233384340, 0), + after: time.Unix(233398860, 0), + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + ft := NewFrozenTimeUnix(tc.beginTime) + if !tc.now.Equal(ft.Now()) { + t.Fatalf("Unexpected now: wanted %#v, got %#v", tc.now, ft.Now()) + } + if !tc.before.Equal(ft.Before(time.Duration(tc.timeOffsetSec) * time.Second)) { + t.Fatalf("Unexpected before: wanted %#v, got %#v", tc.before, ft.Before(time.Duration(tc.timeOffsetSec)*time.Second)) + } + if !tc.after.Equal(ft.After(time.Duration(tc.timeOffsetSec) * time.Second)) { + t.Fatalf("Unexpected after: wanted %#v, got %#v", tc.after, ft.After(time.Duration(tc.timeOffsetSec)*time.Second)) + } + if !tc.before.Equal(ft.BeforeSec(tc.timeOffsetSec)) { + t.Fatalf("Unexpected beforeSec: wanted %#v, got %#v", tc.before, ft.BeforeSec(tc.timeOffsetSec)) + } + if !tc.after.Equal(ft.AfterSec(tc.timeOffsetSec)) { + t.Fatalf("Unexpected afterSec: wanted %#v, got %#v", tc.after, ft.AfterSec(tc.timeOffsetSec)) + } + if !tc.before.Equal(ft.BeforeFunc(time.Duration(tc.timeOffsetSec) * time.Second)()) { + t.Fatalf("Unexpected beforeSec: wanted %#v, got %#v", tc.before, ft.BeforeFunc(time.Duration(tc.timeOffsetSec)*time.Second)()) + } + if !tc.after.Equal(ft.AfterFunc(time.Duration(tc.timeOffsetSec) * time.Second)()) { + t.Fatalf("Unexpected afterSec: wanted %#v, got %#v", tc.after, ft.AfterFunc(time.Duration(tc.timeOffsetSec)*time.Second)()) + } + }) + } +} diff --git a/pkg/apis/core/v1alpha1/workflow_methods.go b/pkg/apis/core/v1alpha1/workflow_methods.go index 899a0d596..978f406d4 100644 --- a/pkg/apis/core/v1alpha1/workflow_methods.go +++ b/pkg/apis/core/v1alpha1/workflow_methods.go @@ -1,6 +1,8 @@ package v1alpha1 -import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) const ( // WorkflowIDAnnotation is used by the controller to store the diff --git a/rules.mk b/rules.mk index 9600a8c16..a881c42da 100644 --- a/rules.mk +++ b/rules.mk @@ -93,3 +93,6 @@ check-pbfiles: pbfiles echo "Protobuf files need to be regenerated!"; git diff --no-ext-diff --exit-code --stat -- protos/*/*.pb.* ) + +e2etest-setup: $(toolsBins) + setup-envtest use diff --git a/server/kubernetes_api.go b/server/kubernetes_api.go new file mode 100644 index 000000000..4b5c0d14d --- /dev/null +++ b/server/kubernetes_api.go @@ -0,0 +1,69 @@ +package server + +import ( + "context" + "time" + + "github.com/packethost/pkg/log" + "github.com/tinkerbell/tink/pkg/controllers" + pb "github.com/tinkerbell/tink/protos/workflow" + "google.golang.org/grpc" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// NewKubeBackedServer returns a server that implements the Workflow server interface for a given kubeconfig. +func NewKubeBackedServer(logger log.Logger, kubeconfig, apiserver string) (*KubernetesBackedServer, error) { + ccfg := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig}, + &clientcmd.ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: apiserver}}) + + cfg, err := ccfg.ClientConfig() + if err != nil { + return nil, err + } + + namespace, _, err := ccfg.Namespace() + if err != nil { + return nil, err + } + + if err != nil { + return nil, err + } + return NewKubeBackedServerFromREST(logger, cfg, namespace), nil +} + +// NewKubeBackedServerFromREST returns a server that implements the Workflow +// server interface with the given Kubernetes rest client and namespace. +func NewKubeBackedServerFromREST(logger log.Logger, config *rest.Config, namespace string) *KubernetesBackedServer { + manager := controllers.NewManagerOrDie(config, controllers.GetServerOptions()) + go func() { + err := manager.Start(context.Background()) + if err != nil { + logger.Error(err, "Error starting manager") + } + }() + return &KubernetesBackedServer{ + logger: logger, + ClientFunc: manager.GetClient, + namespace: namespace, + nowFunc: time.Now, + } +} + +// KubernetesBackedServer is a server that implements a workflow API. +type KubernetesBackedServer struct { + logger log.Logger + ClientFunc func() client.Client + namespace string + + nowFunc func() time.Time +} + +// Register registers the service on the gRPC server. +func (s *KubernetesBackedServer) Register(server *grpc.Server) { + pb.RegisterWorkflowServiceServer(server, s) +} diff --git a/server/kubernetes_api_test.go b/server/kubernetes_api_test.go new file mode 100644 index 000000000..706df95fb --- /dev/null +++ b/server/kubernetes_api_test.go @@ -0,0 +1,370 @@ +package server + +import ( + "errors" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/packethost/pkg/log" + "github.com/tinkerbell/tink/internal/tests" + "github.com/tinkerbell/tink/pkg/apis/core/v1alpha1" + "github.com/tinkerbell/tink/protos/workflow" +) + +var TestTime = tests.NewFrozenTimeUnix(1637361793) + +func TestModifyWorkflowState(t *testing.T) { + cases := []struct { + name string + inputWf *v1alpha1.Workflow + inputWfContext *workflow.WorkflowContext + want *v1alpha1.Workflow + wantErr error + }{ + { + name: "no workflow", + inputWf: nil, + inputWfContext: &workflow.WorkflowContext{}, + want: nil, + wantErr: errors.New("no workflow provided"), + }, + { + name: "no context", + inputWf: &v1alpha1.Workflow{}, + inputWfContext: nil, + want: nil, + wantErr: errors.New("no workflow context provided"), + }, + { + name: "no task", + inputWf: &v1alpha1.Workflow{ + Status: v1alpha1.WorkflowStatus{ + State: "STATE_PENDING", + GlobalTimeout: 600, + Tasks: []v1alpha1.Task{ + { + Name: "provision", + WorkerAddr: "machine-mac-1", + Actions: []v1alpha1.Action{ + { + Name: "stream", + Image: "quay.io/tinkerbell-actions/image2disk:v1.0.0", + Timeout: 300, + Status: "STATE_PENDING", + }, + }, + }, + }, + }, + }, + inputWfContext: &workflow.WorkflowContext{ + WorkflowId: "debian", + CurrentWorker: "machine-mac-1", + CurrentTask: "power-on", + CurrentAction: "power-on-bmc", + CurrentActionIndex: 0, + CurrentActionState: workflow.State_STATE_RUNNING, + TotalNumberOfActions: 1, + }, + want: nil, + wantErr: errors.New("task not found"), + }, + { + name: "no action found", + inputWf: &v1alpha1.Workflow{ + Status: v1alpha1.WorkflowStatus{ + State: "STATE_PENDING", + GlobalTimeout: 600, + Tasks: []v1alpha1.Task{ + { + Name: "provision", + WorkerAddr: "machine-mac-1", + Actions: []v1alpha1.Action{ + { + Name: "stream", + Image: "quay.io/tinkerbell-actions/image2disk:v1.0.0", + Timeout: 300, + Status: "STATE_PENDING", + }, + }, + }, + }, + }, + }, + inputWfContext: &workflow.WorkflowContext{ + CurrentWorker: "machine-mac-1", + CurrentTask: "provision", + CurrentAction: "power-on-bmc", + CurrentActionIndex: 0, + CurrentActionState: workflow.State_STATE_RUNNING, + TotalNumberOfActions: 1, + }, + want: nil, + wantErr: errors.New("action not found"), + }, + { + name: "running task", + inputWf: &v1alpha1.Workflow{ + Status: v1alpha1.WorkflowStatus{ + State: "STATE_PENDING", + GlobalTimeout: 600, + Tasks: []v1alpha1.Task{ + { + Name: "provision", + WorkerAddr: "machine-mac-1", + Actions: []v1alpha1.Action{ + { + Name: "stream", + Image: "quay.io/tinkerbell-actions/image2disk:v1.0.0", + Timeout: 300, + Status: "STATE_PENDING", + }, + }, + }, + }, + }, + }, + inputWfContext: &workflow.WorkflowContext{ + CurrentWorker: "machine-mac-1", + CurrentTask: "provision", + CurrentAction: "stream", + CurrentActionIndex: 0, + CurrentActionState: workflow.State_STATE_RUNNING, + TotalNumberOfActions: 1, + }, + want: &v1alpha1.Workflow{ + Status: v1alpha1.WorkflowStatus{ + State: "STATE_RUNNING", + GlobalTimeout: 600, + Tasks: []v1alpha1.Task{ + { + Name: "provision", + WorkerAddr: "machine-mac-1", + Actions: []v1alpha1.Action{ + { + Name: "stream", + Image: "quay.io/tinkerbell-actions/image2disk:v1.0.0", + Timeout: 300, + Status: "STATE_RUNNING", + StartedAt: TestTime.MetaV1Now(), + }, + }, + }, + }, + }, + }, + wantErr: nil, + }, + { + name: "timed out task", + inputWf: &v1alpha1.Workflow{ + Status: v1alpha1.WorkflowStatus{ + State: "STATE_RUNNING", + GlobalTimeout: 600, + Tasks: []v1alpha1.Task{ + { + Name: "provision", + WorkerAddr: "machine-mac-1", + Actions: []v1alpha1.Action{ + { + Name: "stream", + Image: "quay.io/tinkerbell-actions/image2disk:v1.0.0", + Timeout: 300, + Status: "STATE_RUNNING", + StartedAt: TestTime.MetaV1Before(time.Second * 301), + }, + }, + }, + }, + }, + }, + inputWfContext: &workflow.WorkflowContext{ + CurrentWorker: "machine-mac-1", + CurrentTask: "provision", + CurrentAction: "stream", + CurrentActionIndex: 0, + CurrentActionState: workflow.State_STATE_TIMEOUT, + TotalNumberOfActions: 1, + }, + want: &v1alpha1.Workflow{ + Status: v1alpha1.WorkflowStatus{ + State: "STATE_TIMEOUT", + GlobalTimeout: 600, + Tasks: []v1alpha1.Task{ + { + Name: "provision", + WorkerAddr: "machine-mac-1", + Actions: []v1alpha1.Action{ + { + Name: "stream", + Image: "quay.io/tinkerbell-actions/image2disk:v1.0.0", + Timeout: 300, + Status: "STATE_TIMEOUT", + StartedAt: TestTime.MetaV1Before(time.Second * 301), + Seconds: 301, + }, + }, + }, + }, + }, + }, + wantErr: nil, + }, + { + name: "successful task", + inputWf: &v1alpha1.Workflow{ + Status: v1alpha1.WorkflowStatus{ + State: "STATE_RUNNING", + GlobalTimeout: 600, + Tasks: []v1alpha1.Task{ + { + Name: "provision", + WorkerAddr: "machine-mac-1", + Actions: []v1alpha1.Action{ + { + Name: "stream", + Image: "quay.io/tinkerbell-actions/image2disk:v1.0.0", + Timeout: 300, + Status: "STATE_RUNNING", + StartedAt: TestTime.MetaV1Before(time.Second * 30), + }, + { + Name: "kexec", + Image: "quay.io/tinkerbell-actions/kexec:v1.0.0", + Timeout: 5, + Status: "STATE_PENDING", + }, + }, + }, + }, + }, + }, + inputWfContext: &workflow.WorkflowContext{ + CurrentWorker: "machine-mac-1", + CurrentTask: "provision", + CurrentAction: "stream", + CurrentActionIndex: 0, + CurrentActionState: workflow.State_STATE_SUCCESS, + TotalNumberOfActions: 2, + }, + want: &v1alpha1.Workflow{ + Status: v1alpha1.WorkflowStatus{ + State: "STATE_RUNNING", + GlobalTimeout: 600, + Tasks: []v1alpha1.Task{ + { + Name: "provision", + WorkerAddr: "machine-mac-1", + Actions: []v1alpha1.Action{ + { + Name: "stream", + Image: "quay.io/tinkerbell-actions/image2disk:v1.0.0", + Timeout: 300, + Status: "STATE_SUCCESS", + StartedAt: TestTime.MetaV1Before(time.Second * 30), + Seconds: 30, + }, + { + Name: "kexec", + Image: "quay.io/tinkerbell-actions/kexec:v1.0.0", + Timeout: 5, + Status: "STATE_PENDING", + }, + }, + }, + }, + }, + }, + wantErr: nil, + }, + { + name: "successful last task", + inputWf: &v1alpha1.Workflow{ + Status: v1alpha1.WorkflowStatus{ + State: "STATE_RUNNING", + GlobalTimeout: 600, + Tasks: []v1alpha1.Task{ + { + Name: "provision", + WorkerAddr: "machine-mac-1", + Actions: []v1alpha1.Action{ + { + Name: "stream", + Image: "quay.io/tinkerbell-actions/image2disk:v1.0.0", + Timeout: 300, + Status: "STATE_SUCCESS", + StartedAt: TestTime.MetaV1Before(time.Second * 30), + Seconds: 27, + }, + { + Name: "kexec", + Image: "quay.io/tinkerbell-actions/kexec:v1.0.0", + Timeout: 5, + Status: "STATE_RUNNING", + }, + }, + }, + }, + }, + }, + inputWfContext: &workflow.WorkflowContext{ + CurrentWorker: "machine-mac-1", + CurrentTask: "provision", + CurrentAction: "kexec", + CurrentActionIndex: 1, + CurrentActionState: workflow.State_STATE_SUCCESS, + TotalNumberOfActions: 2, + }, + want: &v1alpha1.Workflow{ + Status: v1alpha1.WorkflowStatus{ + State: "STATE_SUCCESS", + GlobalTimeout: 600, + Tasks: []v1alpha1.Task{ + { + Name: "provision", + WorkerAddr: "machine-mac-1", + Actions: []v1alpha1.Action{ + { + Name: "stream", + Image: "quay.io/tinkerbell-actions/image2disk:v1.0.0", + Timeout: 300, + Status: "STATE_SUCCESS", + StartedAt: TestTime.MetaV1Before(time.Second * 30), + Seconds: 27, + }, + { + Name: "kexec", + Image: "quay.io/tinkerbell-actions/kexec:v1.0.0", + Timeout: 5, + Status: "STATE_SUCCESS", + }, + }, + }, + }, + }, + }, + wantErr: nil, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + server := &KubernetesBackedServer{ + logger: log.Test(t, "TestModifyWorkflowState"), + ClientFunc: nil, + namespace: "default", + nowFunc: TestTime.Now, + } + gotErr := server.modifyWorkflowState(tc.inputWf, tc.inputWfContext) + tests.CompareErrors(t, gotErr, tc.wantErr) + if tc.want == nil { + return + } + + if diff := cmp.Diff(tc.inputWf, tc.want); diff != "" { + t.Errorf("unexpected difference:\n%v", diff) + } + }) + } +} diff --git a/server/kubernetes_api_workflow.go b/server/kubernetes_api_workflow.go new file mode 100644 index 000000000..7978b0e8a --- /dev/null +++ b/server/kubernetes_api_workflow.go @@ -0,0 +1,218 @@ +package server + +import ( + "context" + + "github.com/pkg/errors" + "github.com/tinkerbell/tink/pkg/apis/core/v1alpha1" + "github.com/tinkerbell/tink/pkg/controllers" + "github.com/tinkerbell/tink/pkg/convert" + pb "github.com/tinkerbell/tink/protos/workflow" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func getWorkflowContext(wf v1alpha1.Workflow) *pb.WorkflowContext { + return &pb.WorkflowContext{ + WorkflowId: wf.Name, + CurrentWorker: wf.GetCurrentWorker(), + CurrentTask: wf.GetCurrentTask(), + CurrentAction: wf.GetCurrentAction(), + CurrentActionIndex: int64(wf.GetCurrentActionIndex()), + CurrentActionState: pb.State(pb.State_value[string(wf.GetCurrentActionState())]), + TotalNumberOfActions: int64(wf.GetTotalNumberOfActions()), + } +} + +func (s *KubernetesBackedServer) getCurrentAssignedNonTerminalWorkflowsForWorker(ctx context.Context, workerID string) ([]v1alpha1.Workflow, error) { + stored := &v1alpha1.WorkflowList{} + err := s.ClientFunc().List(ctx, stored, &client.MatchingFields{ + controllers.WorkflowWorkerNonTerminalStateIndex: workerID, + }) + if err != nil { + return nil, err + } + wfs := []v1alpha1.Workflow{} + for _, wf := range stored.Items { + // If the current assigned or running action is assigned to the requested worker, include it + if wf.Status.Tasks[wf.GetCurrentTaskIndex()].WorkerAddr == workerID { + wfs = append(wfs, wf) + } + } + return wfs, nil +} + +func (s *KubernetesBackedServer) getWorkflowByName(ctx context.Context, workflowID, namespace string) (*v1alpha1.Workflow, error) { + workflow := &v1alpha1.Workflow{} + err := s.ClientFunc().Get(ctx, types.NamespacedName{Name: workflowID, Namespace: namespace}, workflow) + if err != nil { + s.logger.With("workflow", workflowID).Error(err) + return nil, err + } + return workflow, nil +} + +// The following APIs are used by the worker. + +func (s *KubernetesBackedServer) GetWorkflowContexts(req *pb.WorkflowContextRequest, stream pb.WorkflowService_GetWorkflowContextsServer) error { + if req.GetWorkerId() == "" { + return status.Errorf(codes.InvalidArgument, errInvalidWorkflowID) + } + wflows, err := s.getCurrentAssignedNonTerminalWorkflowsForWorker(stream.Context(), req.WorkerId) + if err != nil { + return err + } + for _, wf := range wflows { + if err := stream.Send(getWorkflowContext(wf)); err != nil { + return err + } + } + return nil +} + +func (s *KubernetesBackedServer) GetWorkflowActions(ctx context.Context, req *pb.WorkflowActionsRequest) (*pb.WorkflowActionList, error) { + wfID := req.GetWorkflowId() + if wfID == "" { + return nil, status.Errorf(codes.InvalidArgument, errInvalidWorkflowID) + } + wf, err := s.getWorkflowByName(ctx, wfID, s.namespace) + if err != nil { + return nil, err + } + return convert.WorkflowActionListCRDToProto(wf), nil +} + +// Modifies a workflow for a given workflowContext. +func (s *KubernetesBackedServer) modifyWorkflowState(wf *v1alpha1.Workflow, wfContext *pb.WorkflowContext) error { + if wf == nil { + return errors.New("no workflow provided") + } + if wfContext == nil { + return errors.New("no workflow context provided") + } + var ( + taskIndex = -1 + actionIndex = -1 + ) + + for ti, task := range wf.Status.Tasks { + if wfContext.CurrentTask == task.Name { + taskIndex = ti + for ai, action := range task.Actions { + if action.Name == wfContext.CurrentAction && wfContext.CurrentActionIndex == int64(ai) { + actionIndex = ai + goto cont + } + } + } + } +cont: + + if taskIndex < 0 { + return errors.New("task not found") + } + if actionIndex < 0 { + return errors.New("action not found") + } + wf.Status.Tasks[taskIndex].Actions[actionIndex].Status = v1alpha1.WorkflowState(pb.State_name[int32(wfContext.CurrentActionState)]) + + switch wfContext.CurrentActionState { + case pb.State_STATE_RUNNING: + // Workflow is running, so set the start time to now + wf.Status.State = v1alpha1.WorkflowState(pb.State_name[int32(wfContext.CurrentActionState)]) + wf.Status.Tasks[taskIndex].Actions[actionIndex].StartedAt = func() *metav1.Time { + t := metav1.NewTime(s.nowFunc()) + return &t + }() + case pb.State_STATE_FAILED: + case pb.State_STATE_TIMEOUT: + // Handle terminal statuses by updating the workflow state and time + wf.Status.State = v1alpha1.WorkflowState(pb.State_name[int32(wfContext.CurrentActionState)]) + if wf.Status.Tasks[taskIndex].Actions[actionIndex].StartedAt != nil { + wf.Status.Tasks[taskIndex].Actions[actionIndex].Seconds = int64(s.nowFunc().Sub(wf.Status.Tasks[taskIndex].Actions[actionIndex].StartedAt.Time).Seconds()) + } + case pb.State_STATE_SUCCESS: + // Handle a success by marking the task as complete + if wf.Status.Tasks[taskIndex].Actions[actionIndex].StartedAt != nil { + wf.Status.Tasks[taskIndex].Actions[actionIndex].Seconds = int64(s.nowFunc().Sub(wf.Status.Tasks[taskIndex].Actions[actionIndex].StartedAt.Time).Seconds()) + } + // Mark success on last action success + if wfContext.CurrentActionIndex+1 == wfContext.TotalNumberOfActions { + wf.Status.State = v1alpha1.WorkflowState(pb.State_name[int32(wfContext.CurrentActionState)]) + } + case pb.State_STATE_PENDING: + // This is probably a client bug? + return errors.New("no update requested") + } + return nil +} + +func validateActionStatusRequest(req *pb.WorkflowActionStatus) error { + if req.GetWorkflowId() == "" { + return status.Errorf(codes.InvalidArgument, errInvalidWorkflowID) + } + if req.GetTaskName() == "" { + return status.Errorf(codes.InvalidArgument, errInvalidTaskName) + } + if req.GetActionName() == "" { + return status.Errorf(codes.InvalidArgument, errInvalidActionName) + } + return nil +} + +func getWorkflowContextForRequest(req *pb.WorkflowActionStatus, wf *v1alpha1.Workflow) *pb.WorkflowContext { + wfContext := getWorkflowContext(*wf) + wfContext.CurrentWorker = req.GetWorkerId() + wfContext.CurrentTask = req.GetTaskName() + wfContext.CurrentActionState = req.GetActionStatus() + wfContext.CurrentActionIndex = int64(wf.GetCurrentActionIndex()) + return wfContext +} + +func (s *KubernetesBackedServer) ReportActionStatus(ctx context.Context, req *pb.WorkflowActionStatus) (*pb.Empty, error) { + err := validateActionStatusRequest(req) + if err != nil { + return nil, err + } + wfID := req.GetWorkflowId() + l := s.logger.With("actionName", req.GetActionName(), "status", req.GetActionStatus(), "workflowID", req.GetWorkflowId(), "taskName", req.GetTaskName(), "worker", req.WorkerId) + + wf, err := s.getWorkflowByName(ctx, wfID, s.namespace) + if err != nil { + l.Error(err) + return nil, status.Errorf(codes.InvalidArgument, errInvalidWorkflowID) + } + if req.GetTaskName() != wf.GetCurrentTask() { + return nil, status.Errorf(codes.InvalidArgument, errInvalidTaskReported) + } + if req.GetActionName() != wf.GetCurrentAction() { + return nil, status.Errorf(codes.InvalidArgument, errInvalidActionReported) + } + + wfContext := getWorkflowContextForRequest(req, wf) + err = s.modifyWorkflowState(wf, wfContext) + if err != nil { + l.Error(err) + return nil, status.Errorf(codes.InvalidArgument, errInvalidWorkflowID) + } + l.Info("updating workflow in Kubernetes") + err = s.ClientFunc().Status().Update(ctx, wf) + if err != nil { + l.Error(err) + return nil, status.Errorf(codes.InvalidArgument, errInvalidWorkflowID) + } + return &pb.Empty{}, nil +} + +// GetWorkflowData is deprecated, responding with empty values until it is removed. +func (s *KubernetesBackedServer) GetWorkflowData(_ context.Context, _ *pb.GetWorkflowDataRequest) (*pb.GetWorkflowDataResponse, error) { + return &pb.GetWorkflowDataResponse{Data: []byte("")}, nil +} + +// UpdateWorkflowData is deprecated, responding with empty values until it is removed. +func (s *KubernetesBackedServer) UpdateWorkflowData(_ context.Context, _ *pb.UpdateWorkflowDataRequest) (*pb.Empty, error) { + return &pb.Empty{}, nil +} diff --git a/server/kubernetes_unimplemented.go b/server/kubernetes_unimplemented.go new file mode 100644 index 000000000..861a1a9e7 --- /dev/null +++ b/server/kubernetes_unimplemented.go @@ -0,0 +1,55 @@ +package server + +import ( + "context" + + "github.com/pkg/errors" + pb "github.com/tinkerbell/tink/protos/workflow" +) + +var errNotImplemented = errors.New("not implemented") + +// CreateWorkflow will return a not implemented error. +func (s *KubernetesBackedServer) CreateWorkflow(context.Context, *pb.CreateRequest) (*pb.CreateResponse, error) { + return nil, errNotImplemented +} + +// GetWorkflow will return a not implemented error. +func (s *KubernetesBackedServer) GetWorkflow(context.Context, *pb.GetRequest) (*pb.Workflow, error) { + return nil, errNotImplemented +} + +// DeleteWorkflow will return a not implemented error. +func (s *KubernetesBackedServer) DeleteWorkflow(context.Context, *pb.GetRequest) (*pb.Empty, error) { + return nil, errNotImplemented +} + +// ListWOrkflows will return a not implemented error. +func (s *KubernetesBackedServer) ListWorkflows(*pb.Empty, pb.WorkflowService_ListWorkflowsServer) error { + return errNotImplemented +} + +// ShowWorkflowEvents will return a not implemented error. +func (s *KubernetesBackedServer) ShowWorkflowEvents(*pb.GetRequest, pb.WorkflowService_ShowWorkflowEventsServer) error { + return errNotImplemented +} + +// GetWorkflowContext will return a not implemented error. +func (s *KubernetesBackedServer) GetWorkflowContext(context.Context, *pb.GetRequest) (*pb.WorkflowContext, error) { + return nil, errNotImplemented +} + +// GetWorkflowContextList will return a not implemented error. +func (s *KubernetesBackedServer) GetWorkflowContextList(context.Context, *pb.WorkflowContextRequest) (*pb.WorkflowContextList, error) { + return nil, errNotImplemented +} + +// GetWorkflowMetadata will return a not implemented error. +func (s *KubernetesBackedServer) GetWorkflowMetadata(context.Context, *pb.GetWorkflowDataRequest) (*pb.GetWorkflowDataResponse, error) { + return nil, errNotImplemented +} + +// GetWorkflowDataVersion will return a not implemented error. +func (s *KubernetesBackedServer) GetWorkflowDataVersion(context.Context, *pb.GetWorkflowDataRequest) (*pb.GetWorkflowDataResponse, error) { + return nil, errNotImplemented +} diff --git a/tests/e2e_test.go b/tests/e2e_test.go new file mode 100644 index 000000000..9594430df --- /dev/null +++ b/tests/e2e_test.go @@ -0,0 +1,204 @@ +package tests_test + +import ( + "context" + "fmt" + "io/ioutil" + "path/filepath" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/tinkerbell/tink/client" + "github.com/tinkerbell/tink/cmd/tink-worker/worker" + virtWorker "github.com/tinkerbell/tink/cmd/virtual-worker/worker" + "github.com/tinkerbell/tink/pkg/apis/core/v1alpha1" + pb "github.com/tinkerbell/tink/protos/workflow" + "google.golang.org/protobuf/proto" + + "k8s.io/apimachinery/pkg/types" + + "sigs.k8s.io/yaml" +) + +func parseFile(filename string, obj interface{}) error { + data, err := ioutil.ReadFile(filename) + if err != nil { + return err + } + return yaml.Unmarshal(data, obj) +} + +func createHardware(filename string) *v1alpha1.Hardware { + ctx := context.Background() + obj := &v1alpha1.Hardware{} + err := parseFile(filename, obj) + Expect(err).NotTo(HaveOccurred()) + err = k8sClient.Create(ctx, obj) + Expect(err).NotTo(HaveOccurred()) + return obj +} + +func createTemplate(filename string) *v1alpha1.Template { + ctx := context.Background() + obj := &v1alpha1.Template{} + err := parseFile(filename, obj) + Expect(err).NotTo(HaveOccurred()) + err = k8sClient.Create(ctx, obj) + Expect(err).NotTo(HaveOccurred()) + return obj +} + +func createWorkflow(filename string) *v1alpha1.Workflow { + ctx := context.Background() + obj := &v1alpha1.Workflow{} + err := parseFile(filename, obj) + Expect(err).NotTo(HaveOccurred()) + err = k8sClient.Create(ctx, obj) + Expect(err).NotTo(HaveOccurred()) + return obj +} + +var _ = Describe("Tink API", func() { + Context("When a workflow is created", func() { + It("01 - should complete a workflow", func() { + ctx := context.Background() + By("creating a hardware") + hardware := createHardware(filepath.Join("./testdata/01", "hardware.yaml")) + defer k8sClient.Delete(ctx, hardware) + By("Creating a template") + template := createTemplate(filepath.Join("./testdata/01", "template.yaml")) + defer k8sClient.Delete(ctx, template) + By("Creating a workflow") + workflow := createWorkflow(filepath.Join("./testdata/01", "workflow.yaml")) + defer k8sClient.Delete(ctx, workflow) + + By("Wait for the controller to update the workflow") + timeout := time.Second * 2 + interval := time.Millisecond * 200 + Eventually(func() (string, error) { + err := k8sClient.Get(ctx, types.NamespacedName{Namespace: workflow.Namespace, Name: workflow.Name}, workflow) + if err != nil { + return "", err + } + return string(workflow.Status.State), nil + }, timeout, interval).Should(Equal("STATE_PENDING")) + + By("Running a virtual worker") + conn, err := client.NewClientConn(serverAddr, false) + Expect(err).NotTo(HaveOccurred()) + rClient := pb.NewWorkflowServiceClient(conn) + + containerManager := virtWorker.NewFakeContainerManager(logger, time.Millisecond*100, time.Millisecond*200) + logCapturer := virtWorker.NewEmptyLogCapturer() + workerID := hardware.Spec.Interfaces[0].DHCP.MAC + w := worker.NewWorker( + workerID, + rClient, + containerManager, + logCapturer, + logger, + worker.WithDataDir("./worker"), + worker.WithMaxFileSize(1<<10), + worker.WithRetries(time.Millisecond*500, 3)) + logger.With("workerID", workerID).Info("Created worker") + + errChan := make(chan error) + workerCtx, cancel := context.WithTimeout(ctx, time.Second*8) + defer cancel() + go func(errChan chan error) { + err := w.ProcessWorkflowActions(workerCtx) + errChan <- err + }(errChan) + Eventually(func() (string, error) { + err := k8sClient.Get(ctx, types.NamespacedName{Namespace: workflow.Namespace, Name: workflow.Name}, workflow) + if err != nil { + return "", err + } + return string(workflow.Status.State), nil + }, 8*time.Second, 1*time.Second).Should(Equal("STATE_SUCCESS")) + + workerErr := <-errChan + Expect(workerErr.Error()).To(Equal("failed to get workflow context: rpc error: code = DeadlineExceeded desc = context deadline exceeded")) + }) + + It("02 - should return the correct workflow contexts", func() { + By("creating hardware") + hardware := createHardware(filepath.Join("./testdata/02", "hardware1.yaml")) + defer k8sClient.Delete(ctx, hardware) + + By("Creating templates") + template1 := createTemplate(filepath.Join("./testdata/02", "template1.yaml")) + defer k8sClient.Delete(ctx, template1) + template2 := createTemplate(filepath.Join("./testdata/02", "template2.yaml")) + defer k8sClient.Delete(ctx, template2) + template3 := createTemplate(filepath.Join("./testdata/02", "template3.yaml")) + defer k8sClient.Delete(ctx, template3) + + By("Creating workflows") + workflow1 := createWorkflow(filepath.Join("./testdata/02", "workflow1.yaml")) + defer k8sClient.Delete(ctx, workflow1) + workflow2 := createWorkflow(filepath.Join("./testdata/02", "workflow2.yaml")) + defer k8sClient.Delete(ctx, workflow2) + workflow3 := createWorkflow(filepath.Join("./testdata/02", "workflow3.yaml")) + defer k8sClient.Delete(ctx, workflow3) + + By("Wait for the controller to update a workflow") + timeout := time.Second * 2 + interval := time.Millisecond * 200 + Eventually(func() (string, error) { + err := k8sClient.Get(ctx, types.NamespacedName{Namespace: workflow3.Namespace, Name: workflow3.Name}, workflow3) + if err != nil { + return "", err + } + return string(workflow3.Status.State), nil + }, timeout, interval).Should(Equal("STATE_PENDING")) + + By("Getting Workflow Contexts") + conn, err := client.NewClientConn(serverAddr, false) + Expect(err).NotTo(HaveOccurred()) + rClient := pb.NewWorkflowServiceClient(conn) + workerID := hardware.Spec.Interfaces[0].DHCP.MAC + res, err := rClient.GetWorkflowContexts(ctx, &pb.WorkflowContextRequest{WorkerId: workerID}) + Expect(err).NotTo(HaveOccurred()) + + // expected workflow name to context mapping + expectedWorkflows := map[string]*pb.WorkflowContext{ + "wf1": { + WorkflowId: "wf1", + CurrentWorker: "3c:ec:ef:4c:4f:54", + CurrentTask: "os-installation", + CurrentAction: "stream-image", + CurrentActionIndex: 0, + CurrentActionState: pb.State_STATE_PENDING, + TotalNumberOfActions: 3, + }, + "wf3": { + WorkflowId: "wf3", + CurrentWorker: "3c:ec:ef:4c:4f:54", + CurrentTask: "task-1", + CurrentAction: "task-1-action-1", + CurrentActionIndex: 0, + CurrentActionState: pb.State_STATE_PENDING, + TotalNumberOfActions: 2, + }, + } + + for got, err := res.Recv(); err == nil && got != nil; got, err = res.Recv() { + want, ok := expectedWorkflows[got.WorkflowId] + Expect(ok).To(BeTrue(), fmt.Sprintf("Didn't find expected context for %s", got.WorkflowId)) + if !ok { + continue + } + if !proto.Equal(want, got) { + fmt.Printf("Expected:\n\t%#v\nGot:\n\t%#v", want, got) + } + Expect(proto.Equal(want, got)).To(Equal(true), fmt.Sprintf("Didn't find expected context for %s", got.WorkflowId)) + + // Remove the key from the map + delete(expectedWorkflows, got.WorkflowId) + } + Expect(expectedWorkflows).To(BeEmpty(), "All expected workflows should be found") + }) + }) +}) diff --git a/tests/testdata/01/hardware.yaml b/tests/testdata/01/hardware.yaml new file mode 100644 index 000000000..296cfe5aa --- /dev/null +++ b/tests/testdata/01/hardware.yaml @@ -0,0 +1,44 @@ +apiVersion: "tinkerbell.org/v1alpha1" +kind: Hardware +metadata: + name: "sm01" + namespace: default +spec: + metadata: + facility: + facility_code: onprem + manufacturer: + slug: supermicro + instance: + userdata: "" + hostname: "sm01" + id: "3c:ec:ef:4c:4f:54" + operating_system: + distro: "ubuntu" + os_slug: "ubuntu_20_04" + version: "20.04" + storage: + disks: + - device: /dev/nvme0n1 + partitions: + - label: ROOT + number: 1 + size: 0 + wipe_table: true + interfaces: + - dhcp: + arch: x86_64 + hostname: sm01 + ip: + address: 172.16.10.100 + gateway: 172.16.10.1 + netmask: 255.255.255.0 + lease_time: 86400 + mac: 3c:ec:ef:4c:4f:54 + name_servers: + - 172.16.10.1 + - 10.1.1.11 + uefi: true + netboot: + allowPXE: true + allowWorkflow: true diff --git a/tests/testdata/01/template.yaml b/tests/testdata/01/template.yaml new file mode 100644 index 000000000..6906f325f --- /dev/null +++ b/tests/testdata/01/template.yaml @@ -0,0 +1,74 @@ +apiVersion: "tinkerbell.org/v1alpha1" +kind: Template +metadata: + name: debian + namespace: default +spec: + data: | + version: "0.1" + name: debian + global_timeout: 1800 + tasks: + - name: "os-installation" + worker: "{{.device_1}}" + volumes: + - /dev:/dev + - /dev/console:/dev/console + - /lib/firmware:/lib/firmware:ro + actions: + - name: "stream-debian-image" + image: quay.io/tinkerbell-actions/image2disk:v1.0.0 + timeout: 600 + environment: + DEST_DISK: /dev/nvme0n1 + # Hegel IP + IMG_URL: "http://10.1.1.11:8080/debian-10-openstack-amd64.raw.gz" + COMPRESSED: true + - name: "add-tink-cloud-init-config" + image: writefile:v1.0.0 + timeout: 90 + environment: + DEST_DISK: /dev/nvme0n1p1 + FS_TYPE: ext4 + DEST_PATH: /etc/cloud/cloud.cfg.d/10_tinkerbell.cfg + UID: 0 + GID: 0 + MODE: 0600 + DIRMODE: 0700 + CONTENTS: | + datasource: + Ec2: + # Hegel IP + #metadata_urls: ["http://10.1.1.11:50061"] + strict_id: false + system_info: + default_user: + name: tink + groups: [wheel, adm, sudo] + sudo: ["ALL=(ALL) NOPASSWD:ALL"] + shell: /bin/bash + users: + - name: tink + sudo: ["ALL=(ALL) NOPASSWD:ALL"] + warnings: + dsid_missing_source: off + - name: "add-tink-cloud-init-ds-config" + image: writefile:v1.0.0 + timeout: 90 + environment: + DEST_DISK: /dev/nvme0n1p1 + FS_TYPE: ext4 + DEST_PATH: /etc/cloud/ds-identify.cfg + UID: 0 + GID: 0 + MODE: 0600 + DIRMODE: 0700 + CONTENTS: | + datasource: Ec2 + - name: "kexec-debian" + image: quay.io/tinkerbell-actions/kexec:v1.0.1 + timeout: 90 + pid: host + environment: + BLOCK_DEVICE: /dev/nvme0n1p1 + FS_TYPE: ext4 diff --git a/tests/testdata/01/workflow.yaml b/tests/testdata/01/workflow.yaml new file mode 100644 index 000000000..4a7d0c289 --- /dev/null +++ b/tests/testdata/01/workflow.yaml @@ -0,0 +1,9 @@ +apiVersion: "tinkerbell.org/v1alpha1" +kind: Workflow +metadata: + name: wf1 + namespace: default +spec: + templateRef: debian + hardwareMap: + device_1: 3c:ec:ef:4c:4f:54 diff --git a/tests/testdata/02/hardware1.yaml b/tests/testdata/02/hardware1.yaml new file mode 100644 index 000000000..58aeedc37 --- /dev/null +++ b/tests/testdata/02/hardware1.yaml @@ -0,0 +1,44 @@ +apiVersion: "tinkerbell.org/v1alpha1" +kind: Hardware +metadata: + name: "hardware1" + namespace: default +spec: + metadata: + facility: + facility_code: onprem + manufacturer: + slug: supermicro + instance: + userdata: "" + hostname: "sm01" + id: "3c:ec:ef:4c:4f:54" + operating_system: + distro: "ubuntu" + os_slug: "ubuntu_20_04" + version: "20.04" + storage: + disks: + - device: /dev/nvme0n1 + partitions: + - label: ROOT + number: 1 + size: 0 + wipe_table: true + interfaces: + - dhcp: + arch: x86_64 + hostname: sm01 + ip: + address: 172.16.10.100 + gateway: 172.16.10.1 + netmask: 255.255.255.0 + lease_time: 86400 + mac: 3c:ec:ef:4c:4f:54 + name_servers: + - 172.16.10.1 + - 10.1.1.11 + uefi: true + netboot: + allowPXE: true + allowWorkflow: true diff --git a/tests/testdata/02/template1.yaml b/tests/testdata/02/template1.yaml new file mode 100644 index 000000000..ba23ee29d --- /dev/null +++ b/tests/testdata/02/template1.yaml @@ -0,0 +1,46 @@ +apiVersion: "tinkerbell.org/v1alpha1" +kind: Template +metadata: + name: template1 + namespace: default +spec: + data: | + version: "0.1" + name: debian + global_timeout: 1800 + tasks: + - name: "os-installation" + worker: "{{.device_1}}" + volumes: + - /dev:/dev + - /dev/console:/dev/console + - /lib/firmware:/lib/firmware:ro + actions: + - name: "stream-image" + image: quay.io/tinkerbell-actions/image2disk:v1.0.0 + timeout: 600 + environment: + DEST_DISK: /dev/nvme0n1 + # Hegel IP + IMG_URL: "http://10.1.1.11:8080/debian-10-openstack-amd64.raw.gz" + COMPRESSED: true + - name: "add-tink-cloud-init-ds-config" + image: writefile:v1.0.0 + timeout: 90 + environment: + DEST_DISK: /dev/nvme0n1p1 + FS_TYPE: ext4 + DEST_PATH: /etc/cloud/ds-identify.cfg + UID: 0 + GID: 0 + MODE: 0600 + DIRMODE: 0700 + CONTENTS: | + datasource: Ec2 + - name: "kexec-debian" + image: quay.io/tinkerbell-actions/kexec:v1.0.1 + timeout: 90 + pid: host + environment: + BLOCK_DEVICE: /dev/nvme0n1p1 + FS_TYPE: ext4 diff --git a/tests/testdata/02/template2.yaml b/tests/testdata/02/template2.yaml new file mode 100644 index 000000000..54004ddc4 --- /dev/null +++ b/tests/testdata/02/template2.yaml @@ -0,0 +1,65 @@ +apiVersion: "tinkerbell.org/v1alpha1" +kind: Template +metadata: + name: template2 + namespace: default +spec: + data: | + version: "0.1" + name: template1 + global_timeout: 1800 + tasks: + - name: "bmc-setup" + worker: "{{.device_2}}" + volumes: + - /dev:/dev + - /dev/console:/dev/console + - /lib/firmware:/lib/firmware:ro + actions: + - name: "setup-netboot" + image: quay.io/tinkerbell-actions/pbnj:v1.0.0 + timeout: 60 + environment: + NET_BOOT: IPXE + MACHINE: "{{.device_1}}" + - name: "power-on" + image: quay.io/tinkerbell-actions/pbnj:v1.0.0 + timeout: 60 + environment: + POWER: ON + MACHINE: "{{.device_1}}" + - name: "os-installation" + worker: "{{.device_1}}" + volumes: + - /dev:/dev + - /dev/console:/dev/console + - /lib/firmware:/lib/firmware:ro + actions: + - name: "stream-debian-image" + image: quay.io/tinkerbell-actions/image2disk:v1.0.0 + timeout: 600 + environment: + DEST_DISK: /dev/nvme0n1 + # Hegel IP + IMG_URL: "http://10.1.1.11:8080/debian-10-openstack-amd64.raw.gz" + COMPRESSED: true + - name: "add-tink-cloud-init-ds-config" + image: writefile:v1.0.0 + timeout: 90 + environment: + DEST_DISK: /dev/nvme0n1p1 + FS_TYPE: ext4 + DEST_PATH: /etc/cloud/ds-identify.cfg + UID: 0 + GID: 0 + MODE: 0600 + DIRMODE: 0700 + CONTENTS: | + datasource: Ec2 + - name: "kexec-debian" + image: quay.io/tinkerbell-actions/kexec:v1.0.1 + timeout: 90 + pid: host + environment: + BLOCK_DEVICE: /dev/nvme0n1p1 + FS_TYPE: ext4 diff --git a/tests/testdata/02/template3.yaml b/tests/testdata/02/template3.yaml new file mode 100644 index 000000000..0f1968c7f --- /dev/null +++ b/tests/testdata/02/template3.yaml @@ -0,0 +1,33 @@ +apiVersion: "tinkerbell.org/v1alpha1" +kind: Template +metadata: + name: template3 + namespace: default +spec: + data: | + version: "0.1" + name: ubuntu + global_timeout: 1800 + tasks: + - name: "task-1" + worker: "{{.device_1}}" + volumes: + - /dev:/dev + - /dev/console:/dev/console + - /lib/firmware:/lib/firmware:ro + actions: + - name: "task-1-action-1" + image: quay.io/tinkerbell-actions/image2disk:v1.0.0 + timeout: 600 + environment: + DEST_DISK: /dev/nvme0n1 + # Hegel IP + IMG_URL: "http://10.1.1.11:8080/ubuntu-amd64.raw.gz" + COMPRESSED: true + - name: "task-1-action-2" + image: quay.io/tinkerbell-actions/kexec:v1.0.1 + timeout: 90 + pid: host + environment: + BLOCK_DEVICE: /dev/nvme0n1p1 + FS_TYPE: ext4 diff --git a/tests/testdata/02/workflow1.yaml b/tests/testdata/02/workflow1.yaml new file mode 100644 index 000000000..b6b50b249 --- /dev/null +++ b/tests/testdata/02/workflow1.yaml @@ -0,0 +1,9 @@ +apiVersion: "tinkerbell.org/v1alpha1" +kind: Workflow +metadata: + name: wf1 + namespace: default +spec: + templateRef: template1 + hardwareMap: + device_1: 3c:ec:ef:4c:4f:54 diff --git a/tests/testdata/02/workflow2.yaml b/tests/testdata/02/workflow2.yaml new file mode 100644 index 000000000..99e82b0b1 --- /dev/null +++ b/tests/testdata/02/workflow2.yaml @@ -0,0 +1,10 @@ +apiVersion: "tinkerbell.org/v1alpha1" +kind: Workflow +metadata: + name: wf2 + namespace: default +spec: + templateRef: template2 + hardwareMap: + device_1: 3c:ec:ef:4c:4f:54 + device_2: pnj diff --git a/tests/testdata/02/workflow3.yaml b/tests/testdata/02/workflow3.yaml new file mode 100644 index 000000000..907c2e107 --- /dev/null +++ b/tests/testdata/02/workflow3.yaml @@ -0,0 +1,9 @@ +apiVersion: "tinkerbell.org/v1alpha1" +kind: Workflow +metadata: + name: wf3 + namespace: default +spec: + templateRef: template3 + hardwareMap: + device_1: 3c:ec:ef:4c:4f:54 diff --git a/tests/tink_suite_test.go b/tests/tink_suite_test.go new file mode 100644 index 000000000..70fad176c --- /dev/null +++ b/tests/tink_suite_test.go @@ -0,0 +1,103 @@ +package tests_test + +import ( + "context" + "fmt" + "path/filepath" + "testing" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/packethost/pkg/log" + grpcserver "github.com/tinkerbell/tink/grpc-server" + "github.com/tinkerbell/tink/pkg/apis/core/v1alpha1" + "github.com/tinkerbell/tink/pkg/controllers" + wfctrl "github.com/tinkerbell/tink/pkg/controllers/workflow" + server "github.com/tinkerbell/tink/server" + "k8s.io/client-go/kubernetes/scheme" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" +) + +var ( + k8sClient client.Client // You'll be using this client in your tests. + testEnv *envtest.Environment + ctx context.Context + cancel context.CancelFunc + serverAddr string + logger log.Logger +) + +func TestTests(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Tests Suite") +} + +var _ = BeforeSuite(func() { + ctx, cancel = context.WithCancel(context.TODO()) + + var err error + // Create Test Tink API gRPC Server + logger, err = log.Init("github.com/tinkerbell/tink/tests") + Expect(err).NotTo(HaveOccurred()) + + // Installs CRDs into cluster + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, + } + + // Start the test cluster + cfg, err := testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + cfg.Timeout = time.Second * 5 // Graceful shutdown of testenv for only 5s + logger.With("host", cfg.Host).Info("started test environment") + + // Add tink API to the client scheme + err = v1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + // Create the K8s client + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + + // database, err := db.NewK8sDatabaseFromREST(cfg, logger) + // Expect(err).NotTo(HaveOccurred()) + errCh := make(chan error, 2) + + tinkServer := server.NewKubeBackedServerFromREST(logger, + cfg, + "default", + ) + serverAddr, err = grpcserver.SetupGRPC( + ctx, + tinkServer, + "127.0.0.1:0", // Randomly selected port + nil, + errCh) + Expect(err).NotTo(HaveOccurred()) + logger.Info("HTTP server: ", fmt.Sprintf("%+v", serverAddr)) + + // Start the controller + options := controllers.GetControllerOptions() + options.LeaderElectionNamespace = "default" + manager, err := controllers.NewManager(cfg, options) + Expect(err).NotTo(HaveOccurred()) + go func() { + err := manager.RegisterControllers(ctx, wfctrl.NewController(manager.GetClient())).Start(ctx) + Expect(err).To(BeNil()) + }() +}) + +var _ = AfterSuite(func() { + By("Cancelling the context") + cancel() + By("stopping the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/tools.go b/tools.go index bf2b59d96..0b1b442dc 100644 --- a/tools.go +++ b/tools.go @@ -9,5 +9,6 @@ import ( _ "google.golang.org/grpc/cmd/protoc-gen-go-grpc" _ "google.golang.org/protobuf/cmd/protoc-gen-go" _ "mvdan.cc/gofumpt" + _ "sigs.k8s.io/controller-runtime/tools/setup-envtest" _ "sigs.k8s.io/controller-tools/cmd/controller-gen" )