From 521253c66f87b810adc319f566a4321e6cdb5684 Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Fri, 5 May 2023 16:43:26 -0600 Subject: [PATCH 1/6] Add linting to Makefile: This makes it easier to run the linting. And ci will run the exact same linting. Signed-off-by: Jacob Weinstock --- .github/workflows/ci.yaml | 6 +- .golangci.yml | 203 ++++++++++++++++++++++++++++++++++++++ Makefile | 8 +- lint.mk | 45 +++++++++ 4 files changed, 257 insertions(+), 5 deletions(-) create mode 100644 .golangci.yml create mode 100644 lint.mk diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8504c32..d970b98 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,10 +27,8 @@ jobs: - name: make vet run: make vet - - name: golangci-lint - uses: golangci/golangci-lint-action@v3 - with: - args: -v + - name: lint + run: make lint test: runs-on: ubuntu-latest diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..08d51d6 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,203 @@ +run: + # The default runtime timeout is 1m, which doesn't work well on Github Actions. + timeout: 4m + +# NOTE: This file is populated by the lint-install tool. Local adjustments may be overwritten. +linters-settings: + cyclop: + # NOTE: This is a very high transitional threshold + max-complexity: 37 + package-average: 34.0 + skip-tests: true + + gocognit: + # NOTE: This is a very high transitional threshold + min-complexity: 98 + + dupl: + threshold: 200 + + goconst: + min-len: 4 + min-occurrences: 5 + ignore-tests: true + + gosec: + excludes: + - G107 # Potential HTTP request made with variable url + - G204 # Subprocess launched with function call as argument or cmd arguments + - G404 # Use of weak random number generator (math/rand instead of crypto/rand + + errorlint: + # these are still common in Go: for instance, exit errors. + asserts: false + + exhaustive: + default-signifies-exhaustive: true + + nestif: + min-complexity: 8 + + nolintlint: + require-explanation: true + allow-unused: false + require-specific: true + + revive: + ignore-generated-header: true + severity: warning + rules: + - name: atomic + - name: blank-imports + - name: bool-literal-in-expr + - name: confusing-naming + - name: constant-logical-expr + - name: context-as-argument + - name: context-keys-type + - name: deep-exit + - name: defer + - name: range-val-in-closure + - name: range-val-address + - name: dot-imports + - name: error-naming + - name: error-return + - name: error-strings + - name: errorf + - name: exported + - name: identical-branches + - name: if-return + - name: import-shadowing + - name: increment-decrement + - name: indent-error-flow + - name: indent-error-flow + - name: package-comments + - name: range + - name: receiver-naming + - name: redefines-builtin-id + - name: superfluous-else + - name: struct-tag + - name: time-naming + - name: unexported-naming + - name: unexported-return + - name: unnecessary-stmt + - name: unreachable-code + - name: unused-parameter + - name: var-declaration + - name: var-naming + - name: unconditional-recursion + - name: waitgroup-by-value + + staticcheck: + go: "1.20" + + unused: + go: "1.20" + +output: + sort-results: true + +linters: + disable-all: true + enable: + - asciicheck + - bodyclose + - cyclop + - dogsled + - dupl + - durationcheck + - errcheck + - errname + - errorlint + - exhaustive + - exportloopref + - forcetypeassert + - gocognit + - goconst + - gocritic + - godot + - gofmt + - gofumpt + - gosec + - goheader + - goimports + - goprintffuncname + - gosimple + - govet + - importas + - ineffassign + - makezero + - misspell + - nakedret + - nestif + - nilerr + - noctx + - nolintlint + - predeclared + # disabling for the initial iteration of the linting tool + # - promlinter + - revive + - rowserrcheck + - sqlclosecheck + - staticcheck + - stylecheck + - thelper + - tparallel + - typecheck + - unconvert + - unparam + - unused + - wastedassign + - whitespace + + # Disabled linters, due to being misaligned with Go practices + # - exhaustivestruct + # - gochecknoglobals + # - gochecknoinits + # - goconst + # - godox + # - goerr113 + # - gomnd + # - lll + # - nlreturn + # - testpackage + # - wsl + # Disabled linters, due to not being relevant to our code base: + # - maligned + # - prealloc "For most programs usage of prealloc will be a premature optimization." + # Disabled linters due to bad error messages or bugs + # - tagliatelle + +issues: + # Excluding configuration per-path, per-linter, per-text and per-source + exclude-rules: + - path: _test\.go + linters: + - dupl + - errcheck + - forcetypeassert + - gocyclo + - gosec + - noctx + + - path: .*cmd.* + linters: + - noctx + + - path: main\.go + linters: + - noctx + + - path: .*cmd.* + text: "deep-exit" + + - path: main\.go + text: "deep-exit" + + # This check is of questionable value + - linters: + - tparallel + text: "call t.Parallel on the top level as well as its subtests" + + # Don't hide lint issues just because there are many of them + max-same-issues: 0 + max-issues-per-linter: 0 \ No newline at end of file diff --git a/Makefile b/Makefile index c86af5c..941bfb4 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,8 @@ all: build help: ## Display this help. @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) +include lint.mk + ##@ Development .PHONY: manifests @@ -57,7 +59,11 @@ vet: ## Run go vet against code. .PHONY: test test: manifests generate ## Run unit tests. - go test ./... -coverprofile cover.out + go test -v ./... -coverprofile cover.out + +.PHONY: cover +cover: test ## Run unit tests with coverage report + go tool cover -func=cover.out .PHONY: integration-test integration-test: manifests generate fmt vet envtest ## Run integration tests. diff --git a/lint.mk b/lint.mk new file mode 100644 index 0000000..a132280 --- /dev/null +++ b/lint.mk @@ -0,0 +1,45 @@ +# BEGIN: lint-install tinkerbell/rufio +# http://github.com/tinkerbell/lint-install + +.PHONY: lint +lint: _lint + +LINT_ARCH := $(shell uname -m) +LINT_OS := $(shell uname) +LINT_OS_LOWER := $(shell echo $(LINT_OS) | tr '[:upper:]' '[:lower:]') +LINT_ROOT := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) + +# shellcheck and hadolint lack arm64 native binaries: rely on x86-64 emulation +ifeq ($(LINT_OS),Darwin) + ifeq ($(LINT_ARCH),arm64) + LINT_ARCH=x86_64 + endif +endif + +LINTERS := +FIXERS := + +GOLANGCI_LINT_CONFIG := $(LINT_ROOT)/.golangci.yml +GOLANGCI_LINT_VERSION ?= v1.52.2 +GOLANGCI_LINT_BIN := out/linters/golangci-lint-$(GOLANGCI_LINT_VERSION)-$(LINT_ARCH) +$(GOLANGCI_LINT_BIN): + mkdir -p out/linters + rm -rf out/linters/golangci-lint-* + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b out/linters $(GOLANGCI_LINT_VERSION) + mv out/linters/golangci-lint $@ + +LINTERS += golangci-lint-lint +golangci-lint-lint: $(GOLANGCI_LINT_BIN) + find . -name go.mod -execdir "$(GOLANGCI_LINT_BIN)" run -c "$(GOLINT_CONFIG)" \; + +FIXERS += golangci-lint-fix +golangci-lint-fix: $(GOLANGCI_LINT_BIN) + find . -name go.mod -execdir "$(GOLANGCI_LINT_BIN)" run -c "$(GOLINT_CONFIG)" --fix \; + +.PHONY: _lint $(LINTERS) +_lint: $(LINTERS) + +.PHONY: fix $(FIXERS) +fix: $(FIXERS) + +# END: lint-install tinkerbell/rufio \ No newline at end of file From eb17a20500cbc0b7e3060fe92191d69887945aef Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Fri, 5 May 2023 16:59:30 -0600 Subject: [PATCH 2/6] Remove the BMCClient interface, ginkgo, and gomega: The BMCClient interface was unnecessary and added complexity to testing. bmclib already has the capabilities to specify mock providers. Removing this interface also allowed us to remove ginkgo and gomega. These frameworks were unneeded and overly complex. Also fixed all linting errors. Signed-off-by: Jacob Weinstock --- Makefile | 13 +- api/v1alpha1/groupversion_info.go | 4 +- api/v1alpha1/job.go | 12 +- api/v1alpha1/machine.go | 10 +- api/v1alpha1/task.go | 8 +- config/crd/bases/bmc.tinkerbell.org_jobs.yaml | 6 +- .../bases/bmc.tinkerbell.org_machines.yaml | 6 +- .../crd/bases/bmc.tinkerbell.org_tasks.yaml | 2 +- controllers/bmc_client.go | 15 +- controllers/helpers_test.go | 85 +++- controllers/job_controller.go | 33 +- controllers/job_controller_test.go | 201 ++++---- controllers/machine_controller.go | 33 +- controllers/machine_controller_test.go | 224 ++------- controllers/mocks/bmcclient.go | 124 ----- controllers/suite_test.go | 110 ----- controllers/task_controller.go | 37 +- controllers/task_controller_test.go | 438 ++++-------------- go.mod | 15 +- go.sum | 46 +- main.go | 4 +- 21 files changed, 411 insertions(+), 1015 deletions(-) delete mode 100644 controllers/mocks/bmcclient.go delete mode 100644 controllers/suite_test.go diff --git a/Makefile b/Makefile index 941bfb4..87e3c78 100644 --- a/Makefile +++ b/Makefile @@ -17,9 +17,6 @@ endif SHELL = /usr/bin/env bash -o pipefail .SHELLFLAGS = -ec -.PHONY: all -all: build - ##@ General # The help target prints out all targets with their descriptions organized @@ -39,6 +36,9 @@ help: ## Display this help. include lint.mk +.PHONY: all +all: build + ##@ Development .PHONY: manifests @@ -125,13 +125,6 @@ ENVTEST = $(shell pwd)/bin/setup-envtest envtest: ## Download envtest-setup locally if necessary. $(call go-get-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest) -MOCKGEN = $(shell pwd)/bin/mockgen -.PHONY: mocks -mocks: ## Generate mocks - $(call go-get-tool,$(MOCKGEN),github.com/golang/mock/mockgen@v1.6.0) - ${MOCKGEN} -destination=controllers/mocks/bmcclient.go -package=mocks "github.com/tinkerbell/rufio/controllers" BMCClient - - ##@ Release RELEASE_TAG := $(shell git describe --abbrev=0 2>/dev/null) diff --git a/api/v1alpha1/groupversion_info.go b/api/v1alpha1/groupversion_info.go index b83c299..e25a93c 100644 --- a/api/v1alpha1/groupversion_info.go +++ b/api/v1alpha1/groupversion_info.go @@ -25,10 +25,10 @@ import ( ) var ( - // GroupVersion is group version used to register these objects + // GroupVersion is group version used to register these objects. GroupVersion = schema.GroupVersion{Group: "bmc.tinkerbell.org", Version: "v1alpha1"} - // SchemeBuilder is used to add go types to the GroupVersionKind scheme + // SchemeBuilder is used to add go types to the GroupVersionKind scheme. SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} // AddToScheme adds the types in this group-version to the given scheme. diff --git a/api/v1alpha1/job.go b/api/v1alpha1/job.go index faa2082..0bbfa90 100644 --- a/api/v1alpha1/job.go +++ b/api/v1alpha1/job.go @@ -43,7 +43,7 @@ type MachineRef struct { Namespace string `json:"namespace"` } -// JobSpec defines the desired state of Job +// JobSpec defines the desired state of Job. type JobSpec struct { // MachineRef represents the Machine resource to execute the job. // All the tasks in the job are executed for the same Machine. @@ -58,7 +58,7 @@ type JobSpec struct { Tasks []Action `json:"tasks"` } -// JobStatus defines the observed state of Job +// JobStatus defines the observed state of Job. type JobStatus struct { // Conditions represents the latest available observations of an object's current state. // +optional @@ -144,9 +144,9 @@ func FormatTaskName(job Job, n int) string { //+kubebuilder:subresource:status //+kubebuilder:resource:path=jobs,scope=Namespaced,categories=tinkerbell,singular=job,shortName=j -// Job is the Schema for the bmcjobs API +// Job is the Schema for the bmcjobs API. type Job struct { - metav1.TypeMeta `json:",inline"` + metav1.TypeMeta `json:""` metav1.ObjectMeta `json:"metadata,omitempty"` Spec JobSpec `json:"spec,omitempty"` @@ -155,9 +155,9 @@ type Job struct { //+kubebuilder:object:root=true -// JobList contains a list of Job +// JobList contains a list of Job. type JobList struct { - metav1.TypeMeta `json:",inline"` + metav1.TypeMeta `json:""` metav1.ListMeta `json:"metadata,omitempty"` Items []Job `json:"items"` } diff --git a/api/v1alpha1/machine.go b/api/v1alpha1/machine.go index 451e756..e9cbdf2 100644 --- a/api/v1alpha1/machine.go +++ b/api/v1alpha1/machine.go @@ -45,7 +45,7 @@ const ( ConditionFalse ConditionStatus = "False" ) -// MachineSpec defines desired machine state +// MachineSpec defines desired machine state. type MachineSpec struct { // Connection contains connection data for a Baseboard Management Controller. Connection Connection `json:"connection"` @@ -69,7 +69,7 @@ type Connection struct { InsecureTLS bool `json:"insecureTLS"` } -// MachineStatus defines the observed state of Machine +// MachineStatus defines the observed state of Machine. type MachineStatus struct { // Power is the current power state of the Machine. // +kubebuilder:validation:Enum=on;off @@ -142,9 +142,9 @@ func WithMachineConditionMessage(m string) MachineSetConditionOption { //+kubebuilder:subresource:status //+kubebuilder:resource:path=machines,scope=Namespaced,categories=tinkerbell,singular=machine -// Machine is the Schema for the machines API +// Machine is the Schema for the machines API. type Machine struct { - metav1.TypeMeta `json:",inline"` + metav1.TypeMeta `json:""` metav1.ObjectMeta `json:"metadata,omitempty"` Spec MachineSpec `json:"spec,omitempty"` @@ -155,7 +155,7 @@ type Machine struct { // MachineList contains a list of Machines. type MachineList struct { - metav1.TypeMeta `json:",inline"` + metav1.TypeMeta `json:""` metav1.ListMeta `json:"metadata,omitempty"` Items []Machine `json:"items"` } diff --git a/api/v1alpha1/task.go b/api/v1alpha1/task.go index 498a864..dd1fa5c 100644 --- a/api/v1alpha1/task.go +++ b/api/v1alpha1/task.go @@ -55,7 +55,7 @@ type Action struct { VirtualMediaAction *VirtualMediaAction `json:"virtualMediaAction,omitempty"` } -// TaskStatus defines the observed state of Task +// TaskStatus defines the observed state of Task. type TaskStatus struct { // Conditions represents the latest available observations of an object's current state. // +optional @@ -138,7 +138,7 @@ func (t *Task) HasCondition(cType TaskConditionType, cStatus ConditionStatus) bo // Task is the Schema for the Task API. type Task struct { - metav1.TypeMeta `json:",inline"` + metav1.TypeMeta `json:""` metav1.ObjectMeta `json:"metadata,omitempty"` Spec TaskSpec `json:"spec,omitempty"` @@ -147,9 +147,9 @@ type Task struct { //+kubebuilder:object:root=true -// TaskList contains a list of Task +// TaskList contains a list of Task. type TaskList struct { - metav1.TypeMeta `json:",inline"` + metav1.TypeMeta `json:""` metav1.ListMeta `json:"metadata,omitempty"` Items []Task `json:"items"` } diff --git a/config/crd/bases/bmc.tinkerbell.org_jobs.yaml b/config/crd/bases/bmc.tinkerbell.org_jobs.yaml index cbe8dfc..544080e 100644 --- a/config/crd/bases/bmc.tinkerbell.org_jobs.yaml +++ b/config/crd/bases/bmc.tinkerbell.org_jobs.yaml @@ -22,7 +22,7 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: Job is the Schema for the bmcjobs API + description: Job is the Schema for the bmcjobs API. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -37,7 +37,7 @@ spec: metadata: type: object spec: - description: JobSpec defines the desired state of Job + description: JobSpec defines the desired state of Job. properties: machineRef: description: MachineRef represents the Machine resource to execute @@ -117,7 +117,7 @@ spec: - tasks type: object status: - description: JobStatus defines the observed state of Job + description: JobStatus defines the observed state of Job. properties: completionTime: description: CompletionTime represents time when the job was completed. diff --git a/config/crd/bases/bmc.tinkerbell.org_machines.yaml b/config/crd/bases/bmc.tinkerbell.org_machines.yaml index 3512a32..097ead7 100644 --- a/config/crd/bases/bmc.tinkerbell.org_machines.yaml +++ b/config/crd/bases/bmc.tinkerbell.org_machines.yaml @@ -20,7 +20,7 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: Machine is the Schema for the machines API + description: Machine is the Schema for the machines API. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -35,7 +35,7 @@ spec: metadata: type: object spec: - description: MachineSpec defines desired machine state + description: MachineSpec defines desired machine state. properties: connection: description: Connection contains connection data for a Baseboard Management @@ -76,7 +76,7 @@ spec: - connection type: object status: - description: MachineStatus defines the observed state of Machine + description: MachineStatus defines the observed state of Machine. properties: conditions: description: Conditions represents the latest available observations diff --git a/config/crd/bases/bmc.tinkerbell.org_tasks.yaml b/config/crd/bases/bmc.tinkerbell.org_tasks.yaml index 8c9c56c..6cdcc37 100644 --- a/config/crd/bases/bmc.tinkerbell.org_tasks.yaml +++ b/config/crd/bases/bmc.tinkerbell.org_tasks.yaml @@ -124,7 +124,7 @@ spec: - task type: object status: - description: TaskStatus defines the observed state of Task + description: TaskStatus defines the observed state of Task. properties: completionTime: description: CompletionTime represents time when the task was completed. diff --git a/controllers/bmc_client.go b/controllers/bmc_client.go index 8493db2..5d66a53 100644 --- a/controllers/bmc_client.go +++ b/controllers/bmc_client.go @@ -6,10 +6,10 @@ import ( "time" bmclib "github.com/bmc-toolbox/bmclib/v2" - "github.com/bmc-toolbox/bmclib/v2/bmc" "github.com/go-logr/logr" ) +/* // BMCClient represents a baseboard management controller client. It defines a set of methods to // connect and interact with a BMC. type BMCClient interface { @@ -33,17 +33,18 @@ type BMCClient interface { // GetMetadata returns the metadata of the bmc client. GetMetadata() bmc.Metadata } +*/ -// BMCClientFactoryFunc defines a func that returns a BMCClient -type BMCClientFactoryFunc func(ctx context.Context, log logr.Logger, hostIP, port, username, password string) (BMCClient, error) +// ClientFunc defines a func that returns a bmclib.Client. +type ClientFunc func(ctx context.Context, log logr.Logger, hostIP, port, username, password string) (*bmclib.Client, error) -// NewBMCClientFactoryFunc returns a new BMCClientFactoryFunc. The timeout parameter determines the +// NewClientFunc returns a new BMCClientFactoryFunc. The timeout parameter determines the // maximum time to probe for compatible interfaces. -func NewBMCClientFactoryFunc(timeout time.Duration) BMCClientFactoryFunc { +func NewClientFunc(timeout time.Duration) ClientFunc { // Initializes a bmclib client based on input host and credentials // Establishes a connection with the bmc with client.Open // Returns a BMCClient - return func(ctx context.Context, log logr.Logger, hostIP, port, username, password string) (BMCClient, error) { + return func(ctx context.Context, log logr.Logger, hostIP, port, username, password string) (*bmclib.Client, error) { client := bmclib.NewClient(hostIP, port, username, password) log = log.WithValues("host", hostIP, "port", port, "username", username) @@ -55,7 +56,7 @@ func NewBMCClientFactoryFunc(timeout time.Duration) BMCClientFactoryFunc { if err := client.Open(ctx); err != nil { md := client.GetMetadata() log.Info("Failed to open connection to BMC", "error", err, "providersAttempted", md.ProvidersAttempted, "successfulProvider", md.SuccessfulOpenConns) - return nil, fmt.Errorf("failed to open connection to BMC: %v", err) + return nil, fmt.Errorf("failed to open connection to BMC: %w", err) } md := client.GetMetadata() log.Info("Connected to BMC", "providersAttempted", md.ProvidersAttempted, "successfulProvider", md.SuccessfulOpenConns) diff --git a/controllers/helpers_test.go b/controllers/helpers_test.go index ad9d5b3..5bcc8db 100644 --- a/controllers/helpers_test.go +++ b/controllers/helpers_test.go @@ -1,8 +1,13 @@ package controllers_test import ( - bmcv1alpha1 "github.com/tinkerbell/rufio/api/v1alpha1" - rufiov1alpha1 "github.com/tinkerbell/rufio/api/v1alpha1" + "context" + + bmclib "github.com/bmc-toolbox/bmclib/v2" + "github.com/bmc-toolbox/bmclib/v2/providers" + "github.com/go-logr/logr" + "github.com/jacobweinstock/registrar" + "github.com/tinkerbell/rufio/api/v1alpha1" "github.com/tinkerbell/rufio/controllers" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" @@ -17,7 +22,7 @@ import ( // corev1 schemes. func createKubeClientBuilder() *fake.ClientBuilder { scheme := runtime.NewScheme() - if err := rufiov1alpha1.AddToScheme(scheme); err != nil { + if err := v1alpha1.AddToScheme(scheme); err != nil { panic(err) } if err := corev1.AddToScheme(scheme); err != nil { @@ -40,6 +45,78 @@ func createKubeClientWithObjects(objects ...client.Object) client.WithWatch { func createKubeClientWithObjectsForJobController(objects ...client.Object) client.WithWatch { return createKubeClientBuilder(). WithObjects(objects...). - WithIndex(&bmcv1alpha1.Task{}, ".metadata.controller", controllers.TaskOwnerIndexFunc). + WithIndex(&v1alpha1.Task{}, ".metadata.controller", controllers.TaskOwnerIndexFunc). Build() } + +type testProvider struct { + PName string + Proto string + Powerstate string + PowerSetOK bool + BootdeviceOK bool + VirtualMediaOK bool + ErrOpen error + ErrClose error + ErrPowerStateGet error + ErrPowerStateSet error + ErrBootDeviceSet error + ErrVirtualMediaInsert error +} + +func (t *testProvider) Name() string { + if t.PName != "" { + return t.PName + } + return "tester" +} + +func (t *testProvider) Protocol() string { + if t.Proto != "" { + return t.Proto + } + return "redfish" +} + +func (t *testProvider) Features() registrar.Features { + return registrar.Features{ + providers.FeaturePowerState, + providers.FeaturePowerSet, + providers.FeatureBootDeviceSet, + providers.FeatureVirtualMedia, + } +} + +func (t *testProvider) Open(_ context.Context) error { + return t.ErrOpen +} + +func (t *testProvider) Close(_ context.Context) error { + return t.ErrClose +} + +func (t *testProvider) PowerStateGet(_ context.Context) (string, error) { + return t.Powerstate, t.ErrPowerStateGet +} + +func (t *testProvider) PowerSet(_ context.Context, _ string) (ok bool, err error) { + return t.PowerSetOK, t.ErrPowerStateSet +} + +func (t *testProvider) BootDeviceSet(_ context.Context, _ string, _, _ bool) (ok bool, err error) { + return t.BootdeviceOK, t.ErrBootDeviceSet +} + +func (t *testProvider) SetVirtualMedia(_ context.Context, _ string, _ string) (ok bool, err error) { + return t.VirtualMediaOK, t.ErrVirtualMediaInsert +} + +// newMockBMCClientFactoryFunc returns a new BMCClientFactoryFunc. +func newTestClient(provider *testProvider) controllers.ClientFunc { + return func(ctx context.Context, log logr.Logger, hostIP, port, username, password string) (*bmclib.Client, error) { + reg := registrar.NewRegistry(registrar.WithLogger(log)) + reg.Register(provider.Name(), provider.Protocol(), provider.Features(), nil, provider) + cl := bmclib.NewClient(hostIP, port, username, password, bmclib.WithLogger(log), bmclib.WithRegistry(reg)) + return cl, cl.Open(ctx) + } +} diff --git a/controllers/job_controller.go b/controllers/job_controller.go index 9e040c1..8ad5367 100644 --- a/controllers/job_controller.go +++ b/controllers/job_controller.go @@ -20,7 +20,6 @@ import ( "context" "fmt" - "github.com/go-logr/logr" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -33,18 +32,18 @@ import ( bmcv1alpha1 "github.com/tinkerbell/rufio/api/v1alpha1" ) -// Index key for Job Owner Name +// Index key for Job Owner Name. const jobOwnerKey = ".metadata.controller" -// JobReconciler reconciles a Job object +// JobReconciler reconciles a Job object. type JobReconciler struct { client client.Client } -// NewJobReconciler returns a new JobReconciler -func NewJobReconciler(client client.Client) *JobReconciler { +// NewJobReconciler returns a new JobReconciler. +func NewJobReconciler(c client.Client) *JobReconciler { return &JobReconciler{ - client: client, + client: c, } } @@ -86,10 +85,10 @@ func (r *JobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R // Patch is used to update Status after reconciliation jobPatch := client.MergeFrom(job.DeepCopy()) - return r.reconcile(ctx, job, jobPatch, logger) + return r.doReconcile(ctx, job, jobPatch) } -func (r *JobReconciler) reconcile(ctx context.Context, job *bmcv1alpha1.Job, jobPatch client.Patch, logger logr.Logger) (ctrl.Result, error) { +func (r *JobReconciler) doReconcile(ctx context.Context, job *bmcv1alpha1.Job, jobPatch client.Patch) (ctrl.Result, error) { // Check if Job is not currently Running // Initialize the StartTime for the Job // Set the Job to Running condition True @@ -104,14 +103,14 @@ func (r *JobReconciler) reconcile(ctx context.Context, job *bmcv1alpha1.Job, job machine := &bmcv1alpha1.Machine{} err := r.getMachine(ctx, job.Spec.MachineRef, machine) if err != nil { - return ctrl.Result{}, fmt.Errorf("get Job %s/%s MachineRef: %v", job.Namespace, job.Name, err) + return ctrl.Result{}, fmt.Errorf("get Job %s/%s MachineRef: %w", job.Namespace, job.Name, err) } // List all Task owned by Job tasks := &bmcv1alpha1.TaskList{} err = r.client.List(ctx, tasks, client.MatchingFields{jobOwnerKey: job.Name}) if err != nil { - return ctrl.Result{}, fmt.Errorf("failed to list owned Tasks for Job %s/%s: %v", job.Namespace, job.Name, err) + return ctrl.Result{}, fmt.Errorf("failed to list owned Tasks for Job %s/%s: %w", job.Namespace, job.Name, err) } completedTasksCount := 0 @@ -121,7 +120,7 @@ func (r *JobReconciler) reconcile(ctx context.Context, job *bmcv1alpha1.Job, job // If the Task has neither Completed or Failed is noop. for _, task := range tasks.Items { if task.HasCondition(bmcv1alpha1.TaskCompleted, bmcv1alpha1.ConditionTrue) { - completedTasksCount += 1 + completedTasksCount++ continue } @@ -167,21 +166,21 @@ func (r *JobReconciler) reconcile(ctx context.Context, job *bmcv1alpha1.Job, job return ctrl.Result{}, err } -// getMachine Gets the Machine from MachineRef +// getMachine Gets the Machine from MachineRef. func (r *JobReconciler) getMachine(ctx context.Context, reference bmcv1alpha1.MachineRef, machine *bmcv1alpha1.Machine) error { key := types.NamespacedName{Namespace: reference.Namespace, Name: reference.Name} err := r.client.Get(ctx, key, machine) if err != nil { if apierrors.IsNotFound(err) { - return fmt.Errorf("machine %s not found: %v", key, err) + return fmt.Errorf("machine %s not found: %w", key, err) } - return fmt.Errorf("failed to get Machine %s: %v", key, err) + return fmt.Errorf("failed to get Machine %s: %w", key, err) } return nil } -// createTaskWithOwner creates a Task object with an OwnerReference set to the Job +// createTaskWithOwner creates a Task object with an OwnerReference set to the Job. func (r *JobReconciler) createTaskWithOwner(ctx context.Context, job bmcv1alpha1.Job, taskIndex int, conn bmcv1alpha1.Connection) error { isController := true task := &bmcv1alpha1.Task{ @@ -206,7 +205,7 @@ func (r *JobReconciler) createTaskWithOwner(ctx context.Context, job bmcv1alpha1 err := r.client.Create(ctx, task) if err != nil { - return fmt.Errorf("failed to create Task %s/%s: %v", task.Namespace, task.Name, err) + return fmt.Errorf("failed to create Task %s/%s: %w", task.Namespace, task.Name, err) } return nil @@ -216,7 +215,7 @@ func (r *JobReconciler) createTaskWithOwner(ctx context.Context, job bmcv1alpha1 func (r *JobReconciler) patchStatus(ctx context.Context, job *bmcv1alpha1.Job, patch client.Patch) error { err := r.client.Status().Patch(ctx, job, patch) if err != nil { - return fmt.Errorf("failed to patch Job %s/%s status: %v", job.Namespace, job.Name, err) + return fmt.Errorf("failed to patch Job %s/%s status: %w", job.Namespace, job.Name, err) } return nil diff --git a/controllers/job_controller_test.go b/controllers/job_controller_test.go index 0a05e89..118ef3b 100644 --- a/controllers/job_controller_test.go +++ b/controllers/job_controller_test.go @@ -3,149 +3,124 @@ package controllers_test import ( "context" "testing" - "time" - "github.com/onsi/gomega" - bmcv1alpha1 "github.com/tinkerbell/rufio/api/v1alpha1" + "github.com/google/go-cmp/cmp" + "github.com/tinkerbell/rufio/api/v1alpha1" "github.com/tinkerbell/rufio/controllers" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) -func TestJobReconciler_TasklessJob(t *testing.T) { - g := gomega.NewWithT(t) - - machine := createMachine() - secret := createSecret() - job := createJob("test", machine) - - kubeClient := createKubeClientWithObjectsForJobController(machine, secret, job) - - reconciler := controllers.NewJobReconciler(kubeClient) - - request := reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: job.Namespace, - Name: job.Name, - }, +func TestJobReconcile(t *testing.T) { + tests := map[string]struct { + machine *v1alpha1.Machine + secret *corev1.Secret + job *v1alpha1.Job + shouldErr bool + testAll bool + }{ + "success taskless job": {machine: createMachine(), secret: createSecret(), job: createJob("test", createMachine())}, + "failure unknown machine": {machine: &v1alpha1.Machine{}, secret: createSecret(), job: createJob("test", createMachine()), shouldErr: true}, + "success power on job": {machine: createMachine(), secret: createSecret(), job: createJob("test", createMachine(), getAction("PowerOn")), testAll: true}, } - result, err := reconciler.Reconcile(context.Background(), request) - g.Expect(err).To(gomega.Succeed()) - g.Expect(result).To(gomega.Equal(reconcile.Result{})) - - var retrieved bmcv1alpha1.Job - err = kubeClient.Get(context.Background(), request.NamespacedName, &retrieved) - g.Expect(err).To(gomega.Succeed()) -} - -func TestJobReconciler_UnknownMachine(t *testing.T) { - g := gomega.NewWithT(t) - - secret := createSecret() - job := &bmcv1alpha1.Job{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "test", - }, - Spec: bmcv1alpha1.JobSpec{ - MachineRef: bmcv1alpha1.MachineRef{Name: "unknown", Namespace: "default"}, - Tasks: []bmcv1alpha1.Action{}, - }, - } - - builder := createKubeClientBuilder() - builder.WithObjects(secret, job) - kubeClient := builder.Build() - - reconciler := controllers.NewJobReconciler(kubeClient) - - request := reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: job.Namespace, - Name: job.Name, - }, - } - - _, err := reconciler.Reconcile(context.Background(), request) - g.Expect(err).To(gomega.HaveOccurred()) -} - -func TestJobReconciler_Reconcile(t *testing.T) { - for name, action := range map[string]bmcv1alpha1.Action{ - "PowerAction": {PowerAction: bmcv1alpha1.PowerOn.Ptr()}, - "OneTimeBootDeviceAction": { - OneTimeBootDeviceAction: &bmcv1alpha1.OneTimeBootDeviceAction{ - Devices: []bmcv1alpha1.BootDevice{bmcv1alpha1.PXE}, - }, - }, - } { + for name, tt := range tests { t.Run(name, func(t *testing.T) { - g := gomega.NewWithT(t) + kubeClient := createKubeClientWithObjectsForJobController(tt.machine, tt.secret, tt.job) - machine := createMachine() - secret := createSecret() - job := createJob(name, machine) - job.Spec.Tasks = append(job.Spec.Tasks, action) - - cluster := createKubeClientWithObjectsForJobController(machine, secret, job) + reconciler := controllers.NewJobReconciler(kubeClient) request := reconcile.Request{ NamespacedName: types.NamespacedName{ - Namespace: job.Namespace, - Name: job.Name, + Namespace: tt.job.Namespace, + Name: tt.job.Name, }, } - reconciler := controllers.NewJobReconciler(cluster) - result, err := reconciler.Reconcile(context.Background(), request) - g.Expect(err).To(gomega.Succeed()) - g.Expect(result).To(gomega.Equal(reconcile.Result{})) - - var retrieved1 bmcv1alpha1.Job - err = cluster.Get(context.Background(), request.NamespacedName, &retrieved1) - g.Expect(err).To(gomega.Succeed()) - g.Expect(retrieved1.Status.StartTime.Unix()).To(gomega.BeNumerically("~", time.Now().Unix(), 10)) - g.Expect(retrieved1.Status.CompletionTime.IsZero()).To(gomega.BeTrue()) - g.Expect(retrieved1.Status.Conditions).To(gomega.HaveLen(1)) - g.Expect(retrieved1.Status.Conditions[0].Type).To(gomega.Equal(bmcv1alpha1.JobRunning)) - g.Expect(retrieved1.Status.Conditions[0].Status).To(gomega.Equal(bmcv1alpha1.ConditionTrue)) + _, err := reconciler.Reconcile(context.Background(), request) + if !tt.shouldErr && err != nil { + t.Fatalf("expected no error, got %v", err) + } + if tt.shouldErr && err == nil { + t.Fatal("expected error, got nil") + } + if tt.shouldErr || !tt.testAll { + return + } + var retrieved1 v1alpha1.Job + if err = kubeClient.Get(context.Background(), request.NamespacedName, &retrieved1); err != nil { + t.Fatalf("expected no error, got %v", err) + } + // TODO: g.Expect(retrieved1.Status.StartTime.Unix()).To(gomega.BeNumerically("~", time.Now().Unix(), 10)) + if !retrieved1.Status.CompletionTime.IsZero() { + t.Fatalf("expected CompletionTime to be zero, got %v", retrieved1.Status.CompletionTime) + } + if len(retrieved1.Status.Conditions) != 1 { + t.Fatalf("expected 1 condition, got %v", len(retrieved1.Status.Conditions)) + } + if retrieved1.Status.Conditions[0].Type != v1alpha1.JobRunning { + t.Fatalf("expected condition type %v, got %v", v1alpha1.JobRunning, retrieved1.Status.Conditions[0].Type) + } + if retrieved1.Status.Conditions[0].Status != v1alpha1.ConditionTrue { + t.Fatalf("expected condition status %v, got %v", v1alpha1.ConditionTrue, retrieved1.Status.Conditions[0].Status) + } - var task bmcv1alpha1.Task + var task v1alpha1.Task taskKey := types.NamespacedName{ - Namespace: job.Namespace, - Name: bmcv1alpha1.FormatTaskName(*job, 0), - } - err = cluster.Get(context.Background(), taskKey, &task) - g.Expect(err).To(gomega.Succeed()) - g.Expect(task.Spec.Task).To(gomega.BeEquivalentTo(job.Spec.Tasks[0])) - g.Expect(task.OwnerReferences).To(gomega.HaveLen(1)) - g.Expect(task.OwnerReferences[0].Name).To(gomega.Equal(job.Name)) - g.Expect(task.OwnerReferences[0].Kind).To(gomega.Equal("Job")) + Namespace: tt.job.Namespace, + Name: v1alpha1.FormatTaskName(*tt.job, 0), + } + if err = kubeClient.Get(context.Background(), taskKey, &task); err != nil { + t.Fatalf("expected no error, got %v", err) + } + if diff := cmp.Diff(task.Spec.Task, tt.job.Spec.Tasks[0]); diff != "" { + t.Fatalf("expected task %v, got %v", tt.job.Spec.Tasks[0], task.Spec.Task) + } + if len(task.OwnerReferences) != 1 { + t.Fatalf("expected 1 owner reference, got %v", len(task.OwnerReferences)) + } + if task.OwnerReferences[0].Name != tt.job.Name { + t.Fatalf("expected owner reference name %v, got %v", tt.job.Name, task.OwnerReferences[0].Name) + } + if diff := cmp.Diff(task.OwnerReferences[0].Kind, "Job"); diff != "" { + t.Fatal(diff) + } // Ensure re-reconciling a job does nothing given the task is still outstanding. - result, err = reconciler.Reconcile(context.Background(), request) - g.Expect(err).To(gomega.Succeed()) - g.Expect(result).To(gomega.Equal(reconcile.Result{})) + result, err := reconciler.Reconcile(context.Background(), request) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if diff := cmp.Diff(result, reconcile.Result{}); diff != "" { + t.Fatal(diff) + } - var retrieved2 bmcv1alpha1.Job - err = cluster.Get(context.Background(), request.NamespacedName, &retrieved2) - g.Expect(err).To(gomega.Succeed()) - g.Expect(retrieved2).To(gomega.BeEquivalentTo(retrieved1)) + var retrieved2 v1alpha1.Job + if err = kubeClient.Get(context.Background(), request.NamespacedName, &retrieved2); err != nil { + t.Fatalf("expected no error, got %v", err) + } + if diff := cmp.Diff(retrieved1, retrieved2); diff != "" { + t.Fatal(diff) + } }) } } -func createJob(name string, machine *bmcv1alpha1.Machine) *bmcv1alpha1.Job { - return &bmcv1alpha1.Job{ +func createJob(name string, machine *v1alpha1.Machine, t ...v1alpha1.Action) *v1alpha1.Job { + tasks := []v1alpha1.Action{} + if len(t) > 0 { + tasks = t + } + return &v1alpha1.Job{ ObjectMeta: metav1.ObjectMeta{ Namespace: "default", Name: name, }, - Spec: bmcv1alpha1.JobSpec{ - MachineRef: bmcv1alpha1.MachineRef{Name: machine.Name, Namespace: machine.Namespace}, - Tasks: []bmcv1alpha1.Action{}, + Spec: v1alpha1.JobSpec{ + MachineRef: v1alpha1.MachineRef{Name: machine.Name, Namespace: machine.Namespace}, + Tasks: tasks, }, } } diff --git a/controllers/machine_controller.go b/controllers/machine_controller.go index 6c61b6d..7863cac 100644 --- a/controllers/machine_controller.go +++ b/controllers/machine_controller.go @@ -22,6 +22,7 @@ import ( "strconv" "strings" + bmclib "github.com/bmc-toolbox/bmclib/v2" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -34,11 +35,11 @@ import ( bmcv1alpha1 "github.com/tinkerbell/rufio/api/v1alpha1" ) -// MachineReconciler reconciles a Machine object +// MachineReconciler reconciles a Machine object. type MachineReconciler struct { client client.Client recorder record.EventRecorder - bmcClientFactory BMCClientFactoryFunc + bmcClientFactory ClientFunc } const ( @@ -46,17 +47,17 @@ const ( EventSetPowerStateFailed = "SetPowerStateFailed" ) -// NewMachineReconciler returns a new MachineReconciler -func NewMachineReconciler(client client.Client, recorder record.EventRecorder, bmcClientFactory BMCClientFactoryFunc) *MachineReconciler { +// NewMachineReconciler returns a new MachineReconciler. +func NewMachineReconciler(c client.Client, recorder record.EventRecorder, bmcClientFactory ClientFunc) *MachineReconciler { return &MachineReconciler{ - client: client, + client: c, recorder: recorder, bmcClientFactory: bmcClientFactory, } } -// machineFieldReconciler defines a function to reconcile Machine spec field -type machineFieldReconciler func(context.Context, *bmcv1alpha1.Machine, BMCClient) error +// machineFieldReconciler defines a function to reconcile Machine spec field. +type machineFieldReconciler func(context.Context, *bmcv1alpha1.Machine, *bmclib.Client) error //+kubebuilder:rbac:groups=bmc.tinkerbell.org,resources=machines,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=bmc.tinkerbell.org,resources=machines/status,verbs=get;update;patch @@ -91,15 +92,15 @@ func (r *MachineReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct // Patch is used to update Status after reconciliation machinePatch := client.MergeFrom(machine.DeepCopy()) - return r.reconcile(ctx, machine, machinePatch, logger) + return r.doReconcile(ctx, machine, machinePatch, logger) } -func (r *MachineReconciler) reconcile(ctx context.Context, bm *bmcv1alpha1.Machine, bmPatch client.Patch, logger logr.Logger) (ctrl.Result, error) { +func (r *MachineReconciler) doReconcile(ctx context.Context, bm *bmcv1alpha1.Machine, bmPatch client.Patch, logger logr.Logger) (ctrl.Result, error) { // Fetching username, password from SecretReference // Requeue if error fetching secret username, password, err := resolveAuthSecretRef(ctx, r.client, bm.Spec.Connection.AuthSecretRef) if err != nil { - return ctrl.Result{Requeue: true}, fmt.Errorf("resolving Machine %s/%s SecretReference: %v", bm.Namespace, bm.Name, err) + return ctrl.Result{Requeue: true}, fmt.Errorf("resolving Machine %s/%s SecretReference: %w", bm.Namespace, bm.Name, err) } // Initializing BMC Client @@ -115,7 +116,7 @@ func (r *MachineReconciler) reconcile(ctx context.Context, bm *bmcv1alpha1.Machi return result, err } - // Close BMC connection after reconcilation + // Close BMC connection after reconciliation defer func() { if err := bmcClient.Close(ctx); err != nil { md := bmcClient.GetMetadata() @@ -153,11 +154,11 @@ func (r *MachineReconciler) reconcile(ctx context.Context, bm *bmcv1alpha1.Machi } // reconcilePower ensures the Machine Power is in the desired state. -func (r *MachineReconciler) reconcilePower(ctx context.Context, bm *bmcv1alpha1.Machine, bmcClient BMCClient) error { +func (r *MachineReconciler) reconcilePower(ctx context.Context, bm *bmcv1alpha1.Machine, bmcClient *bmclib.Client) error { rawState, err := bmcClient.GetPowerState(ctx) if err != nil { r.recorder.Eventf(bm, corev1.EventTypeWarning, EventGetPowerStateFailed, "get power state: %v", err) - return fmt.Errorf("get power state: %v", err) + return fmt.Errorf("get power state: %w", err) } state, err := convertRawBMCPowerState(rawState) @@ -174,7 +175,7 @@ func (r *MachineReconciler) reconcilePower(ctx context.Context, bm *bmcv1alpha1. func (r *MachineReconciler) patchStatus(ctx context.Context, bm *bmcv1alpha1.Machine, patch client.Patch) (ctrl.Result, error) { err := r.client.Status().Patch(ctx, bm, patch) if err != nil { - return ctrl.Result{}, fmt.Errorf("failed to patch Machine %s/%s status: %v", bm.Namespace, bm.Name, err) + return ctrl.Result{}, fmt.Errorf("failed to patch Machine %s/%s status: %w", bm.Namespace, bm.Name, err) } return ctrl.Result{}, nil @@ -204,10 +205,10 @@ func resolveAuthSecretRef(ctx context.Context, c client.Client, secretRef corev1 if err := c.Get(ctx, key, secret); err != nil { if apierrors.IsNotFound(err) { - return "", "", fmt.Errorf("secret %s not found: %v", key, err) + return "", "", fmt.Errorf("secret %s not found: %w", key, err) } - return "", "", fmt.Errorf("failed to retrieve secret %s : %v", secretRef, err) + return "", "", fmt.Errorf("failed to retrieve secret %s : %w", secretRef, err) } username, ok := secret.Data["username"] diff --git a/controllers/machine_controller_test.go b/controllers/machine_controller_test.go index 460dbde..0793d22 100644 --- a/controllers/machine_controller_test.go +++ b/controllers/machine_controller_test.go @@ -5,10 +5,6 @@ import ( "errors" "testing" - "github.com/bmc-toolbox/bmclib/v2/bmc" - "github.com/go-logr/logr" - "github.com/golang/mock/gomock" - "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -19,102 +15,57 @@ import ( bmcv1alpha1 "github.com/tinkerbell/rufio/api/v1alpha1" "github.com/tinkerbell/rufio/controllers" - "github.com/tinkerbell/rufio/controllers/mocks" ) -func TestReconcileGetPowerStateSuccess(t *testing.T) { - g := gomega.NewWithT(t) - - ctx := context.Background() - ctrl := gomock.NewController(t) - mockBMCClient := mocks.NewMockBMCClient(ctrl) - - bm := createMachine() - authSecret := createSecret() - - objs := []runtime.Object{bm, authSecret} - scheme := runtime.NewScheme() - _ = bmcv1alpha1.AddToScheme(scheme) - _ = corev1.AddToScheme(scheme) - - clientBuilder := fake.NewClientBuilder() - client := clientBuilder.WithScheme(scheme).WithRuntimeObjects(objs...).Build() - fakeRecorder := record.NewFakeRecorder(2) - - mockBMCClient.EXPECT().GetPowerState(ctx).Return(string(bmcv1alpha1.On), nil) - mockBMCClient.EXPECT().Close(ctx).Return(nil) - mockBMCClient.EXPECT().GetMetadata().Return(bmc.Metadata{}) - - reconciler := controllers.NewMachineReconciler( - client, - fakeRecorder, - newMockBMCClientFactoryFunc(mockBMCClient), - ) - - req := reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: "test-namespace", - Name: "test-bm", - }, - } - - _, err := reconciler.Reconcile(ctx, req) - g.Expect(err).ToNot(gomega.HaveOccurred()) -} - -func TestReconcileSecretReferenceError(t *testing.T) { - g := gomega.NewWithT(t) - - ctx := context.Background() - ctrl := gomock.NewController(t) - mockBMCClient := mocks.NewMockBMCClient(ctrl) - - bm := createMachine() - - scheme := runtime.NewScheme() - _ = bmcv1alpha1.AddToScheme(scheme) - _ = corev1.AddToScheme(scheme) - fakeRecorder := record.NewFakeRecorder(2) - - tt := map[string]struct { - Secret *corev1.Secret +func TestMachineReconcile(t *testing.T) { + tests := map[string]struct { + provider *testProvider + shouldErr bool + secret *corev1.Secret }{ - "secret not found": { - Secret: &corev1.Secret{}, - }, - "username not found": { - Secret: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test-namespace", - Name: "test-bm-auth", - }, - Data: map[string][]byte{ - "password": []byte("test"), - }, + "success power on": {provider: &testProvider{Powerstate: "on"}, secret: createSecret()}, + "success power off": {provider: &testProvider{Powerstate: "off"}, secret: createSecret()}, + "fail on open": {provider: &testProvider{ErrOpen: errors.New("failed to open connection")}, shouldErr: true, secret: createSecret()}, + "fail on power get": {provider: &testProvider{ErrPowerStateGet: errors.New("failed to set power state")}, shouldErr: true, secret: createSecret()}, + "fail bad power state": {provider: &testProvider{Powerstate: "bad"}, shouldErr: true, secret: createSecret()}, + "fail on close": {provider: &testProvider{ErrClose: errors.New("failed to close connection")}, shouldErr: true, secret: createSecret()}, + "fail secret not found": {provider: &testProvider{Powerstate: "on"}, shouldErr: true, secret: &corev1.Secret{}}, + "fail secret username not found": {provider: &testProvider{Powerstate: "on"}, shouldErr: true, secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-namespace", + Name: "test-bm-auth", }, - }, - "password not found": { - Secret: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test-namespace", - Name: "test-bm-auth", - }, - Data: map[string][]byte{ - "username": []byte("test"), - }, + Data: map[string][]byte{ + "password": []byte("test"), }, - }, + }}, + "fail secret password not found": {provider: &testProvider{Powerstate: "on"}, shouldErr: true, secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-namespace", + Name: "test-bm-auth", + }, + Data: map[string][]byte{ + "username": []byte("test"), + }, + }}, } - for name, test := range tt { + for name, tt := range tests { t.Run(name, func(t *testing.T) { - objs := []runtime.Object{bm, test.Secret} + scheme := runtime.NewScheme() + _ = bmcv1alpha1.AddToScheme(scheme) + _ = corev1.AddToScheme(scheme) + clientBuilder := fake.NewClientBuilder() + bm := createMachine() + objs := []runtime.Object{bm, tt.secret} client := clientBuilder.WithScheme(scheme).WithRuntimeObjects(objs...).Build() + fakeRecorder := record.NewFakeRecorder(2) + reconciler := controllers.NewMachineReconciler( client, fakeRecorder, - newMockBMCClientFactoryFunc(mockBMCClient), + newTestClient(tt.provider), ) req := reconcile.Request{ @@ -124,99 +75,14 @@ func TestReconcileSecretReferenceError(t *testing.T) { }, } - _, err := reconciler.Reconcile(ctx, req) - g.Expect(err).To(gomega.HaveOccurred()) + _, err := reconciler.Reconcile(context.Background(), req) + if !tt.shouldErr && err != nil { + t.Fatalf("expected no error, got %v", err) + } + if tt.shouldErr && err == nil { + t.Fatal("expected error, got nil") + } }) - - } - -} - -func TestReconcileConnectionError(t *testing.T) { - g := gomega.NewWithT(t) - - ctx := context.Background() - - bm := createMachine() - authSecret := createSecret() - - objs := []runtime.Object{bm, authSecret} - scheme := runtime.NewScheme() - _ = bmcv1alpha1.AddToScheme(scheme) - _ = corev1.AddToScheme(scheme) - clientBuilder := fake.NewClientBuilder() - client := clientBuilder.WithScheme(scheme).WithRuntimeObjects(objs...).Build() - fakeRecorder := record.NewFakeRecorder(2) - - reconciler := controllers.NewMachineReconciler( - client, - fakeRecorder, - newMockBMCClientFactoryFuncError(), - ) - - req := reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: "test-namespace", - Name: "test-bm", - }, - } - - _, err := reconciler.Reconcile(ctx, req) - g.Expect(err).To(gomega.HaveOccurred()) -} - -func TestReconcileGetPowerStateError(t *testing.T) { - g := gomega.NewWithT(t) - - ctx := context.Background() - ctrl := gomock.NewController(t) - mockBMCClient := mocks.NewMockBMCClient(ctrl) - - bm := createMachine() - authSecret := createSecret() - - objs := []runtime.Object{bm, authSecret} - scheme := runtime.NewScheme() - _ = bmcv1alpha1.AddToScheme(scheme) - _ = corev1.AddToScheme(scheme) - - clientBuilder := fake.NewClientBuilder() - client := clientBuilder.WithScheme(scheme).WithRuntimeObjects(objs...).Build() - fakeRecorder := record.NewFakeRecorder(2) - - mockBMCClient.EXPECT().GetPowerState(ctx).Return(string(bmcv1alpha1.Off), errors.New("this is not allowed")) - mockBMCClient.EXPECT().Close(ctx).Return(nil) - mockBMCClient.EXPECT().GetMetadata().Return(bmc.Metadata{}) - - reconciler := controllers.NewMachineReconciler( - client, - fakeRecorder, - newMockBMCClientFactoryFunc(mockBMCClient), - ) - - req := reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: "test-namespace", - Name: "test-bm", - }, - } - - _, err := reconciler.Reconcile(ctx, req) - g.Expect(err).To(gomega.HaveOccurred()) - g.Expect(fakeRecorder.Events).NotTo(gomega.BeEmpty()) -} - -// newMockBMCClientFactoryFunc returns a new BMCClientFactoryFunc -func newMockBMCClientFactoryFunc(mockBMCClient *mocks.MockBMCClient) controllers.BMCClientFactoryFunc { - return func(ctx context.Context, log logr.Logger, hostIP, port, username, password string) (controllers.BMCClient, error) { - return mockBMCClient, nil - } -} - -// newMockBMCClientFactoryFunc returns a new BMCClientFactoryFunc -func newMockBMCClientFactoryFuncError() controllers.BMCClientFactoryFunc { - return func(ctx context.Context, log logr.Logger, hostIP, port, username, password string) (controllers.BMCClient, error) { - return nil, errors.New("connection failed") } } diff --git a/controllers/mocks/bmcclient.go b/controllers/mocks/bmcclient.go deleted file mode 100644 index 02331c6..0000000 --- a/controllers/mocks/bmcclient.go +++ /dev/null @@ -1,124 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/tinkerbell/rufio/controllers (interfaces: BMCClient) - -// Package mocks is a generated GoMock package. -package mocks - -import ( - context "context" - reflect "reflect" - - bmc "github.com/bmc-toolbox/bmclib/v2/bmc" - gomock "github.com/golang/mock/gomock" -) - -// MockBMCClient is a mock of BMCClient interface. -type MockBMCClient struct { - ctrl *gomock.Controller - recorder *MockBMCClientMockRecorder -} - -// MockBMCClientMockRecorder is the mock recorder for MockBMCClient. -type MockBMCClientMockRecorder struct { - mock *MockBMCClient -} - -// NewMockBMCClient creates a new mock instance. -func NewMockBMCClient(ctrl *gomock.Controller) *MockBMCClient { - mock := &MockBMCClient{ctrl: ctrl} - mock.recorder = &MockBMCClientMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockBMCClient) EXPECT() *MockBMCClientMockRecorder { - return m.recorder -} - -// Close mocks base method. -func (m *MockBMCClient) Close(arg0 context.Context) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Close", arg0) - ret0, _ := ret[0].(error) - return ret0 -} - -// Close indicates an expected call of Close. -func (mr *MockBMCClientMockRecorder) Close(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockBMCClient)(nil).Close), arg0) -} - -// GetMetadata mocks base method. -func (m *MockBMCClient) GetMetadata() bmc.Metadata { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetMetadata") - ret0, _ := ret[0].(bmc.Metadata) - return ret0 -} - -// GetMetadata indicates an expected call of GetMetadata. -func (mr *MockBMCClientMockRecorder) GetMetadata() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMetadata", reflect.TypeOf((*MockBMCClient)(nil).GetMetadata)) -} - -// GetPowerState mocks base method. -func (m *MockBMCClient) GetPowerState(arg0 context.Context) (string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetPowerState", arg0) - ret0, _ := ret[0].(string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetPowerState indicates an expected call of GetPowerState. -func (mr *MockBMCClientMockRecorder) GetPowerState(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPowerState", reflect.TypeOf((*MockBMCClient)(nil).GetPowerState), arg0) -} - -// SetBootDevice mocks base method. -func (m *MockBMCClient) SetBootDevice(arg0 context.Context, arg1 string, arg2, arg3 bool) (bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SetBootDevice", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SetBootDevice indicates an expected call of SetBootDevice. -func (mr *MockBMCClientMockRecorder) SetBootDevice(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBootDevice", reflect.TypeOf((*MockBMCClient)(nil).SetBootDevice), arg0, arg1, arg2, arg3) -} - -// SetPowerState mocks base method. -func (m *MockBMCClient) SetPowerState(arg0 context.Context, arg1 string) (bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SetPowerState", arg0, arg1) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SetPowerState indicates an expected call of SetPowerState. -func (mr *MockBMCClientMockRecorder) SetPowerState(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPowerState", reflect.TypeOf((*MockBMCClient)(nil).SetPowerState), arg0, arg1) -} - -// SetVirtualMedia mocks base method. -func (m *MockBMCClient) SetVirtualMedia(arg0 context.Context, arg1, arg2 string) (bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SetVirtualMedia", arg0, arg1, arg2) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SetVirtualMedia indicates an expected call of SetVirtualMedia. -func (mr *MockBMCClientMockRecorder) SetVirtualMedia(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetVirtualMedia", reflect.TypeOf((*MockBMCClient)(nil).SetVirtualMedia), arg0, arg1, arg2) -} diff --git a/controllers/suite_test.go b/controllers/suite_test.go deleted file mode 100644 index 4195ce7..0000000 --- a/controllers/suite_test.go +++ /dev/null @@ -1,110 +0,0 @@ -//go:build integration - -/* -Copyright 2022 Tinkerbell. - -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 controllers - -import ( - "fmt" - "path/filepath" - "testing" - - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/config" - "github.com/onsi/ginkgo/types" - . "github.com/onsi/gomega" - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/envtest" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - - bmcv1alpha1 "github.com/tinkerbell/rufio/api/v1alpha1" - //+kubebuilder:scaffold:imports -) - -// These tests use Ginkgo (BDD-style Go testing framework). Refer to -// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. - -var cfg *rest.Config -var k8sClient client.Client -var testEnv *envtest.Environment - -func TestAPIs(t *testing.T) { - RegisterFailHandler(Fail) - - RunSpecsWithDefaultAndCustomReporters(t, - "Controller Suite", - []Reporter{newlineReporter{}}) -} - -var _ = BeforeSuite(func() { - logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) - - By("bootstrapping test environment") - testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, - ErrorIfCRDPathMissing: true, - } - - var err error - cfg, err = testEnv.Start() - Expect(err).NotTo(HaveOccurred()) - Expect(cfg).NotTo(BeNil()) - - err = bmcv1alpha1.AddToScheme(scheme.Scheme) - Expect(err).NotTo(HaveOccurred()) - - //+kubebuilder:scaffold:scheme - - k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) - Expect(err).NotTo(HaveOccurred()) - Expect(k8sClient).NotTo(BeNil()) -}, 60) - -var _ = AfterSuite(func() { - By("tearing down the test environment") - err := testEnv.Stop() - Expect(err).NotTo(HaveOccurred()) -}) - -// newlineReporter is Reporter that Prints a newline after the default Reporter output so that the results -// are correctly parsed by test automation. -// See issue https://github.com/jstemmer/go-junit-report/issues/31 -// This is copied from sigs.k8s.io/controller-runtime/pkg/envtest/printer since controller-runtime -// doesn't include this starting with version v0.14.0 -type newlineReporter struct{} - -// SpecSuiteWillBegin implements ginkgo.Reporter. -func (newlineReporter) SpecSuiteWillBegin(config config.GinkgoConfigType, summary *types.SuiteSummary) { -} - -// BeforeSuiteDidRun implements ginkgo.Reporter. -func (newlineReporter) BeforeSuiteDidRun(setupSummary *types.SetupSummary) {} - -// AfterSuiteDidRun implements ginkgo.Reporter. -func (newlineReporter) AfterSuiteDidRun(setupSummary *types.SetupSummary) {} - -// SpecWillRun implements ginkgo.Reporter. -func (newlineReporter) SpecWillRun(specSummary *types.SpecSummary) {} - -// SpecDidComplete implements ginkgo.Reporter. -func (newlineReporter) SpecDidComplete(specSummary *types.SpecSummary) {} - -// SpecSuiteDidEnd Prints a newline between "35 Passed | 0 Failed | 0 Pending | 0 Skipped" and "--- PASS:". -func (newlineReporter) SpecSuiteDidEnd(summary *types.SuiteSummary) { fmt.Printf("\n") } diff --git a/controllers/task_controller.go b/controllers/task_controller.go index dc0280c..fe36f30 100644 --- a/controllers/task_controller.go +++ b/controllers/task_controller.go @@ -19,6 +19,7 @@ import ( "strconv" "time" + bmclib "github.com/bmc-toolbox/bmclib/v2" "github.com/go-logr/logr" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -31,16 +32,16 @@ import ( const powerActionRequeueAfter = 3 * time.Second -// TaskReconciler reconciles a Task object +// TaskReconciler reconciles a Task object. type TaskReconciler struct { client client.Client - bmcClientFactory BMCClientFactoryFunc + bmcClientFactory ClientFunc } -// NewTaskReconciler returns a new TaskReconciler -func NewTaskReconciler(client client.Client, bmcClientFactory BMCClientFactoryFunc) *TaskReconciler { +// NewTaskReconciler returns a new TaskReconciler. +func NewTaskReconciler(c client.Client, bmcClientFactory ClientFunc) *TaskReconciler { return &TaskReconciler{ - client: client, + client: c, bmcClientFactory: bmcClientFactory, } } @@ -83,15 +84,15 @@ func (r *TaskReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl. taskPatch := client.MergeFrom(task.DeepCopy()) logger = logger.WithValues("action", task.Spec.Task, "host", task.Spec.Connection.Host) - return r.reconcile(ctx, task, taskPatch, logger) + return r.doReconcile(ctx, task, taskPatch, logger) } -func (r *TaskReconciler) reconcile(ctx context.Context, task *bmcv1alpha1.Task, taskPatch client.Patch, logger logr.Logger) (ctrl.Result, error) { +func (r *TaskReconciler) doReconcile(ctx context.Context, task *bmcv1alpha1.Task, taskPatch client.Patch, logger logr.Logger) (ctrl.Result, error) { // Fetching username, password from SecretReference in Connection. // Requeue if error fetching secret username, password, err := resolveAuthSecretRef(ctx, r.client, task.Spec.Connection.AuthSecretRef) if err != nil { - return ctrl.Result{}, fmt.Errorf("resolving connection secret for task %s/%s: %v", task.Namespace, task.Name, err) + return ctrl.Result{}, fmt.Errorf("resolving connection secret for task %s/%s: %w", task.Namespace, task.Name, err) } // Initializing BMC Client @@ -137,7 +138,7 @@ func (r *TaskReconciler) reconcile(ctx context.Context, task *bmcv1alpha1.Task, result, err := r.checkTaskStatus(ctx, logger, task.Spec.Task, bmcClient) if err != nil { - return result, fmt.Errorf("bmc task status check: %s", err) + return result, fmt.Errorf("bmc task status check: %w", err) } if !result.IsZero() { @@ -182,12 +183,12 @@ func (r *TaskReconciler) reconcile(ctx context.Context, task *bmcv1alpha1.Task, return ctrl.Result{}, nil } -// runTask executes the defined Task in a Task -func (r *TaskReconciler) runTask(ctx context.Context, logger logr.Logger, task bmcv1alpha1.Action, bmcClient BMCClient) error { +// runTask executes the defined Task in a Task. +func (r *TaskReconciler) runTask(ctx context.Context, logger logr.Logger, task bmcv1alpha1.Action, bmcClient *bmclib.Client) error { if task.PowerAction != nil { ok, err := bmcClient.SetPowerState(ctx, string(*task.PowerAction)) if err != nil { - return fmt.Errorf("failed to perform PowerAction: %v", err) + return fmt.Errorf("failed to perform PowerAction: %w", err) } md := bmcClient.GetMetadata() logger.Info("power state set successfully", "providersAttempted", md.ProvidersAttempted, "successfulProvider", md.SuccessfulProvider, "ok", ok) @@ -198,7 +199,7 @@ func (r *TaskReconciler) runTask(ctx context.Context, logger logr.Logger, task b // setPersistent is false. ok, err := bmcClient.SetBootDevice(ctx, string(task.OneTimeBootDeviceAction.Devices[0]), false, task.OneTimeBootDeviceAction.EFIBoot) if err != nil { - return fmt.Errorf("failed to perform OneTimeBootDeviceAction: %v", err) + return fmt.Errorf("failed to perform OneTimeBootDeviceAction: %w", err) } md := bmcClient.GetMetadata() logger.Info("one time boot device set successfully", "providersAttempted", md.ProvidersAttempted, "successfulProvider", md.SuccessfulProvider, "ok", ok) @@ -207,7 +208,7 @@ func (r *TaskReconciler) runTask(ctx context.Context, logger logr.Logger, task b if task.VirtualMediaAction != nil { ok, err := bmcClient.SetVirtualMedia(ctx, string(task.VirtualMediaAction.Kind), task.VirtualMediaAction.MediaURL) if err != nil { - return fmt.Errorf("failed to perform SetVirtualMedia: %v", err) + return fmt.Errorf("failed to perform SetVirtualMedia: %w", err) } md := bmcClient.GetMetadata() logger.Info("virtual media set successfully", "providersAttempted", md.ProvidersAttempted, "successfulProvider", md.SuccessfulProvider, "ok", ok) @@ -218,12 +219,12 @@ func (r *TaskReconciler) runTask(ctx context.Context, logger logr.Logger, task b // checkTaskStatus checks if Task action completed. // This is currently limited only to a few PowerAction types. -func (r *TaskReconciler) checkTaskStatus(ctx context.Context, log logr.Logger, task bmcv1alpha1.Action, bmcClient BMCClient) (ctrl.Result, error) { +func (r *TaskReconciler) checkTaskStatus(ctx context.Context, log logr.Logger, task bmcv1alpha1.Action, bmcClient *bmclib.Client) (ctrl.Result, error) { // TODO(pokearu): Extend to all actions. if task.PowerAction != nil { rawState, err := bmcClient.GetPowerState(ctx) if err != nil { - return ctrl.Result{}, fmt.Errorf("failed to get power state: %v", err) + return ctrl.Result{}, fmt.Errorf("failed to get power state: %w", err) } log = log.WithValues("currentPowerState", rawState) log.Info("power state check") @@ -233,7 +234,7 @@ func (r *TaskReconciler) checkTaskStatus(ctx context.Context, log logr.Logger, t return ctrl.Result{}, err } - switch *task.PowerAction { + switch *task.PowerAction { //nolint:exhaustive // we only support a few power actions right now. case bmcv1alpha1.PowerOn: if state != bmcv1alpha1.On { log.Info("requeuing task", "requeueAfter", powerActionRequeueAfter) @@ -254,7 +255,7 @@ func (r *TaskReconciler) checkTaskStatus(ctx context.Context, log logr.Logger, t func (r *TaskReconciler) patchStatus(ctx context.Context, task *bmcv1alpha1.Task, patch client.Patch) error { err := r.client.Status().Patch(ctx, task, patch) if err != nil { - return fmt.Errorf("failed to patch Task %s/%s status: %v", task.Namespace, task.Name, err) + return fmt.Errorf("failed to patch Task %s/%s status: %w", task.Namespace, task.Name, err) } return nil diff --git a/controllers/task_controller_test.go b/controllers/task_controller_test.go index af0b8e4..43951ce 100644 --- a/controllers/task_controller_test.go +++ b/controllers/task_controller_test.go @@ -6,193 +6,59 @@ import ( "testing" "time" - "github.com/bmc-toolbox/bmclib/v2/bmc" - "github.com/go-logr/logr" - "github.com/golang/mock/gomock" - "github.com/onsi/gomega" + "github.com/google/go-cmp/cmp" + "github.com/tinkerbell/rufio/api/v1alpha1" + "github.com/tinkerbell/rufio/controllers" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/reconcile" - - "github.com/tinkerbell/rufio/api/v1alpha1" - "github.com/tinkerbell/rufio/controllers" - "github.com/tinkerbell/rufio/controllers/mocks" ) -func TestTaskReconciler_ReconcilePower(t *testing.T) { - for name, tt := range map[string]struct { - configureClientCalls func(expect *mocks.MockBMCClientMockRecorder) - action v1alpha1.Action - }{ - "PowerOn": { - configureClientCalls: func(expect *mocks.MockBMCClientMockRecorder) { - gomock.InOrder( - expect.SetPowerState(gomock.Any(), string(v1alpha1.PowerOn)).Return(true, nil), - expect.GetMetadata().Return(bmc.Metadata{}), - expect.Close(gomock.Any()).Return(nil), - expect.GetMetadata().Return(bmc.Metadata{}), - expect.GetPowerState(gomock.Any()).Return("on", nil), - expect.Close(gomock.Any()).Return(nil), - expect.GetMetadata().Return(bmc.Metadata{}), - ) - }, - action: v1alpha1.Action{PowerAction: v1alpha1.PowerOn.Ptr()}, - }, - "HardOff": { - configureClientCalls: func(expect *mocks.MockBMCClientMockRecorder) { - gomock.InOrder( - expect.SetPowerState(gomock.Any(), string(v1alpha1.PowerHardOff)).Return(true, nil), - expect.GetMetadata().Return(bmc.Metadata{}), - expect.Close(gomock.Any()).Return(nil), - expect.GetMetadata().Return(bmc.Metadata{}), - expect.GetPowerState(gomock.Any()).Return("off", nil), - expect.Close(gomock.Any()).Return(nil), - expect.GetMetadata().Return(bmc.Metadata{}), - ) - }, - action: v1alpha1.Action{PowerAction: v1alpha1.PowerHardOff.Ptr()}, - }, - "SoftOff": { - configureClientCalls: func(expect *mocks.MockBMCClientMockRecorder) { - gomock.InOrder( - expect.SetPowerState(gomock.Any(), string(v1alpha1.PowerSoftOff)).Return(true, nil), - expect.GetMetadata().Return(bmc.Metadata{}), - expect.Close(gomock.Any()).Return(nil), - expect.GetMetadata().Return(bmc.Metadata{}), - expect.GetPowerState(gomock.Any()).Return("off", nil), - expect.Close(gomock.Any()).Return(nil), - expect.GetMetadata().Return(bmc.Metadata{}), - ) - }, - action: v1alpha1.Action{PowerAction: v1alpha1.PowerSoftOff.Ptr()}, - }, - } { - t.Run(name, func(t *testing.T) { - g := gomega.NewWithT(t) - - secret := createSecret() - task := createTask(name, tt.action, secret) - cluster := createKubeClientWithObjects(task, secret) - - bmc := mocks.NewMockBMCClient(gomock.NewController(t)) - tt.configureClientCalls(bmc.EXPECT()) - - reconciler := controllers.NewTaskReconciler(cluster, mockBMCClientFactoryFunc(bmc)) - - request := reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: task.Namespace, - Name: task.Name, - }, - } - - result, err := reconciler.Reconcile(context.Background(), request) - g.Expect(err).To(gomega.Succeed()) - g.Expect(result).To(gomega.Equal(ctrl.Result{})) - - var retrieved v1alpha1.Task - err = cluster.Get(context.Background(), request.NamespacedName, &retrieved) - g.Expect(err).To(gomega.Succeed()) - g.Expect(retrieved.Status.StartTime.Unix()).To(gomega.BeNumerically("~", time.Now().Unix(), 2)) - g.Expect(retrieved.Status.CompletionTime.IsZero()).To(gomega.BeTrue()) - g.Expect(retrieved.Status.Conditions).To(gomega.HaveLen(0)) - - // Ensure re-reconciling a task does sends it into a success state. - result, err = reconciler.Reconcile(context.Background(), request) - g.Expect(err).To(gomega.Succeed()) - g.Expect(result).To(gomega.Equal(reconcile.Result{})) - - err = cluster.Get(context.Background(), request.NamespacedName, &retrieved) - g.Expect(err).To(gomega.Succeed()) - g.Expect(retrieved.Status.CompletionTime.Unix()).To(gomega.BeNumerically("~", time.Now().Unix(), 2)) - g.Expect(retrieved.Status.Conditions).To(gomega.HaveLen(1)) - g.Expect(retrieved.Status.Conditions[0].Type).To(gomega.Equal(v1alpha1.TaskCompleted)) - g.Expect(retrieved.Status.Conditions[0].Status).To(gomega.Equal(v1alpha1.ConditionTrue)) - - var retrieved2 v1alpha1.Task - err = cluster.Get(context.Background(), request.NamespacedName, &retrieved2) - g.Expect(err).To(gomega.Succeed()) - g.Expect(retrieved2).To(gomega.BeEquivalentTo(retrieved)) - }) +func getAction(s string) v1alpha1.Action { + switch s { + case "PowerOn": + return v1alpha1.Action{PowerAction: v1alpha1.PowerOn.Ptr()} + case "HardOff": + return v1alpha1.Action{PowerAction: v1alpha1.PowerHardOff.Ptr()} + case "SoftOff": + return v1alpha1.Action{PowerAction: v1alpha1.PowerSoftOff.Ptr()} + case "BootPXE": + return v1alpha1.Action{OneTimeBootDeviceAction: &v1alpha1.OneTimeBootDeviceAction{Devices: []v1alpha1.BootDevice{v1alpha1.PXE}}} + case "VirtualMedia": + return v1alpha1.Action{VirtualMediaAction: &v1alpha1.VirtualMediaAction{MediaURL: "http://example.com/image.iso", Kind: v1alpha1.VirtualMediaCD}} + default: + return v1alpha1.Action{} } } -func TestTaskReconciler_ReconcileBootDevice(t *testing.T) { - for name, tt := range map[string]struct { - configureClientCalls func(expect *mocks.MockBMCClientMockRecorder) - action v1alpha1.Action +func TestTaskReconcile(t *testing.T) { + tests := map[string]struct { + taskName string + action v1alpha1.Action + provider *testProvider + shouldErr bool + timeoutErr bool }{ - "Set": { - configureClientCalls: func(expect *mocks.MockBMCClientMockRecorder) { - gomock.InOrder( - expect. - SetBootDevice(gomock.Any(), string(v1alpha1.PXE), false, gomock.Any()). - Return(true, nil), - expect.GetMetadata().Return(bmc.Metadata{}), - expect.Close(gomock.Any()).Return(nil), - expect.GetMetadata().Return(bmc.Metadata{}), - expect.Close(gomock.Any()).Return(nil), - expect.GetMetadata().Return(bmc.Metadata{}), - ) - }, - action: v1alpha1.Action{ - OneTimeBootDeviceAction: &v1alpha1.OneTimeBootDeviceAction{ - Devices: []v1alpha1.BootDevice{v1alpha1.PXE}, - }, - }, - }, - "MultipleDevices": { - configureClientCalls: func(expect *mocks.MockBMCClientMockRecorder) { - gomock.InOrder( - expect. - SetBootDevice(gomock.Any(), string(v1alpha1.BIOS), false, gomock.Any()). - Return(true, nil), - expect.GetMetadata().Return(bmc.Metadata{}), - expect.Close(gomock.Any()).Return(nil), - expect.GetMetadata().Return(bmc.Metadata{}), - expect.Close(gomock.Any()).Return(nil), - expect.GetMetadata().Return(bmc.Metadata{}), - ) - }, - action: v1alpha1.Action{ - OneTimeBootDeviceAction: &v1alpha1.OneTimeBootDeviceAction{ - Devices: []v1alpha1.BootDevice{v1alpha1.BIOS, v1alpha1.PXE}, - }, - }, - }, - "EFIBoot": { - configureClientCalls: func(expect *mocks.MockBMCClientMockRecorder) { - gomock.InOrder( - expect.SetBootDevice(gomock.Any(), string(v1alpha1.PXE), false, true).Return(true, nil), - expect.GetMetadata().Return(bmc.Metadata{}), - expect.Close(gomock.Any()).Return(nil), - expect.GetMetadata().Return(bmc.Metadata{}), - expect.Close(gomock.Any()).Return(nil), - expect.GetMetadata().Return(bmc.Metadata{}), - ) - }, - action: v1alpha1.Action{ - OneTimeBootDeviceAction: &v1alpha1.OneTimeBootDeviceAction{ - Devices: []v1alpha1.BootDevice{v1alpha1.PXE}, - EFIBoot: true, - }, - }, - }, - } { - t.Run(name, func(t *testing.T) { - g := gomega.NewWithT(t) + "success power on": {taskName: "PowerOn", action: getAction("PowerOn"), provider: &testProvider{Powerstate: "on", PowerSetOK: true}}, + "success hard off": {taskName: "HardOff", action: getAction("HardOff"), provider: &testProvider{Powerstate: "off", PowerSetOK: true}}, + "success soft off": {taskName: "SoftOff", action: getAction("SoftOff"), provider: &testProvider{Powerstate: "off", PowerSetOK: true}}, + "success boot pxe": {taskName: "BootPXE", action: getAction("BootPXE"), provider: &testProvider{BootdeviceOK: true}}, + "success virtual media": {taskName: "VirtualMedia", action: getAction("VirtualMedia"), provider: &testProvider{VirtualMediaOK: true}}, + "failure on bmc open": {taskName: "PowerOn", action: getAction("PowerOn"), provider: &testProvider{ErrOpen: errors.New("failed to open")}, shouldErr: true}, + "failure on bmc power on": {taskName: "PowerOn", action: getAction("PowerOn"), provider: &testProvider{ErrPowerStateSet: errors.New("failed to set power state")}, shouldErr: true}, + "failure on set boot device": {taskName: "BootPXE", action: getAction("BootPXE"), provider: &testProvider{ErrBootDeviceSet: errors.New("failed to set boot device")}, shouldErr: true}, + "failure on virtual media": {taskName: "VirtualMedia", action: getAction("VirtualMedia"), provider: &testProvider{ErrVirtualMediaInsert: errors.New("failed to set virtual media")}, shouldErr: true}, + "failure timeout": {taskName: "PowerOn", action: getAction("PowerOn"), provider: &testProvider{Powerstate: "off", PowerSetOK: true}, timeoutErr: true}, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { secret := createSecret() - task := createTask(name, tt.action, secret) + task := createTask(tt.taskName, tt.action, secret) cluster := createKubeClientWithObjects(task, secret) - - bmc := mocks.NewMockBMCClient(gomock.NewController(t)) - tt.configureClientCalls(bmc.EXPECT()) - - reconciler := controllers.NewTaskReconciler(cluster, mockBMCClientFactoryFunc(bmc)) - + reconciler := controllers.NewTaskReconciler(cluster, newTestClient(tt.provider)) request := reconcile.Request{ NamespacedName: types.NamespacedName{ Namespace: task.Namespace, @@ -201,171 +67,85 @@ func TestTaskReconciler_ReconcileBootDevice(t *testing.T) { } result, err := reconciler.Reconcile(context.Background(), request) - g.Expect(err).To(gomega.Succeed()) - g.Expect(result).To(gomega.Equal(ctrl.Result{})) + if !tt.shouldErr && err != nil { + t.Fatalf("expected nil err, got: %v", err) + } + if tt.shouldErr && err == nil { + t.Fatalf("expected err, got: %v", err) + } + if tt.shouldErr { + return + } + if diff := cmp.Diff(result, ctrl.Result{}); diff != "" { + t.Fatalf("expected no diff, got: %v", diff) + } var retrieved v1alpha1.Task - err = cluster.Get(context.Background(), request.NamespacedName, &retrieved) - g.Expect(err).To(gomega.Succeed()) - g.Expect(retrieved.Status.StartTime.Unix()).To(gomega.BeNumerically("~", time.Now().Unix(), 2)) - g.Expect(retrieved.Status.CompletionTime.IsZero()).To(gomega.BeTrue()) - g.Expect(retrieved.Status.Conditions).To(gomega.HaveLen(0)) + if err = cluster.Get(context.Background(), request.NamespacedName, &retrieved); err != nil { + t.Fatalf("expected nil err, got: %v", err) + } + // TODO: g.Expect(retrieved.Status.StartTime.Unix()).To(gomega.BeNumerically("~", time.Now().Unix(), 2)) + if !retrieved.Status.CompletionTime.IsZero() { + t.Fatalf("expected completion time to be zero, got: %v", retrieved.Status.CompletionTime) + } + if len(retrieved.Status.Conditions) != 0 { + t.Fatalf("expected no conditions, got: %v", retrieved.Status.Conditions) + } + + // Timeout check + if tt.timeoutErr { + expired := metav1.NewTime(retrieved.Status.StartTime.Add(-time.Hour)) + retrieved.Status.StartTime = &expired + if err = cluster.Update(context.Background(), &retrieved); err != nil { + t.Fatalf("expected nil err, got: %v", err) + } + + result, err = reconciler.Reconcile(context.Background(), request) + if err == nil { + t.Fatalf("expected err, got: %v", err) + } + if diff := cmp.Diff(result, ctrl.Result{}); diff != "" { + t.Fatalf("expected no diff, got: %v", diff) + } + return + } // Ensure re-reconciling a task does sends it into a success state. result, err = reconciler.Reconcile(context.Background(), request) - g.Expect(err).To(gomega.Succeed()) - g.Expect(result).To(gomega.Equal(reconcile.Result{})) + if err != nil { + t.Fatalf("expected nil err, got: %v", err) + } + if diff := cmp.Diff(result, reconcile.Result{}); diff != "" { + t.Fatalf("expected no diff, got: %v", diff) + } err = cluster.Get(context.Background(), request.NamespacedName, &retrieved) - g.Expect(err).To(gomega.Succeed()) - g.Expect(retrieved.Status.CompletionTime.Unix()).To(gomega.BeNumerically("~", time.Now().Unix(), 2)) - g.Expect(retrieved.Status.Conditions).To(gomega.HaveLen(1)) - g.Expect(retrieved.Status.Conditions[0].Type).To(gomega.Equal(v1alpha1.TaskCompleted)) - g.Expect(retrieved.Status.Conditions[0].Status).To(gomega.Equal(v1alpha1.ConditionTrue)) + if err != nil { + t.Fatalf("expected nil err, got: %v", err) + } + // TODO: g.Expect(retrieved.Status.CompletionTime.Unix()).To(gomega.BeNumerically("~", time.Now().Unix(), 2)) + if len(retrieved.Status.Conditions) != 1 { + t.Fatalf("expected 1 condition, got: %v", retrieved.Status.Conditions) + } + if retrieved.Status.Conditions[0].Type != v1alpha1.TaskCompleted { + t.Fatalf("expected condition type to be %s, got: %s", v1alpha1.TaskCompleted, retrieved.Status.Conditions[0].Type) + } + if retrieved.Status.Conditions[0].Status != v1alpha1.ConditionTrue { + t.Fatalf("expected condition status to be %s, got: %s", v1alpha1.ConditionTrue, retrieved.Status.Conditions[0].Status) + } var retrieved2 v1alpha1.Task err = cluster.Get(context.Background(), request.NamespacedName, &retrieved2) - g.Expect(err).To(gomega.Succeed()) - g.Expect(retrieved2).To(gomega.BeEquivalentTo(retrieved)) - }) - } -} - -func TestTaskReconciler_ReconcileWithBMCClientError(t *testing.T) { - g := gomega.NewWithT(t) - secret := createSecret() - task := createTask("task", v1alpha1.Action{}, secret) - client := createKubeClientWithObjects(task, secret) - - reconciler := controllers.NewTaskReconciler(client, erroringBMCClientFactoryFunc()) - - request := reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: task.Namespace, - Name: task.Name, - }, - } - - _, err := reconciler.Reconcile(context.Background(), request) - g.Expect(err).To(gomega.HaveOccurred()) -} - -func TestTaskReconciler_BMCClientErrors(t *testing.T) { - for name, tt := range map[string]struct { - configureClientCalls func(expect *mocks.MockBMCClientMockRecorder) - action v1alpha1.Action - }{ - "PowerOn": { - configureClientCalls: func(expect *mocks.MockBMCClientMockRecorder) { - gomock.InOrder( - expect. - SetPowerState(gomock.Any(), gomock.Any()). - Return(false, errors.New("power on error")), - expect.GetMetadata().Return(bmc.Metadata{}), - expect.Close(gomock.Any()).Return(nil), - expect.GetMetadata().Return(bmc.Metadata{}), - ) - }, - action: v1alpha1.Action{PowerAction: v1alpha1.PowerOn.Ptr()}, - }, - "BootDevice": { - configureClientCalls: func(expect *mocks.MockBMCClientMockRecorder) { - gomock.InOrder( - expect. - SetBootDevice(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). - Return(false, errors.New("boot device error")), - expect.GetMetadata().Return(bmc.Metadata{}), - expect.Close(gomock.Any()).Return(nil), - expect.GetMetadata().Return(bmc.Metadata{}), - ) - }, - action: v1alpha1.Action{OneTimeBootDeviceAction: &v1alpha1.OneTimeBootDeviceAction{ - Devices: []v1alpha1.BootDevice{v1alpha1.PXE}, - }}, - }, - } { - t.Run(name, func(t *testing.T) { - g := gomega.NewWithT(t) - - secret := createSecret() - task := createTask(name, tt.action, secret) - cluster := createKubeClientWithObjects(task, secret) - - bmc := mocks.NewMockBMCClient(gomock.NewController(t)) - tt.configureClientCalls(bmc.EXPECT()) - - reconciler := controllers.NewTaskReconciler(cluster, mockBMCClientFactoryFunc(bmc)) - - request := reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: task.Namespace, - Name: task.Name, - }, + if err != nil { + t.Fatalf("expected nil err, got: %v", err) + } + if diff := cmp.Diff(retrieved2, retrieved); diff != "" { + t.Fatalf("expected no diff, got: %v", diff) } - - result, err := reconciler.Reconcile(context.Background(), request) - g.Expect(err).To(gomega.HaveOccurred()) - g.Expect(result).To(gomega.Equal(ctrl.Result{})) - - var retrieved v1alpha1.Task - err = cluster.Get(context.Background(), request.NamespacedName, &retrieved) - g.Expect(err).To(gomega.Succeed()) - g.Expect(retrieved.Status.StartTime.Unix()).To(gomega.BeNumerically("~", time.Now().Unix(), 2)) - g.Expect(retrieved.Status.CompletionTime.IsZero()).To(gomega.BeTrue()) - g.Expect(retrieved.Status.Conditions).To(gomega.HaveLen(1)) - g.Expect(retrieved.Status.Conditions[0].Type).To(gomega.Equal(v1alpha1.TaskFailed)) - g.Expect(retrieved.Status.Conditions[0].Status).To(gomega.Equal(v1alpha1.ConditionTrue)) }) } } -func TestTaskReconciler_Timeout(t *testing.T) { - g := gomega.NewWithT(t) - ctx := context.Background() - ctx = ctrl.LoggerInto(ctx, logr.Discard()) - - secret := createSecret() - task := createTask("task", v1alpha1.Action{PowerAction: v1alpha1.PowerOn.Ptr()}, secret) - cluster := createKubeClientWithObjects(task, secret) - - client := mocks.NewMockBMCClient(gomock.NewController(t)) - gomock.InOrder( - client.EXPECT().SetPowerState(gomock.Any(), string(v1alpha1.PowerOn)).Return(true, nil), - client.EXPECT().GetMetadata().Return(bmc.Metadata{}), - client.EXPECT().Close(gomock.Any()).Return(nil), - client.EXPECT().GetMetadata().Return(bmc.Metadata{}), - client.EXPECT().Close(gomock.Any()).Return(nil), - client.EXPECT().GetMetadata().Return(bmc.Metadata{}), - ) - - reconciler := controllers.NewTaskReconciler(cluster, mockBMCClientFactoryFunc(client)) - - request := reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: task.Namespace, - Name: task.Name, - }, - } - - result, err := reconciler.Reconcile(ctx, request) - g.Expect(err).To(gomega.Succeed()) - g.Expect(result).To(gomega.Equal(ctrl.Result{})) - - // Retrieve the task and update the start time to be within the timeout period - var retrieved v1alpha1.Task - err = cluster.Get(ctx, request.NamespacedName, &retrieved) - g.Expect(err).To(gomega.Succeed()) - - expired := metav1.NewTime(retrieved.Status.StartTime.Add(-time.Hour)) - retrieved.Status.StartTime = &expired - err = cluster.Update(ctx, &retrieved) - g.Expect(err).To(gomega.Succeed()) - - result, err = reconciler.Reconcile(ctx, request) - g.Expect(err).To(gomega.HaveOccurred()) - g.Expect(result).To(gomega.Equal(ctrl.Result{})) -} - func createTask(name string, action v1alpha1.Action, secret *corev1.Secret) *v1alpha1.Task { return &v1alpha1.Task{ ObjectMeta: metav1.ObjectMeta{ @@ -385,15 +165,3 @@ func createTask(name string, action v1alpha1.Action, secret *corev1.Secret) *v1a }, } } - -func mockBMCClientFactoryFunc(m *mocks.MockBMCClient) controllers.BMCClientFactoryFunc { - return func(_ context.Context, log logr.Logger, hostIP, port, username, password string) (controllers.BMCClient, error) { - return m, nil - } -} - -func erroringBMCClientFactoryFunc() controllers.BMCClientFactoryFunc { - return func(_ context.Context, log logr.Logger, hostIP, port, username, password string) (controllers.BMCClient, error) { - return nil, errors.New("error") - } -} diff --git a/go.mod b/go.mod index 6fd2edf..ec37bb1 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,8 @@ require ( github.com/bmc-toolbox/bmclib/v2 v2.0.1-0.20230427101248-b5cdfa3ffe02 github.com/go-logr/logr v1.2.4 github.com/go-logr/zerologr v1.2.3 - github.com/golang/mock v1.6.0 - github.com/onsi/ginkgo v1.16.5 - github.com/onsi/gomega v1.27.6 + github.com/google/go-cmp v0.5.9 + github.com/jacobweinstock/registrar v0.4.6 github.com/peterbourgon/ff/v3 v3.3.1 github.com/rs/zerolog v1.29.1 golang.org/x/tools v0.9.1 @@ -29,7 +28,6 @@ require ( github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-logr/zapr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.1 // indirect github.com/go-openapi/swag v0.22.3 // indirect @@ -37,14 +35,12 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect - github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.1.0 // indirect github.com/google/uuid v1.3.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/jacobweinstock/iamt v0.0.0-20230304043040-a6b4a1001123 // indirect - github.com/jacobweinstock/registrar v0.4.6 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -54,7 +50,8 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/nxadm/tail v1.4.8 // indirect + github.com/onsi/ginkgo/v2 v2.9.2 // indirect + github.com/onsi/gomega v1.27.6 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect @@ -64,9 +61,6 @@ require ( github.com/satori/go.uuid v1.2.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stmcginnis/gofish v0.14.0 // indirect - go.uber.org/atomic v1.7.0 // indirect - go.uber.org/multierr v1.6.0 // indirect - go.uber.org/zap v1.24.0 // indirect golang.org/x/exp v0.0.0-20230127130021-4ca2cb1a16b7 // indirect golang.org/x/mod v0.10.0 // indirect golang.org/x/net v0.10.0 // indirect @@ -79,7 +73,6 @@ require ( google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.26.1 // indirect diff --git a/go.sum b/go.sum index 0cc25fb..b2ad595 100644 --- a/go.sum +++ b/go.sum @@ -42,8 +42,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -79,8 +77,6 @@ github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -95,11 +91,9 @@ 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-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= -github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= github.com/go-logr/zerologr v1.2.3 h1:up5N9vcH9Xck3jJkXzgyOxozT14R47IyDODz8LM1KSs= github.com/go-logr/zerologr v1.2.3/go.mod h1:BxwGo7y5zgSHYR1BjbnHPyF/5ZjVKfKxAZANVu6E8Ho= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= @@ -109,7 +103,6 @@ github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -128,8 +121,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -188,7 +179,6 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= @@ -242,16 +232,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -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.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= -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/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/peterbourgon/ff/v3 v3.3.1 h1:XSWvXxeNdgeppLNGGJEAOiXRdX2YMF/LuZfdnqQ1SNc= @@ -313,7 +295,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= @@ -322,21 +303,15 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -375,12 +350,10 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -403,13 +376,11 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -434,10 +405,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -448,10 +417,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -472,11 +438,8 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -519,7 +482,6 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -546,9 +508,7 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -644,12 +604,9 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -660,7 +617,6 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/main.go b/main.go index f1b0c0b..2e6e47f 100644 --- a/main.go +++ b/main.go @@ -135,7 +135,7 @@ func main() { // Setup the context that's going to be used in controllers and for the manager. ctx := ctrl.SetupSignalHandler() - bmcClientFactory := controllers.NewBMCClientFactoryFunc(bmcConnectTimeout) + bmcClientFactory := controllers.NewClientFunc(bmcConnectTimeout) // Setup controller reconcilers setupReconcilers(ctx, mgr, bmcClientFactory) @@ -169,7 +169,7 @@ func newClientConfig(kubeAPIServer, kubeconfig string) clientcmd.ClientConfig { } // setupReconcilers initializes the controllers with the Manager. -func setupReconcilers(ctx context.Context, mgr ctrl.Manager, bmcClientFactory controllers.BMCClientFactoryFunc) { +func setupReconcilers(ctx context.Context, mgr ctrl.Manager, bmcClientFactory controllers.ClientFunc) { err := (controllers.NewMachineReconciler( mgr.GetClient(), mgr.GetEventRecorderFor("machine-controller"), From e67df4f973a2eff2b11a925801d7ddb6e88760e9 Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Fri, 5 May 2023 17:05:46 -0600 Subject: [PATCH 3/6] Add code coverage badge Signed-off-by: Jacob Weinstock --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8e03759..533c75a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Rufio ![For each commit and PR](https://github.com/tinkerbell/rufio/workflows/For%20each%20commit%20and%20PR/badge.svg) +[![codecov](https://codecov.io/gh/tinkerbell/rufio/branch/main/graph/badge.svg)](https://codecov.io/gh/tinkerbell/rufio) ## Description From b4c46aa78c43b9932e4fc39ed4372191b6783ba4 Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Sat, 6 May 2023 10:07:45 -0600 Subject: [PATCH 4/6] Rename files and imports: Files in the controller package are already namespaced and don't need "_controller" in their name. Signed-off-by: Jacob Weinstock --- controllers/{bmc_client.go => client.go} | 26 ---------- controllers/{job_controller.go => job.go} | 52 +++++++++---------- .../{job_controller_test.go => job_test.go} | 0 .../{machine_controller.go => machine.go} | 24 ++++----- ...ine_controller_test.go => machine_test.go} | 12 ++--- controllers/{task_controller.go => task.go} | 34 ++++++------ .../{task_controller_test.go => task_test.go} | 0 main.go | 4 +- 8 files changed, 63 insertions(+), 89 deletions(-) rename controllers/{bmc_client.go => client.go} (56%) rename controllers/{job_controller.go => job.go} (79%) rename controllers/{job_controller_test.go => job_test.go} (100%) rename controllers/{machine_controller.go => machine.go} (90%) rename controllers/{machine_controller_test.go => machine_test.go} (93%) rename controllers/{task_controller.go => task.go} (87%) rename controllers/{task_controller_test.go => task_test.go} (100%) diff --git a/controllers/bmc_client.go b/controllers/client.go similarity index 56% rename from controllers/bmc_client.go rename to controllers/client.go index 5d66a53..f1c4367 100644 --- a/controllers/bmc_client.go +++ b/controllers/client.go @@ -9,32 +9,6 @@ import ( "github.com/go-logr/logr" ) -/* -// BMCClient represents a baseboard management controller client. It defines a set of methods to -// connect and interact with a BMC. -type BMCClient interface { - // Close ends the connection with the bmc. - Close(ctx context.Context) error - - // GetPowerState fetches the current power status of the bmc. - GetPowerState(ctx context.Context) (string, error) - - // SetPowerState power controls the bmc to the input power state. - SetPowerState(ctx context.Context, state string) (bool, error) - - // SetBootDevice sets the boot device on the bmc. Currently this sets the first boot device. - // setPersistent, if true will set the boot device permanently. If false, sets one time boot. - // efiBoot, if true passes efiboot options while setting boot device. - SetBootDevice(ctx context.Context, bootDevice string, setPersistent, efiBoot bool) (bool, error) - - // SetVirtualMedia ejects existing virtual media and then if mediaUrl isn't empty, instructs - // the bmc to download virtual media of the specified kind from mediaUrl. Returns true on success. - SetVirtualMedia(ctx context.Context, kind, mediaUrl string) (bool, error) - // GetMetadata returns the metadata of the bmc client. - GetMetadata() bmc.Metadata -} -*/ - // ClientFunc defines a func that returns a bmclib.Client. type ClientFunc func(ctx context.Context, log logr.Logger, hostIP, port, username, password string) (*bmclib.Client, error) diff --git a/controllers/job_controller.go b/controllers/job.go similarity index 79% rename from controllers/job_controller.go rename to controllers/job.go index 8ad5367..5b3ae3c 100644 --- a/controllers/job_controller.go +++ b/controllers/job.go @@ -29,7 +29,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/source" - bmcv1alpha1 "github.com/tinkerbell/rufio/api/v1alpha1" + "github.com/tinkerbell/rufio/api/v1alpha1" ) // Index key for Job Owner Name. @@ -59,7 +59,7 @@ func (r *JobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R logger.Info("Reconciling Job") // Fetch the job object - job := &bmcv1alpha1.Job{} + job := &v1alpha1.Job{} err := r.client.Get(ctx, req.NamespacedName, job) if err != nil { if apierrors.IsNotFound(err) { @@ -76,8 +76,8 @@ func (r *JobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R } // Job is Completed or Failed is noop. - if job.HasCondition(bmcv1alpha1.JobCompleted, bmcv1alpha1.ConditionTrue) || - job.HasCondition(bmcv1alpha1.JobFailed, bmcv1alpha1.ConditionTrue) { + if job.HasCondition(v1alpha1.JobCompleted, v1alpha1.ConditionTrue) || + job.HasCondition(v1alpha1.JobFailed, v1alpha1.ConditionTrue) { return ctrl.Result{}, nil } @@ -88,26 +88,26 @@ func (r *JobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R return r.doReconcile(ctx, job, jobPatch) } -func (r *JobReconciler) doReconcile(ctx context.Context, job *bmcv1alpha1.Job, jobPatch client.Patch) (ctrl.Result, error) { +func (r *JobReconciler) doReconcile(ctx context.Context, job *v1alpha1.Job, jobPatch client.Patch) (ctrl.Result, error) { // Check if Job is not currently Running // Initialize the StartTime for the Job // Set the Job to Running condition True - if !job.HasCondition(bmcv1alpha1.JobRunning, bmcv1alpha1.ConditionTrue) { + if !job.HasCondition(v1alpha1.JobRunning, v1alpha1.ConditionTrue) { now := metav1.Now() job.Status.StartTime = &now - job.SetCondition(bmcv1alpha1.JobRunning, bmcv1alpha1.ConditionTrue) + job.SetCondition(v1alpha1.JobRunning, v1alpha1.ConditionTrue) } // Get Machine object for the Job // Requeue if error - machine := &bmcv1alpha1.Machine{} + machine := &v1alpha1.Machine{} err := r.getMachine(ctx, job.Spec.MachineRef, machine) if err != nil { return ctrl.Result{}, fmt.Errorf("get Job %s/%s MachineRef: %w", job.Namespace, job.Name, err) } // List all Task owned by Job - tasks := &bmcv1alpha1.TaskList{} + tasks := &v1alpha1.TaskList{} err = r.client.List(ctx, tasks, client.MatchingFields{jobOwnerKey: job.Name}) if err != nil { return ctrl.Result{}, fmt.Errorf("failed to list owned Tasks for Job %s/%s: %w", job.Namespace, job.Name, err) @@ -119,14 +119,14 @@ func (r *JobReconciler) doReconcile(ctx context.Context, job *bmcv1alpha1.Job, j // Set the Job condition Failed True if Task has failed. // If the Task has neither Completed or Failed is noop. for _, task := range tasks.Items { - if task.HasCondition(bmcv1alpha1.TaskCompleted, bmcv1alpha1.ConditionTrue) { + if task.HasCondition(v1alpha1.TaskCompleted, v1alpha1.ConditionTrue) { completedTasksCount++ continue } - if task.HasCondition(bmcv1alpha1.TaskFailed, bmcv1alpha1.ConditionTrue) { + if task.HasCondition(v1alpha1.TaskFailed, v1alpha1.ConditionTrue) { err := fmt.Errorf("task %s/%s failed", task.Namespace, task.Name) - job.SetCondition(bmcv1alpha1.JobFailed, bmcv1alpha1.ConditionTrue, bmcv1alpha1.WithJobConditionMessage(err.Error())) + job.SetCondition(v1alpha1.JobFailed, v1alpha1.ConditionTrue, v1alpha1.WithJobConditionMessage(err.Error())) patchErr := r.patchStatus(ctx, job, jobPatch) if patchErr != nil { return ctrl.Result{}, utilerrors.NewAggregate([]error{patchErr, err}) @@ -142,7 +142,7 @@ func (r *JobReconciler) doReconcile(ctx context.Context, job *bmcv1alpha1.Job, j // Set the Task CompletionTime // Set Task Condition Completed True if completedTasksCount == len(job.Spec.Tasks) { - job.SetCondition(bmcv1alpha1.JobCompleted, bmcv1alpha1.ConditionTrue) + job.SetCondition(v1alpha1.JobCompleted, v1alpha1.ConditionTrue) now := metav1.Now() job.Status.CompletionTime = &now err = r.patchStatus(ctx, job, jobPatch) @@ -152,7 +152,7 @@ func (r *JobReconciler) doReconcile(ctx context.Context, job *bmcv1alpha1.Job, j // Create the first Task for the Job if err := r.createTaskWithOwner(ctx, *job, completedTasksCount, machine.Spec.Connection); err != nil { // Set the Job condition Failed True - job.SetCondition(bmcv1alpha1.JobFailed, bmcv1alpha1.ConditionTrue, bmcv1alpha1.WithJobConditionMessage(err.Error())) + job.SetCondition(v1alpha1.JobFailed, v1alpha1.ConditionTrue, v1alpha1.WithJobConditionMessage(err.Error())) patchErr := r.patchStatus(ctx, job, jobPatch) if patchErr != nil { return ctrl.Result{}, utilerrors.NewAggregate([]error{patchErr, err}) @@ -167,7 +167,7 @@ func (r *JobReconciler) doReconcile(ctx context.Context, job *bmcv1alpha1.Job, j } // getMachine Gets the Machine from MachineRef. -func (r *JobReconciler) getMachine(ctx context.Context, reference bmcv1alpha1.MachineRef, machine *bmcv1alpha1.Machine) error { +func (r *JobReconciler) getMachine(ctx context.Context, reference v1alpha1.MachineRef, machine *v1alpha1.Machine) error { key := types.NamespacedName{Namespace: reference.Namespace, Name: reference.Name} err := r.client.Get(ctx, key, machine) if err != nil { @@ -181,11 +181,11 @@ func (r *JobReconciler) getMachine(ctx context.Context, reference bmcv1alpha1.Ma } // createTaskWithOwner creates a Task object with an OwnerReference set to the Job. -func (r *JobReconciler) createTaskWithOwner(ctx context.Context, job bmcv1alpha1.Job, taskIndex int, conn bmcv1alpha1.Connection) error { +func (r *JobReconciler) createTaskWithOwner(ctx context.Context, job v1alpha1.Job, taskIndex int, conn v1alpha1.Connection) error { isController := true - task := &bmcv1alpha1.Task{ + task := &v1alpha1.Task{ ObjectMeta: metav1.ObjectMeta{ - Name: bmcv1alpha1.FormatTaskName(job, taskIndex), + Name: v1alpha1.FormatTaskName(job, taskIndex), Namespace: job.Namespace, OwnerReferences: []metav1.OwnerReference{ { @@ -197,7 +197,7 @@ func (r *JobReconciler) createTaskWithOwner(ctx context.Context, job bmcv1alpha1 }, }, }, - Spec: bmcv1alpha1.TaskSpec{ + Spec: v1alpha1.TaskSpec{ Task: job.Spec.Tasks[taskIndex], Connection: conn, }, @@ -212,7 +212,7 @@ func (r *JobReconciler) createTaskWithOwner(ctx context.Context, job bmcv1alpha1 } // patchStatus patches the specified patch on the Job. -func (r *JobReconciler) patchStatus(ctx context.Context, job *bmcv1alpha1.Job, patch client.Patch) error { +func (r *JobReconciler) patchStatus(ctx context.Context, job *v1alpha1.Job, patch client.Patch) error { err := r.client.Status().Patch(ctx, job, patch) if err != nil { return fmt.Errorf("failed to patch Job %s/%s status: %w", job.Namespace, job.Name, err) @@ -225,7 +225,7 @@ func (r *JobReconciler) patchStatus(ctx context.Context, job *bmcv1alpha1.Job, p func (r *JobReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error { if err := mgr.GetFieldIndexer().IndexField( ctx, - &bmcv1alpha1.Task{}, + &v1alpha1.Task{}, jobOwnerKey, TaskOwnerIndexFunc, ); err != nil { @@ -233,11 +233,11 @@ func (r *JobReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) } return ctrl.NewControllerManagedBy(mgr). - For(&bmcv1alpha1.Job{}). + For(&v1alpha1.Job{}). Watches( - &source.Kind{Type: &bmcv1alpha1.Task{}}, + &source.Kind{Type: &v1alpha1.Task{}}, &handler.EnqueueRequestForOwner{ - OwnerType: &bmcv1alpha1.Job{}, + OwnerType: &v1alpha1.Job{}, IsController: true, }). Complete(r) @@ -245,7 +245,7 @@ func (r *JobReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) // TaskOwnerIndexFunc is Indexer func which returns the owner name for obj. func TaskOwnerIndexFunc(obj client.Object) []string { - task, ok := obj.(*bmcv1alpha1.Task) + task, ok := obj.(*v1alpha1.Task) if !ok { return nil } @@ -256,7 +256,7 @@ func TaskOwnerIndexFunc(obj client.Object) []string { } // Check if owner is Job - if owner.Kind != "Job" || owner.APIVersion != bmcv1alpha1.GroupVersion.String() { + if owner.Kind != "Job" || owner.APIVersion != v1alpha1.GroupVersion.String() { return nil } diff --git a/controllers/job_controller_test.go b/controllers/job_test.go similarity index 100% rename from controllers/job_controller_test.go rename to controllers/job_test.go diff --git a/controllers/machine_controller.go b/controllers/machine.go similarity index 90% rename from controllers/machine_controller.go rename to controllers/machine.go index 7863cac..12130e0 100644 --- a/controllers/machine_controller.go +++ b/controllers/machine.go @@ -32,7 +32,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - bmcv1alpha1 "github.com/tinkerbell/rufio/api/v1alpha1" + "github.com/tinkerbell/rufio/api/v1alpha1" ) // MachineReconciler reconciles a Machine object. @@ -57,7 +57,7 @@ func NewMachineReconciler(c client.Client, recorder record.EventRecorder, bmcCli } // machineFieldReconciler defines a function to reconcile Machine spec field. -type machineFieldReconciler func(context.Context, *bmcv1alpha1.Machine, *bmclib.Client) error +type machineFieldReconciler func(context.Context, *v1alpha1.Machine, *bmclib.Client) error //+kubebuilder:rbac:groups=bmc.tinkerbell.org,resources=machines,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=bmc.tinkerbell.org,resources=machines/status,verbs=get;update;patch @@ -72,7 +72,7 @@ func (r *MachineReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct logger.Info("Reconciling Machine") // Fetch the Machine object - machine := &bmcv1alpha1.Machine{} + machine := &v1alpha1.Machine{} err := r.client.Get(ctx, req.NamespacedName, machine) if err != nil { if apierrors.IsNotFound(err) { @@ -95,7 +95,7 @@ func (r *MachineReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct return r.doReconcile(ctx, machine, machinePatch, logger) } -func (r *MachineReconciler) doReconcile(ctx context.Context, bm *bmcv1alpha1.Machine, bmPatch client.Patch, logger logr.Logger) (ctrl.Result, error) { +func (r *MachineReconciler) doReconcile(ctx context.Context, bm *v1alpha1.Machine, bmPatch client.Patch, logger logr.Logger) (ctrl.Result, error) { // Fetching username, password from SecretReference // Requeue if error fetching secret username, password, err := resolveAuthSecretRef(ctx, r.client, bm.Spec.Connection.AuthSecretRef) @@ -107,7 +107,7 @@ func (r *MachineReconciler) doReconcile(ctx context.Context, bm *bmcv1alpha1.Mac bmcClient, err := r.bmcClientFactory(ctx, logger, bm.Spec.Connection.Host, strconv.Itoa(bm.Spec.Connection.Port), username, password) if err != nil { logger.Error(err, "BMC connection failed", "host", bm.Spec.Connection.Host) - bm.SetCondition(bmcv1alpha1.Contactable, bmcv1alpha1.ConditionFalse, bmcv1alpha1.WithMachineConditionMessage(err.Error())) + bm.SetCondition(v1alpha1.Contactable, v1alpha1.ConditionFalse, v1alpha1.WithMachineConditionMessage(err.Error())) result, patchErr := r.patchStatus(ctx, bm, bmPatch) if patchErr != nil { return result, utilerrors.NewAggregate([]error{patchErr, err}) @@ -129,7 +129,7 @@ func (r *MachineReconciler) doReconcile(ctx context.Context, bm *bmcv1alpha1.Mac }() // Setting condition Contactable to True. - bm.SetCondition(bmcv1alpha1.Contactable, bmcv1alpha1.ConditionTrue) + bm.SetCondition(v1alpha1.Contactable, v1alpha1.ConditionTrue) // fieldReconcilers defines Machine spec field reconciler functions fieldReconcilers := []machineFieldReconciler{ @@ -154,7 +154,7 @@ func (r *MachineReconciler) doReconcile(ctx context.Context, bm *bmcv1alpha1.Mac } // reconcilePower ensures the Machine Power is in the desired state. -func (r *MachineReconciler) reconcilePower(ctx context.Context, bm *bmcv1alpha1.Machine, bmcClient *bmclib.Client) error { +func (r *MachineReconciler) reconcilePower(ctx context.Context, bm *v1alpha1.Machine, bmcClient *bmclib.Client) error { rawState, err := bmcClient.GetPowerState(ctx) if err != nil { r.recorder.Eventf(bm, corev1.EventTypeWarning, EventGetPowerStateFailed, "get power state: %v", err) @@ -172,7 +172,7 @@ func (r *MachineReconciler) reconcilePower(ctx context.Context, bm *bmcv1alpha1. } // patchStatus patches the specifies patch on the Machine. -func (r *MachineReconciler) patchStatus(ctx context.Context, bm *bmcv1alpha1.Machine, patch client.Patch) (ctrl.Result, error) { +func (r *MachineReconciler) patchStatus(ctx context.Context, bm *v1alpha1.Machine, patch client.Patch) (ctrl.Result, error) { err := r.client.Status().Patch(ctx, bm, patch) if err != nil { return ctrl.Result{}, fmt.Errorf("failed to patch Machine %s/%s status: %w", bm.Namespace, bm.Name, err) @@ -183,15 +183,15 @@ func (r *MachineReconciler) patchStatus(ctx context.Context, bm *bmcv1alpha1.Mac // convertRawBMCPowerState takes a raw BMC power state response and attempts to convert it to // a PowerState. -func convertRawBMCPowerState(response string) (bmcv1alpha1.PowerState, error) { +func convertRawBMCPowerState(response string) (v1alpha1.PowerState, error) { // Normalize the response string for comparison. response = strings.ToLower(response) switch { case strings.Contains(response, "on"): - return bmcv1alpha1.On, nil + return v1alpha1.On, nil case strings.Contains(response, "off"): - return bmcv1alpha1.Off, nil + return v1alpha1.Off, nil } return "", fmt.Errorf("unknown bmc power state: %v", response) @@ -227,6 +227,6 @@ func resolveAuthSecretRef(ctx context.Context, c client.Client, secretRef corev1 // SetupWithManager sets up the controller with the Manager. func (r *MachineReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - For(&bmcv1alpha1.Machine{}). + For(&v1alpha1.Machine{}). Complete(r) } diff --git a/controllers/machine_controller_test.go b/controllers/machine_test.go similarity index 93% rename from controllers/machine_controller_test.go rename to controllers/machine_test.go index 0793d22..15c1d07 100644 --- a/controllers/machine_controller_test.go +++ b/controllers/machine_test.go @@ -13,7 +13,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/reconcile" - bmcv1alpha1 "github.com/tinkerbell/rufio/api/v1alpha1" + "github.com/tinkerbell/rufio/api/v1alpha1" "github.com/tinkerbell/rufio/controllers" ) @@ -53,7 +53,7 @@ func TestMachineReconcile(t *testing.T) { for name, tt := range tests { t.Run(name, func(t *testing.T) { scheme := runtime.NewScheme() - _ = bmcv1alpha1.AddToScheme(scheme) + _ = v1alpha1.AddToScheme(scheme) _ = corev1.AddToScheme(scheme) clientBuilder := fake.NewClientBuilder() @@ -86,14 +86,14 @@ func TestMachineReconcile(t *testing.T) { } } -func createMachine() *bmcv1alpha1.Machine { - return &bmcv1alpha1.Machine{ +func createMachine() *v1alpha1.Machine { + return &v1alpha1.Machine{ ObjectMeta: metav1.ObjectMeta{ Name: "test-bm", Namespace: "test-namespace", }, - Spec: bmcv1alpha1.MachineSpec{ - Connection: bmcv1alpha1.Connection{ + Spec: v1alpha1.MachineSpec{ + Connection: v1alpha1.Connection{ Host: "0.0.0.0", Port: 623, AuthSecretRef: corev1.SecretReference{ diff --git a/controllers/task_controller.go b/controllers/task.go similarity index 87% rename from controllers/task_controller.go rename to controllers/task.go index fe36f30..da46c70 100644 --- a/controllers/task_controller.go +++ b/controllers/task.go @@ -27,7 +27,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - bmcv1alpha1 "github.com/tinkerbell/rufio/api/v1alpha1" + "github.com/tinkerbell/rufio/api/v1alpha1" ) const powerActionRequeueAfter = 3 * time.Second @@ -58,7 +58,7 @@ func (r *TaskReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl. logger.Info("Reconciling Task") // Fetch the Task object - task := &bmcv1alpha1.Task{} + task := &v1alpha1.Task{} if err := r.client.Get(ctx, req.NamespacedName, task); err != nil { if apierrors.IsNotFound(err) { return ctrl.Result{}, nil @@ -74,8 +74,8 @@ func (r *TaskReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl. } // Task is Completed or Failed is noop. - if task.HasCondition(bmcv1alpha1.TaskFailed, bmcv1alpha1.ConditionTrue) || - task.HasCondition(bmcv1alpha1.TaskCompleted, bmcv1alpha1.ConditionTrue) { + if task.HasCondition(v1alpha1.TaskFailed, v1alpha1.ConditionTrue) || + task.HasCondition(v1alpha1.TaskCompleted, v1alpha1.ConditionTrue) { return ctrl.Result{}, nil } @@ -87,7 +87,7 @@ func (r *TaskReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl. return r.doReconcile(ctx, task, taskPatch, logger) } -func (r *TaskReconciler) doReconcile(ctx context.Context, task *bmcv1alpha1.Task, taskPatch client.Patch, logger logr.Logger) (ctrl.Result, error) { +func (r *TaskReconciler) doReconcile(ctx context.Context, task *v1alpha1.Task, taskPatch client.Patch, logger logr.Logger) (ctrl.Result, error) { // Fetching username, password from SecretReference in Connection. // Requeue if error fetching secret username, password, err := resolveAuthSecretRef(ctx, r.client, task.Spec.Connection.AuthSecretRef) @@ -99,7 +99,7 @@ func (r *TaskReconciler) doReconcile(ctx context.Context, task *bmcv1alpha1.Task bmcClient, err := r.bmcClientFactory(ctx, logger, task.Spec.Connection.Host, strconv.Itoa(task.Spec.Connection.Port), username, password) if err != nil { logger.Error(err, "BMC connection failed", "host", task.Spec.Connection.Host) - task.SetCondition(bmcv1alpha1.TaskFailed, bmcv1alpha1.ConditionTrue, bmcv1alpha1.WithTaskConditionMessage(fmt.Sprintf("Failed to connect to BMC: %v", err))) + task.SetCondition(v1alpha1.TaskFailed, v1alpha1.ConditionTrue, v1alpha1.WithTaskConditionMessage(fmt.Sprintf("Failed to connect to BMC: %v", err))) patchErr := r.patchStatus(ctx, task, taskPatch) if patchErr != nil { return ctrl.Result{}, utilerrors.NewAggregate([]error{patchErr, err}) @@ -127,7 +127,7 @@ func (r *TaskReconciler) doReconcile(ctx context.Context, task *bmcv1alpha1.Task if jobRunningTime >= 10*time.Minute { timeOutErr := fmt.Errorf("bmc task timeout: %d", jobRunningTime) // Set Task Condition Failed True - task.SetCondition(bmcv1alpha1.TaskFailed, bmcv1alpha1.ConditionTrue, bmcv1alpha1.WithTaskConditionMessage(timeOutErr.Error())) + task.SetCondition(v1alpha1.TaskFailed, v1alpha1.ConditionTrue, v1alpha1.WithTaskConditionMessage(timeOutErr.Error())) patchErr := r.patchStatus(ctx, task, taskPatch) if patchErr != nil { return ctrl.Result{}, utilerrors.NewAggregate([]error{patchErr, timeOutErr}) @@ -149,7 +149,7 @@ func (r *TaskReconciler) doReconcile(ctx context.Context, task *bmcv1alpha1.Task now := metav1.Now() task.Status.CompletionTime = &now // Set Task Condition Completed True - task.SetCondition(bmcv1alpha1.TaskCompleted, bmcv1alpha1.ConditionTrue) + task.SetCondition(v1alpha1.TaskCompleted, v1alpha1.ConditionTrue) if err := r.patchStatus(ctx, task, taskPatch); err != nil { return result, err } @@ -167,7 +167,7 @@ func (r *TaskReconciler) doReconcile(ctx context.Context, task *bmcv1alpha1.Task md := bmcClient.GetMetadata() logger.Info("failed to perform action", "providersAttempted", md.ProvidersAttempted, "action", task.Spec.Task) // Set Task Condition Failed True - task.SetCondition(bmcv1alpha1.TaskFailed, bmcv1alpha1.ConditionTrue, bmcv1alpha1.WithTaskConditionMessage(err.Error())) + task.SetCondition(v1alpha1.TaskFailed, v1alpha1.ConditionTrue, v1alpha1.WithTaskConditionMessage(err.Error())) patchErr := r.patchStatus(ctx, task, taskPatch) if patchErr != nil { return ctrl.Result{}, utilerrors.NewAggregate([]error{patchErr, err}) @@ -184,7 +184,7 @@ func (r *TaskReconciler) doReconcile(ctx context.Context, task *bmcv1alpha1.Task } // runTask executes the defined Task in a Task. -func (r *TaskReconciler) runTask(ctx context.Context, logger logr.Logger, task bmcv1alpha1.Action, bmcClient *bmclib.Client) error { +func (r *TaskReconciler) runTask(ctx context.Context, logger logr.Logger, task v1alpha1.Action, bmcClient *bmclib.Client) error { if task.PowerAction != nil { ok, err := bmcClient.SetPowerState(ctx, string(*task.PowerAction)) if err != nil { @@ -219,7 +219,7 @@ func (r *TaskReconciler) runTask(ctx context.Context, logger logr.Logger, task b // checkTaskStatus checks if Task action completed. // This is currently limited only to a few PowerAction types. -func (r *TaskReconciler) checkTaskStatus(ctx context.Context, log logr.Logger, task bmcv1alpha1.Action, bmcClient *bmclib.Client) (ctrl.Result, error) { +func (r *TaskReconciler) checkTaskStatus(ctx context.Context, log logr.Logger, task v1alpha1.Action, bmcClient *bmclib.Client) (ctrl.Result, error) { // TODO(pokearu): Extend to all actions. if task.PowerAction != nil { rawState, err := bmcClient.GetPowerState(ctx) @@ -235,13 +235,13 @@ func (r *TaskReconciler) checkTaskStatus(ctx context.Context, log logr.Logger, t } switch *task.PowerAction { //nolint:exhaustive // we only support a few power actions right now. - case bmcv1alpha1.PowerOn: - if state != bmcv1alpha1.On { + case v1alpha1.PowerOn: + if state != v1alpha1.On { log.Info("requeuing task", "requeueAfter", powerActionRequeueAfter) return ctrl.Result{RequeueAfter: powerActionRequeueAfter}, nil } - case bmcv1alpha1.PowerHardOff, bmcv1alpha1.PowerSoftOff: - if bmcv1alpha1.Off != state { + case v1alpha1.PowerHardOff, v1alpha1.PowerSoftOff: + if v1alpha1.Off != state { return ctrl.Result{RequeueAfter: powerActionRequeueAfter}, nil } } @@ -252,7 +252,7 @@ func (r *TaskReconciler) checkTaskStatus(ctx context.Context, log logr.Logger, t } // patchStatus patches the specified patch on the Task. -func (r *TaskReconciler) patchStatus(ctx context.Context, task *bmcv1alpha1.Task, patch client.Patch) error { +func (r *TaskReconciler) patchStatus(ctx context.Context, task *v1alpha1.Task, patch client.Patch) error { err := r.client.Status().Patch(ctx, task, patch) if err != nil { return fmt.Errorf("failed to patch Task %s/%s status: %w", task.Namespace, task.Name, err) @@ -264,6 +264,6 @@ func (r *TaskReconciler) patchStatus(ctx context.Context, task *bmcv1alpha1.Task // SetupWithManager sets up the controller with the Manager. func (r *TaskReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - For(&bmcv1alpha1.Task{}). + For(&v1alpha1.Task{}). Complete(r) } diff --git a/controllers/task_controller_test.go b/controllers/task_test.go similarity index 100% rename from controllers/task_controller_test.go rename to controllers/task_test.go diff --git a/main.go b/main.go index 2e6e47f..aa8b550 100644 --- a/main.go +++ b/main.go @@ -39,7 +39,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/healthz" - bmcv1alpha1 "github.com/tinkerbell/rufio/api/v1alpha1" + "github.com/tinkerbell/rufio/api/v1alpha1" "github.com/tinkerbell/rufio/controllers" //+kubebuilder:scaffold:imports ) @@ -56,7 +56,7 @@ var ( func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) - utilruntime.Must(bmcv1alpha1.AddToScheme(scheme)) + utilruntime.Must(v1alpha1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme } From 4b6b31e496187895d1c340fd170585885d5bc662 Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Sun, 7 May 2023 20:56:48 -0600 Subject: [PATCH 5/6] Split lines in TestJobReconcile: The lines were a bit long for reading. Signed-off-by: Jacob Weinstock --- controllers/job_test.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/controllers/job_test.go b/controllers/job_test.go index 118ef3b..18671b5 100644 --- a/controllers/job_test.go +++ b/controllers/job_test.go @@ -21,9 +21,20 @@ func TestJobReconcile(t *testing.T) { shouldErr bool testAll bool }{ - "success taskless job": {machine: createMachine(), secret: createSecret(), job: createJob("test", createMachine())}, - "failure unknown machine": {machine: &v1alpha1.Machine{}, secret: createSecret(), job: createJob("test", createMachine()), shouldErr: true}, - "success power on job": {machine: createMachine(), secret: createSecret(), job: createJob("test", createMachine(), getAction("PowerOn")), testAll: true}, + "success taskless job": { + machine: createMachine(), + secret: createSecret(), + job: createJob("test", createMachine()), + }, + "failure unknown machine": { + machine: &v1alpha1.Machine{}, + secret: createSecret(), + job: createJob("test", createMachine()), shouldErr: true}, + "success power on job": { + machine: createMachine(), + secret: createSecret(), + job: createJob("test", createMachine(), getAction("PowerOn")), + testAll: true}, } for name, tt := range tests { From 40bb987bb0e8341ff34dfeb74b94b6d98db0e2f0 Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Wed, 10 May 2023 09:18:30 -0600 Subject: [PATCH 6/6] Fix linting issues Signed-off-by: Jacob Weinstock --- controllers/job_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/controllers/job_test.go b/controllers/job_test.go index 18671b5..ddb7e75 100644 --- a/controllers/job_test.go +++ b/controllers/job_test.go @@ -29,12 +29,14 @@ func TestJobReconcile(t *testing.T) { "failure unknown machine": { machine: &v1alpha1.Machine{}, secret: createSecret(), - job: createJob("test", createMachine()), shouldErr: true}, + job: createJob("test", createMachine()), shouldErr: true, + }, "success power on job": { machine: createMachine(), secret: createSecret(), job: createJob("test", createMachine(), getAction("PowerOn")), - testAll: true}, + testAll: true, + }, } for name, tt := range tests {