diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a87a2f2f8f..b1043e53b0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,13 +1,31 @@ version: 2 updates: + # GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + commit-message: + prefix: ":seedling:" + labels: + - "kind/cleanup" + - "area/ci" + - "ok-to-test" + - "release-note-none" + + # Main Go module - package-ecosystem: "gomod" directory: "/" schedule: interval: "weekly" + day: "monday" commit-message: prefix: ":seedling:" labels: - "kind/cleanup" + - "area/dependency" + - "ok-to-test" + - "release-note-none" groups: dependencies: patterns: @@ -15,22 +33,33 @@ updates: ignore: # Ignore Cluster-API as its upgraded manually. - dependency-name: "sigs.k8s.io/cluster-api*" + update-types: [ "version-update:semver-major", "version-update:semver-minor" ] # Ignore controller-runtime as its upgraded manually. - dependency-name: "sigs.k8s.io/controller-runtime" - # Ignore k8s and its transitives modules as they are upgraded manually - # together with controller-runtime. + update-types: [ "version-update:semver-major", "version-update:semver-minor" ] + # Ignore k8s and its transitives modules as they are upgraded manually together with controller-runtime. - dependency-name: "k8s.io/*" + update-types: [ "version-update:semver-major", "version-update:semver-minor" ] - dependency-name: "go.etcd.io/*" + update-types: [ "version-update:semver-major", "version-update:semver-minor" ] - dependency-name: "google.golang.org/grpc" + update-types: [ "version-update:semver-major", "version-update:semver-minor" ] + # Bumping the kustomize API independently can break compatibility with client-go as they share k8s.io/kube-openapi as a dependency. + - dependency-name: "sigs.k8s.io/kustomize/api" + update-types: [ "version-update:semver-major", "version-update:semver-minor" ] - package-ecosystem: "docker" directory: "/" schedule: interval: "weekly" + day: "monday" commit-message: prefix: ":seedling:" labels: - "kind/cleanup" + - "area/dependency" + - "ok-to-test" + - "release-note-none" groups: dependencies: patterns: @@ -41,10 +70,14 @@ updates: directory: "/hack/tools" schedule: interval: "weekly" + day: "wednesday" commit-message: prefix: ":seedling:" labels: - "kind/cleanup" + - "area/dependency" + - "ok-to-test" + - "release-note-none" groups: dependencies: patterns: @@ -52,35 +85,33 @@ updates: ignore: # Ignore Cluster-API as its upgraded manually. - dependency-name: "sigs.k8s.io/cluster-api*" + update-types: [ "version-update:semver-major", "version-update:semver-minor" ] # Ignore controller-runtime as its upgraded manually. - dependency-name: "sigs.k8s.io/controller-runtime" - # Ignore k8s and its transitives modules as they are upgraded manually - # together with controller-runtime. + update-types: [ "version-update:semver-major", "version-update:semver-minor" ] + # Ignore k8s and its transitives modules as they are upgraded manually together with controller-runtime. - dependency-name: "k8s.io/*" - # Ignore controller-tools as its upgraded manually. - - dependency-name: "sigs.k8s.io/controller-tools" + update-types: [ "version-update:semver-major", "version-update:semver-minor" ] + - dependency-name: "go.etcd.io/*" + update-types: [ "version-update:semver-major", "version-update:semver-minor" ] + - dependency-name: "google.golang.org/grpc" + update-types: [ "version-update:semver-major", "version-update:semver-minor" ] + # Bumping the kustomize API independently can break compatibility with client-go as they share k8s.io/kube-openapi as a dependency. + - dependency-name: "sigs.k8s.io/kustomize/api" + update-types: [ "version-update:semver-major", "version-update:semver-minor" ] - package-ecosystem: "docker" directory: "/hack/tools" schedule: interval: "weekly" + day: "wednesday" commit-message: prefix: ":seedling:" labels: - "kind/cleanup" - groups: - dependencies: - patterns: - - "*" - - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - commit-message: - prefix: ":seedling:" - labels: - - "kind/cleanup" + - "area/dependency" + - "ok-to-test" + - "release-note-none" groups: dependencies: patterns: diff --git a/.github/workflows/pr-golangci-lint.yaml b/.github/workflows/pr-golangci-lint.yaml new file mode 100644 index 0000000000..d5e0e91b7e --- /dev/null +++ b/.github/workflows/pr-golangci-lint.yaml @@ -0,0 +1,33 @@ +name: PR golangci-lint + +on: + pull_request: + types: [opened, edited, synchronize, reopened] + +# Remove all permissions from GITHUB_TOKEN except metadata. +permissions: {} + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + working-directory: + - "" + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # tag=v4.1.1 + - name: Calculate go version + id: vars + run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT + - name: Set up Go + uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # tag=v5.0.0 + with: + go-version: ${{ steps.vars.outputs.go_version }} + - name: golangci-lint + uses: golangci/golangci-lint-action@3cfe3a4abbb849e10058ce4af15d205b6da42804 # tag=v4.0.0 + with: + version: v1.56.1 + args: --out-format=colored-line-number + working-directory: ${{matrix.working-directory}} diff --git a/.golangci.yml b/.golangci.yml index bd41d21015..7925fdfadb 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,79 +1,126 @@ linters: - enable-all: true - disable: - - bidichk - - contextcheck - - cyclop - - dupl - - durationcheck - - errname - - errorlint - - exhaustive - - exhaustivestruct - - exhaustruct - - forcetypeassert - - forbidigo - - funlen - - gochecknoglobals - - gochecknoinits - - gocognit - - godox - - goerr113 - - gofumpt - - golint - - gomnd - - gomoddirectives - - gomodguard - - interfacer - - ireturn - - lll - - makezero - - maligned - - musttag - - nestif - - nilnil - - nlreturn - - nonamedreturns - - nosnakecase - - paralleltest - - promlinter - - scopelint - - sqlclosecheck - - tagliatelle - - tenv - - testpackage - - tparallel - - varnamelen - - wastedassign - - wrapcheck - - wsl - - deadcode - - ifshort - - structcheck - - varcheck - - interfacebloat + disable-all: true + enable: + - asasalint + - asciicheck + - bidichk + - bodyclose + - containedctx + - dogsled + - dupword + - durationcheck + - errcheck + - errchkjson + - exportloopref + - gci + - ginkgolinter + - goconst + - gocritic + - godot + - gofmt + - goimports + - goprintffuncname + - gosec + - gosimple + - govet + - importas + - ineffassign + - loggercheck + - misspell + - nakedret + - nilerr + - noctx + - nolintlint + - nosprintfhostport + - prealloc + - predeclared + - revive + - rowserrcheck + - staticcheck + - stylecheck + - thelper + - typecheck + - unconvert + - unparam + - unused + - usestdlibvars + - whitespace linters-settings: - # Restrict revive to exported. - revive: - # see https://github.com/mgechev/revive#available-rules for details. - ignore-generated-header: true - severity: warning - rules: - - name: exported - severity: warning gci: sections: - standard - default - prefix(sigs.k8s.io/cluster-api) ginkgolinter: - # Suppress the wrong length assertion warning. - suppress-len-assertion: true - # Suppress the wrong nil assertion warning. - suppress-nil-assertion: false - # Suppress the wrong error assertion warning. - suppress-err-assertion: true + forbid-focus-container: true + suppress-len-assertion: true # Suppress the wrong length assertion warning. + suppress-nil-assertion: false # Suppress the wrong nil assertion warning. + suppress-err-assertion: true # Suppress the wrong error assertion warning. + gocritic: + enabled-tags: + - diagnostic + - experimental + - performance + disabled-checks: + - appendAssign + - dupImport # https://github.com/go-critic/go-critic/issues/845 + - evalOrder + - ifElseChain + - octalLiteral + - regexpSimplify + - sloppyReassign + - truncateCmp + - typeDefFirst + - unnamedResult + - unnecessaryDefer + - whyNoLint + - wrapperFunc + - rangeValCopy + - hugeParam + - filepathJoin + - emptyStringTest + godot: + # declarations - for top level declaration comments (default); + # toplevel - for top level comments; + # all - for all comments. + scope: toplevel + exclude: + - '^ \+.*' + - '^ ANCHOR.*' + revive: + rules: + # The following rules are recommended https://github.com/mgechev/revive#recommended-configuration + - name: blank-imports + - name: context-as-argument + - name: context-keys-type + - name: dot-imports + - name: error-return + - name: error-strings + - name: error-naming + - name: exported + - name: if-return + - name: increment-decrement + - name: var-naming + - name: var-declaration + - name: package-comments + - name: range + - name: receiver-naming + - name: time-naming + - name: unexported-return + - name: indent-error-flow + - name: errorf + - name: empty-block + - name: superfluous-else + - name: unreachable-code + - name: redefines-builtin-id + # + # Rules in addition to the recommended configuration above. + # + - name: bool-literal-in-expr + - name: constant-logical-expr + goconst: + ignore-tests: true gosec: excludes: - G307 # Deferring unsafe method "Close" on type "\*os.File" @@ -159,6 +206,10 @@ linters-settings: alias: apimachinerytypes - pkg: "sigs.k8s.io/cluster-api/exp/api/v1beta1" alias: expclusterv1 + nolintlint: + allow-unused: false + allow-leading-space: false + require-specific: true staticcheck: go: "1.21" stylecheck: @@ -178,7 +229,6 @@ issues: # List of regexps of issue texts to exclude, empty list by default. exclude: - (Expect directory permissions to be 0750 or less|Expect file permissions to be 0600 or less) - - "exported: exported (const|function|method|type|var) (.+) should have comment or be unexported" - "exported: (func|type) name will be used as (.+) by other packages, and that stutters; consider calling this (.+)" - (G104|G107|G404|G505|ST1000) - "G108: Profiling endpoint is automatically exposed on /debug/pprof" @@ -188,6 +238,13 @@ issues: - "net/http.Get must not be called" exclude-rules: # Exclude revive's exported for certain packages and code, e.g. tests and fake. + - linters: + - revive + text: "exported: exported method .*\\.(Reconcile|SetupWithManager|SetupWebhookWithManager) should have comment or be unexported" + - linters: + - errcheck + text: Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*print(f|ln)?|os\.(Un)?Setenv). is not checked + # Exclude some packages or code to require comments, for example test code, or fake clients. - linters: - revive text: exported (method|function|type|const) (.+) should have comment or be unexported @@ -229,6 +286,11 @@ issues: - revive text: "var-naming: don't use underscores in Go names; func (.+) should be (.+)" path: .*/defaults.go + # These directives allow the mock and gc packages to be imported with an underscore everywhere. + - linters: + - revive + text: "var-naming: don't use an underscore in package name" + path: .*/.*(mock|gc_).*/.+\.go # Disable unparam "always receives" which might not be really # useful when building libraries. - linters: diff --git a/Makefile b/Makefile index d67ddea923..b42d7a1aec 100644 --- a/Makefile +++ b/Makefile @@ -48,6 +48,7 @@ KUBETEST_CONF_PATH ?= $(abspath $(E2E_DATA_DIR)/kubetest/conformance.yaml) EXP_DIR := exp # Binaries. +GO_INSTALL := ./scripts/go_install.sh GO_APIDIFF_BIN := $(BIN_DIR)/go-apidiff GO_APIDIFF := $(TOOLS_DIR)/$(GO_APIDIFF_BIN) CLUSTERCTL := $(BIN_DIR)/clusterctl @@ -58,7 +59,10 @@ DEFAULTER_GEN := $(TOOLS_BIN_DIR)/defaulter-gen ENVSUBST := $(TOOLS_BIN_DIR)/envsubst GH := $(TOOLS_BIN_DIR)/gh GOJQ := $(TOOLS_BIN_DIR)/gojq -GOLANGCI_LINT := $(TOOLS_BIN_DIR)/golangci-lint +GOLANGCI_LINT_BIN := golangci-lint +GOLANGCI_LINT_VER := $(shell cat .github/workflows/pr-golangci-lint.yaml | grep [[:space:]]version: | sed 's/.*version: //') +GOLANGCI_LINT := $(abspath $(TOOLS_BIN_DIR)/$(GOLANGCI_LINT_BIN)-$(GOLANGCI_LINT_VER)) +GOLANGCI_LINT_PKG := github.com/golangci/golangci-lint/cmd/golangci-lint KIND := $(TOOLS_BIN_DIR)/kind KUSTOMIZE := $(TOOLS_BIN_DIR)/kustomize MOCKGEN := $(TOOLS_BIN_DIR)/mockgen @@ -290,6 +294,9 @@ generate-go-apis: ## Alias for .build/generate-go-apis .PHONY: modules +$(GOLANGCI_LINT): # Build golangci-lint from tools folder. + GOBIN=$(abspath $(TOOLS_BIN_DIR)) $(GO_INSTALL) $(GOLANGCI_LINT_PKG) $(GOLANGCI_LINT_BIN) $(GOLANGCI_LINT_VER) + .PHONY: lint lint: $(GOLANGCI_LINT) ## Lint codebase $(GOLANGCI_LINT) run -v --fast=false $(GOLANGCI_LINT_EXTRA_ARGS) diff --git a/OWNERS_ALIASES b/OWNERS_ALIASES index 61e20308a9..6b00c9c108 100644 --- a/OWNERS_ALIASES +++ b/OWNERS_ALIASES @@ -30,3 +30,4 @@ aliases: - faiq - fiunchinho - AndiDog + - damdo diff --git a/api/v1beta1/awscluster_types.go b/api/v1beta1/awscluster_types.go index 0e06987b4b..ddb1d2cd5a 100644 --- a/api/v1beta1/awscluster_types.go +++ b/api/v1beta1/awscluster_types.go @@ -207,6 +207,7 @@ type AWSClusterStatus struct { Conditions clusterv1.Conditions `json:"conditions,omitempty"` } +// S3Bucket defines a supporting S3 bucket for the cluster, currently can be optionally used for Ignition. type S3Bucket struct { // ControlPlaneIAMInstanceProfile is a name of the IAMInstanceProfile, which will be allowed // to read control-plane node bootstrap data from S3 Bucket. diff --git a/api/v1beta1/awsclustertemplate_types.go b/api/v1beta1/awsclustertemplate_types.go index 404da0b88a..07e2cf4039 100644 --- a/api/v1beta1/awsclustertemplate_types.go +++ b/api/v1beta1/awsclustertemplate_types.go @@ -53,6 +53,7 @@ func init() { SchemeBuilder.Register(&AWSClusterTemplate{}, &AWSClusterTemplateList{}) } +// AWSClusterTemplateResource defines the desired state of AWSClusterTemplate. type AWSClusterTemplateResource struct { // Standard object's metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata diff --git a/api/v1beta1/conversion_test.go b/api/v1beta1/conversion_test.go index 7579d59aa8..24aa530ac2 100644 --- a/api/v1beta1/conversion_test.go +++ b/api/v1beta1/conversion_test.go @@ -19,9 +19,8 @@ package v1beta1 import ( "testing" - . "github.com/onsi/gomega" - fuzz "github.com/google/gofuzz" + . "github.com/onsi/gomega" "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" "k8s.io/apimachinery/pkg/runtime" runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" @@ -38,7 +37,7 @@ func fuzzFuncs(_ runtimeserializer.CodecFactory) []interface{} { func AWSMachineFuzzer(obj *AWSMachine, c fuzz.Continue) { c.FuzzNoCustom(obj) - + // AWSMachine.Spec.FailureDomain, AWSMachine.Spec.Subnet.ARN and AWSMachine.Spec.AdditionalSecurityGroups.ARN has been removed in v1beta2, so setting it to nil in order to avoid v1beta1 --> v1beta2 --> v1beta1 round trip errors. if obj.Spec.Subnet != nil { obj.Spec.Subnet.ARN = nil @@ -54,7 +53,7 @@ func AWSMachineFuzzer(obj *AWSMachine, c fuzz.Continue) { func AWSMachineTemplateFuzzer(obj *AWSMachineTemplate, c fuzz.Continue) { c.FuzzNoCustom(obj) - + // AWSMachineTemplate.Spec.Template.Spec.FailureDomain, AWSMachineTemplate.Spec.Template.Spec.Subnet.ARN and AWSMachineTemplate.Spec.Template.Spec.AdditionalSecurityGroups.ARN has been removed in v1beta2, so setting it to nil in order to avoid v1beta1 --> v1beta2 --> v1beta round trip errors. if obj.Spec.Template.Spec.Subnet != nil { obj.Spec.Template.Spec.Subnet.ARN = nil @@ -81,16 +80,16 @@ func TestFuzzyConversion(t *testing.T) { })) t.Run("for AWSMachine", utilconversion.FuzzTestFunc(utilconversion.FuzzTestFuncInput{ - Scheme: scheme, - Hub: &v1beta2.AWSMachine{}, - Spoke: &AWSMachine{}, + Scheme: scheme, + Hub: &v1beta2.AWSMachine{}, + Spoke: &AWSMachine{}, FuzzerFuncs: []fuzzer.FuzzerFuncs{fuzzFuncs}, })) t.Run("for AWSMachineTemplate", utilconversion.FuzzTestFunc(utilconversion.FuzzTestFuncInput{ - Scheme: scheme, - Hub: &v1beta2.AWSMachineTemplate{}, - Spoke: &AWSMachineTemplate{}, + Scheme: scheme, + Hub: &v1beta2.AWSMachineTemplate{}, + Spoke: &AWSMachineTemplate{}, FuzzerFuncs: []fuzzer.FuzzerFuncs{fuzzFuncs}, })) diff --git a/api/v1beta1/zz_generated.conversion.go b/api/v1beta1/zz_generated.conversion.go index 6fab23cc8a..30c7102779 100644 --- a/api/v1beta1/zz_generated.conversion.go +++ b/api/v1beta1/zz_generated.conversion.go @@ -1938,6 +1938,8 @@ func Convert_v1beta1_Ignition_To_v1beta2_Ignition(in *Ignition, out *v1beta2.Ign func autoConvert_v1beta2_Ignition_To_v1beta1_Ignition(in *v1beta2.Ignition, out *Ignition, s conversion.Scope) error { out.Version = in.Version // WARNING: in.StorageType requires manual conversion: does not exist in peer-type + // WARNING: in.Proxy requires manual conversion: does not exist in peer-type + // WARNING: in.TLS requires manual conversion: does not exist in peer-type return nil } diff --git a/api/v1beta2/awscluster_types.go b/api/v1beta2/awscluster_types.go index 1df6c53b89..add00915cd 100644 --- a/api/v1beta2/awscluster_types.go +++ b/api/v1beta2/awscluster_types.go @@ -166,13 +166,19 @@ type Bastion struct { AMI string `json:"ami,omitempty"` } +// LoadBalancerType defines the type of load balancer to use. type LoadBalancerType string var ( - LoadBalancerTypeClassic = LoadBalancerType("classic") - LoadBalancerTypeELB = LoadBalancerType("elb") - LoadBalancerTypeALB = LoadBalancerType("alb") - LoadBalancerTypeNLB = LoadBalancerType("nlb") + // LoadBalancerTypeClassic is the classic ELB type. + LoadBalancerTypeClassic = LoadBalancerType("classic") + // LoadBalancerTypeELB is the ELB type. + LoadBalancerTypeELB = LoadBalancerType("elb") + // LoadBalancerTypeALB is the ALB type. + LoadBalancerTypeALB = LoadBalancerType("alb") + // LoadBalancerTypeNLB is the NLB type. + LoadBalancerTypeNLB = LoadBalancerType("nlb") + // LoadBalancerTypeDisabled disables the load balancer. LoadBalancerTypeDisabled = LoadBalancerType("disabled") ) @@ -268,6 +274,7 @@ type AWSClusterStatus struct { Conditions clusterv1.Conditions `json:"conditions,omitempty"` } +// S3Bucket defines a supporting S3 bucket for the cluster, currently can be optionally used for Ignition. type S3Bucket struct { // ControlPlaneIAMInstanceProfile is a name of the IAMInstanceProfile, which will be allowed // to read control-plane node bootstrap data from S3 Bucket. diff --git a/api/v1beta2/awsclustertemplate_types.go b/api/v1beta2/awsclustertemplate_types.go index 333cb285c3..e0a827fa3d 100644 --- a/api/v1beta2/awsclustertemplate_types.go +++ b/api/v1beta2/awsclustertemplate_types.go @@ -54,6 +54,7 @@ func init() { SchemeBuilder.Register(&AWSClusterTemplate{}, &AWSClusterTemplateList{}) } +// AWSClusterTemplateResource defines the desired state of AWSClusterTemplateResource. type AWSClusterTemplateResource struct { // Standard object's metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata diff --git a/api/v1beta2/awsmachine_types.go b/api/v1beta2/awsmachine_types.go index 10d8ce0dcb..c4fd5530ad 100644 --- a/api/v1beta2/awsmachine_types.go +++ b/api/v1beta2/awsmachine_types.go @@ -210,6 +210,7 @@ type CloudInit struct { } // Ignition defines options related to the bootstrapping systems where Ignition is used. +// For more information on Ignition configuration, see https://coreos.github.io/butane/specs/ type Ignition struct { // Version defines which version of Ignition will be used to generate bootstrap data. // @@ -237,6 +238,66 @@ type Ignition struct { // +kubebuilder:default="ClusterObjectStore" // +kubebuilder:validation:Enum:="ClusterObjectStore";"UnencryptedUserData" StorageType IgnitionStorageTypeOption `json:"storageType,omitempty"` + + // Proxy defines proxy settings for Ignition. + // Only valid for Ignition versions 3.1 and above. + // +optional + Proxy *IgnitionProxy `json:"proxy,omitempty"` + + // TLS defines TLS settings for Ignition. + // Only valid for Ignition versions 3.1 and above. + // +optional + TLS *IgnitionTLS `json:"tls,omitempty"` +} + +// IgnitionCASource defines the source of the certificate authority to use for Ignition. +// +kubebuilder:validation:MaxLength:=65536 +type IgnitionCASource string + +// IgnitionTLS defines TLS settings for Ignition. +type IgnitionTLS struct { + // CASources defines the list of certificate authorities to use for Ignition. + // The value is the certificate bundle (in PEM format). The bundle can contain multiple concatenated certificates. + // Supported schemes are http, https, tftp, s3, arn, gs, and `data` (RFC 2397) URL scheme. + // + // +optional + // +kubebuilder:validation:MaxItems=64 + CASources []IgnitionCASource `json:"certificateAuthorities,omitempty"` +} + +// IgnitionNoProxy defines the list of domains to not proxy for Ignition. +// +kubebuilder:validation:MaxLength:=2048 +type IgnitionNoProxy string + +// IgnitionProxy defines proxy settings for Ignition. +type IgnitionProxy struct { + // HTTPProxy is the HTTP proxy to use for Ignition. + // A single URL that specifies the proxy server to use for HTTP and HTTPS requests, + // unless overridden by the HTTPSProxy or NoProxy options. + // +optional + HTTPProxy *string `json:"httpProxy,omitempty"` + + // HTTPSProxy is the HTTPS proxy to use for Ignition. + // A single URL that specifies the proxy server to use for HTTPS requests, + // unless overridden by the NoProxy option. + // +optional + HTTPSProxy *string `json:"httpsProxy,omitempty"` + + // NoProxy is the list of domains to not proxy for Ignition. + // Specifies a list of strings to hosts that should be excluded from proxying. + // + // Each value is represented by: + // - An IP address prefix (1.2.3.4) + // - An IP address prefix in CIDR notation (1.2.3.4/8) + // - A domain name + // - A domain name matches that name and all subdomains + // - A domain name with a leading . matches subdomains only + // - A special DNS label (*), indicates that no proxying should be done + // + // An IP address prefix and domain name can also include a literal port number (1.2.3.4:80). + // +optional + // +kubebuilder:validation:MaxItems=64 + NoProxy []IgnitionNoProxy `json:"noProxy,omitempty"` } // AWSMachineStatus defines the observed state of AWSMachine. diff --git a/api/v1beta2/awsmachine_webhook.go b/api/v1beta2/awsmachine_webhook.go index 2fe32083db..8938e01dfb 100644 --- a/api/v1beta2/awsmachine_webhook.go +++ b/api/v1beta2/awsmachine_webhook.go @@ -17,10 +17,17 @@ limitations under the License. package v1beta2 import ( + "encoding/base64" + "fmt" + "net" + "net/url" + "strings" + "github.com/google/go-cmp/cmp" "github.com/pkg/errors" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/webhook" @@ -171,17 +178,132 @@ func (r *AWSMachine) ignitionEnabled() bool { func (r *AWSMachine) validateIgnitionAndCloudInit() field.ErrorList { var allErrs field.ErrorList + if !r.ignitionEnabled() { + return allErrs + } // Feature gate is not enabled but ignition is enabled then send a forbidden error. - if !feature.Gates.Enabled(feature.BootstrapFormatIgnition) && r.ignitionEnabled() { + if !feature.Gates.Enabled(feature.BootstrapFormatIgnition) { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "ignition"), "can be set only if the BootstrapFormatIgnition feature gate is enabled")) } - if r.ignitionEnabled() && r.cloudInitConfigured() { + // If ignition is enabled, cloudInit should not be configured. + if r.cloudInitConfigured() { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "cloudInit"), "cannot be set if spec.ignition is set")) } + // Proxy and TLS are only valid for Ignition versions >= 3.1. + if r.Spec.Ignition.Version == "2.3" || r.Spec.Ignition.Version == "3.0" { + if r.Spec.Ignition.Proxy != nil { + allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "ignition", "proxy"), "cannot be set if spec.ignition.version is 2.3 or 3.0")) + } + if r.Spec.Ignition.TLS != nil { + allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "ignition", "tls"), "cannot be set if spec.ignition.version is 2.3 or 3.0")) + } + } + + allErrs = append(allErrs, r.validateIgnitionProxy()...) + allErrs = append(allErrs, r.validateIgnitionTLS()...) + + return allErrs +} + +func (r *AWSMachine) validateIgnitionProxy() field.ErrorList { + var allErrs field.ErrorList + + if r.Spec.Ignition.Proxy == nil { + return allErrs + } + + // Validate HTTPProxy. + if r.Spec.Ignition.Proxy.HTTPProxy != nil { + // Parse the url to check if it is valid. + _, err := url.Parse(*r.Spec.Ignition.Proxy.HTTPProxy) + if err != nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "ignition", "proxy", "httpProxy"), *r.Spec.Ignition.Proxy.HTTPProxy, "invalid URL")) + } + } + + // Validate HTTPSProxy. + if r.Spec.Ignition.Proxy.HTTPSProxy != nil { + // Parse the url to check if it is valid. + _, err := url.Parse(*r.Spec.Ignition.Proxy.HTTPSProxy) + if err != nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "ignition", "proxy", "httpsProxy"), *r.Spec.Ignition.Proxy.HTTPSProxy, "invalid URL")) + } + } + + // Validate NoProxy. + for _, noProxy := range r.Spec.Ignition.Proxy.NoProxy { + noProxy := string(noProxy) + // Validate here that the value `noProxy` is: + // - A domain name + // - A domain name matches that name and all subdomains + // - A domain name with a leading . matches subdomains only + + // A special DNS label (*). + if noProxy == "*" { + continue + } + // An IP address prefix (1.2.3.4). + if ip := net.ParseIP(noProxy); ip != nil { + continue + } + // An IP address prefix in CIDR notation (1.2.3.4/8). + if _, _, err := net.ParseCIDR(noProxy); err == nil { + continue + } + // An IP or domain name with a port. + if _, _, err := net.SplitHostPort(noProxy); err == nil { + continue + } + // A domain name. + if noProxy[0] == '.' { + // If it starts with a dot, it should be a domain name. + noProxy = noProxy[1:] + } + // Validate that the value matches DNS 1123. + if errs := validation.IsDNS1123Subdomain(noProxy); len(errs) > 0 { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "ignition", "proxy", "noProxy"), noProxy, fmt.Sprintf("invalid noProxy value, please refer to the field documentation: %s", strings.Join(errs, "; ")))) + } + } + + return allErrs +} + +func (r *AWSMachine) validateIgnitionTLS() field.ErrorList { + var allErrs field.ErrorList + + if r.Spec.Ignition.TLS == nil { + return allErrs + } + + for _, source := range r.Spec.Ignition.TLS.CASources { + // Validate that source is RFC 2397 data URL. + u, err := url.Parse(string(source)) + if err != nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "ignition", "tls", "caSources"), source, "invalid URL")) + } + + switch u.Scheme { + case "http", "https", "tftp", "s3", "arn", "gs": + // Valid schemes. + case "data": + // Validate that the data URL is base64 encoded. + i := strings.Index(u.Opaque, ",") + if i < 0 { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "ignition", "tls", "caSources"), source, "invalid data URL")) + } + // Validate that the data URL is base64 encoded. + if _, err := base64.StdEncoding.DecodeString(u.Opaque[i+1:]); err != nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "ignition", "tls", "caSources"), source, "invalid base64 encoding for data url")) + } + default: + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "ignition", "tls", "caSources"), source, "unsupported URL scheme")) + } + } + return allErrs } diff --git a/api/v1beta2/awsmachine_webhook_test.go b/api/v1beta2/awsmachine_webhook_test.go index a2b6ecd607..8588211aa7 100644 --- a/api/v1beta2/awsmachine_webhook_test.go +++ b/api/v1beta2/awsmachine_webhook_test.go @@ -24,8 +24,10 @@ import ( "github.com/aws/aws-sdk-go/aws" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + utilfeature "k8s.io/component-base/featuregate/testing" "k8s.io/utils/ptr" + "sigs.k8s.io/cluster-api-provider-aws/v2/feature" utildefaulting "sigs.k8s.io/cluster-api/util/defaulting" ) @@ -248,9 +250,129 @@ func TestAWSMachineCreate(t *testing.T) { }, wantErr: true, }, + { + name: "ignition proxy and TLS can be from version 3.1", + machine: &AWSMachine{ + Spec: AWSMachineSpec{ + InstanceType: "test", + Ignition: &Ignition{ + Version: "3.1", + Proxy: &IgnitionProxy{ + HTTPProxy: ptr.To("http://proxy.example.com:3128"), + }, + TLS: &IgnitionTLS{ + CASources: []IgnitionCASource{"s3://example.com/ca.pem"}, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "ignition tls with invalid CASources URL", + machine: &AWSMachine{ + Spec: AWSMachineSpec{ + InstanceType: "test", + Ignition: &Ignition{ + Version: "3.1", + TLS: &IgnitionTLS{ + CASources: []IgnitionCASource{"data;;"}, + }, + }, + }, + }, + wantErr: true, + }, + { + name: "ignition proxy with valid URLs, and noproxy", + machine: &AWSMachine{ + Spec: AWSMachineSpec{ + InstanceType: "test", + Ignition: &Ignition{ + Version: "3.1", + Proxy: &IgnitionProxy{ + HTTPProxy: ptr.To("http://proxy.example.com:3128"), + HTTPSProxy: ptr.To("https://proxy.example.com:3128"), + NoProxy: []IgnitionNoProxy{ + "10.0.0.1", // single ip + "example.com", // domain + ".example.com", // all subdomains + "example.com:3128", // domain with port + "10.0.0.1:3128", // ip with port + "10.0.0.0/8", // cidr block + "*", // no proxy wildcard + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "ignition proxy with invalid HTTPProxy URL", + machine: &AWSMachine{ + Spec: AWSMachineSpec{ + InstanceType: "test", + Ignition: &Ignition{ + Version: "3.1", + Proxy: &IgnitionProxy{ + HTTPProxy: ptr.To("*:80"), + }, + }, + }, + }, + wantErr: true, + }, + { + name: "ignition proxy with invalid HTTPSProxy URL", + machine: &AWSMachine{ + Spec: AWSMachineSpec{ + InstanceType: "test", + Ignition: &Ignition{ + Version: "3.1", + Proxy: &IgnitionProxy{ + HTTPSProxy: ptr.To("*:80"), + }, + }, + }, + }, + wantErr: true, + }, + { + name: "ignition proxy with invalid noproxy URL", + machine: &AWSMachine{ + Spec: AWSMachineSpec{ + InstanceType: "test", + Ignition: &Ignition{ + Version: "3.1", + Proxy: &IgnitionProxy{ + NoProxy: []IgnitionNoProxy{"&"}, + }, + }, + }, + }, + wantErr: true, + }, + { + name: "cannot use ignition proxy with version 2.3", + machine: &AWSMachine{ + Spec: AWSMachineSpec{ + InstanceType: "test", + Ignition: &Ignition{ + Version: "2.3.0", + Proxy: &IgnitionProxy{ + HTTPProxy: ptr.To("http://proxy.example.com:3128"), + }, + }, + }, + }, + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + defer utilfeature.SetFeatureGateDuringTest(t, feature.Gates, feature.BootstrapFormatIgnition, true)() + machine := tt.machine.DeepCopy() machine.ObjectMeta = metav1.ObjectMeta{ GenerateName: "machine-", diff --git a/api/v1beta2/awsmachinetemplate_webhook.go b/api/v1beta2/awsmachinetemplate_webhook.go index 30dee37458..426a42882f 100644 --- a/api/v1beta2/awsmachinetemplate_webhook.go +++ b/api/v1beta2/awsmachinetemplate_webhook.go @@ -180,7 +180,7 @@ func (r *AWSMachineTemplateWebhook) ValidateCreate(_ context.Context, raw runtim var allErrs field.ErrorList obj, ok := raw.(*AWSMachineTemplate) if !ok { - return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a VSphereMachineTemplate but got a %T", raw)) + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a AWSMachineTemplate but got a %T", raw)) } spec := obj.Spec.Template.Spec diff --git a/api/v1beta2/doc.go b/api/v1beta2/doc.go index 912b8f6556..4ed8bbddb8 100644 --- a/api/v1beta2/doc.go +++ b/api/v1beta2/doc.go @@ -17,5 +17,5 @@ limitations under the License. // +gencrdrefdocs:force // +groupName=infrastructure.cluster.x-k8s.io -// package v1beta2 contains the v1beta2 API implementation. +// Package v1beta2 contains the v1beta2 API implementation. package v1beta2 diff --git a/api/v1beta2/groupversion_info.go b/api/v1beta2/groupversion_info.go index 7b92eca9fa..1d921ac08c 100644 --- a/api/v1beta2/groupversion_info.go +++ b/api/v1beta2/groupversion_info.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// package v1beta2 contains API Schema definitions for the infrastructure v1beta2 API group +// Package v1beta2 contains API Schema definitions for the infrastructure v1beta2 API group // +kubebuilder:object:generate=true // +groupName=infrastructure.cluster.x-k8s.io package v1beta2 diff --git a/api/v1beta2/network_types.go b/api/v1beta2/network_types.go index 55cb919cdc..d487183025 100644 --- a/api/v1beta2/network_types.go +++ b/api/v1beta2/network_types.go @@ -106,6 +106,7 @@ type TargetGroupHealthCheck struct { type TargetGroupAttribute string var ( + // TargetGroupAttributeEnablePreserveClientIP defines the attribute key for enabling preserve client IP. TargetGroupAttributeEnablePreserveClientIP = "preserve_client_ip.enabled" ) @@ -113,8 +114,11 @@ var ( type LoadBalancerAttribute string var ( - LoadBalancerAttributeEnableLoadBalancingCrossZone = "load_balancing.cross_zone.enabled" - LoadBalancerAttributeIdleTimeTimeoutSeconds = "idle_timeout.timeout_seconds" + // LoadBalancerAttributeEnableLoadBalancingCrossZone defines the attribute key for enabling load balancing cross zone. + LoadBalancerAttributeEnableLoadBalancingCrossZone = "load_balancing.cross_zone.enabled" + // LoadBalancerAttributeIdleTimeTimeoutSeconds defines the attribute key for idle timeout. + LoadBalancerAttributeIdleTimeTimeoutSeconds = "idle_timeout.timeout_seconds" + // LoadBalancerAttributeIdleTimeDefaultTimeoutSecondsInSeconds defines the default idle timeout in seconds. LoadBalancerAttributeIdleTimeDefaultTimeoutSecondsInSeconds = "60" ) diff --git a/api/v1beta2/types.go b/api/v1beta2/types.go index 545c4f320c..55ce2f9cca 100644 --- a/api/v1beta2/types.go +++ b/api/v1beta2/types.go @@ -80,6 +80,7 @@ const ( ExternalResourceGCTasksAnnotation = "aws.cluster.x-k8s.io/external-resource-tasks-gc" ) +// GCTask defines a task to be executed by the garbage collector. type GCTask string var ( @@ -313,6 +314,7 @@ type InstanceMetadataOptions struct { InstanceMetadataTags InstanceMetadataState `json:"instanceMetadataTags,omitempty"` } +// SetDefaults sets the default values for the InstanceMetadataOptions. func (obj *InstanceMetadataOptions) SetDefaults() { if obj.HTTPEndpoint == "" { obj.HTTPEndpoint = InstanceMetadataEndpointStateEnabled diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index fa6fe0e594..ee0dc510c2 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -743,7 +743,7 @@ func (in *AWSMachineSpec) DeepCopyInto(out *AWSMachineSpec) { if in.Ignition != nil { in, out := &in.Ignition, &out.Ignition *out = new(Ignition) - **out = **in + (*in).DeepCopyInto(*out) } if in.SpotMarketOptions != nil { in, out := &in.SpotMarketOptions, &out.SpotMarketOptions @@ -1332,6 +1332,16 @@ func (in *IPv6) DeepCopy() *IPv6 { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Ignition) DeepCopyInto(out *Ignition) { *out = *in + if in.Proxy != nil { + in, out := &in.Proxy, &out.Proxy + *out = new(IgnitionProxy) + (*in).DeepCopyInto(*out) + } + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + *out = new(IgnitionTLS) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Ignition. @@ -1344,6 +1354,56 @@ func (in *Ignition) DeepCopy() *Ignition { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IgnitionProxy) DeepCopyInto(out *IgnitionProxy) { + *out = *in + if in.HTTPProxy != nil { + in, out := &in.HTTPProxy, &out.HTTPProxy + *out = new(string) + **out = **in + } + if in.HTTPSProxy != nil { + in, out := &in.HTTPSProxy, &out.HTTPSProxy + *out = new(string) + **out = **in + } + if in.NoProxy != nil { + in, out := &in.NoProxy, &out.NoProxy + *out = make([]IgnitionNoProxy, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IgnitionProxy. +func (in *IgnitionProxy) DeepCopy() *IgnitionProxy { + if in == nil { + return nil + } + out := new(IgnitionProxy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IgnitionTLS) DeepCopyInto(out *IgnitionTLS) { + *out = *in + if in.CASources != nil { + in, out := &in.CASources, &out.CASources + *out = make([]IgnitionCASource, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IgnitionTLS. +func (in *IgnitionTLS) DeepCopy() *IgnitionTLS { + if in == nil { + return nil + } + out := new(IgnitionTLS) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IngressRule) DeepCopyInto(out *IngressRule) { *out = *in diff --git a/bootstrap/eks/api/v1beta1/conversion_test.go b/bootstrap/eks/api/v1beta1/conversion_test.go index c6b4485354..47dcb9736d 100644 --- a/bootstrap/eks/api/v1beta1/conversion_test.go +++ b/bootstrap/eks/api/v1beta1/conversion_test.go @@ -20,7 +20,6 @@ import ( "testing" . "github.com/onsi/gomega" - runtime "k8s.io/apimachinery/pkg/runtime" v1beta2 "sigs.k8s.io/cluster-api-provider-aws/v2/bootstrap/eks/api/v1beta2" utilconversion "sigs.k8s.io/cluster-api/util/conversion" diff --git a/bootstrap/eks/api/v1beta2/doc.go b/bootstrap/eks/api/v1beta2/doc.go index 2069db82a5..992666159f 100644 --- a/bootstrap/eks/api/v1beta2/doc.go +++ b/bootstrap/eks/api/v1beta2/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package v1beta2 contains API Schema definitions for the Amazon EKS Bootstrap v1beta2 API group. // +gencrdrefdocs:force //nolint: revive // +groupName=bootstrap.cluster.x-k8s.io - package v1beta2 diff --git a/bootstrap/eks/api/v1beta2/eksconfig_webhook.go b/bootstrap/eks/api/v1beta2/eksconfig_webhook.go index e1459ba1dd..30609f6755 100644 --- a/bootstrap/eks/api/v1beta2/eksconfig_webhook.go +++ b/bootstrap/eks/api/v1beta2/eksconfig_webhook.go @@ -42,7 +42,7 @@ func (r *EKSConfig) ValidateCreate() (admission.Warnings, error) { } // ValidateUpdate will do any extra validation when updating a EKSConfig. -func (r *EKSConfig) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { +func (r *EKSConfig) ValidateUpdate(_ runtime.Object) (admission.Warnings, error) { return nil, nil } diff --git a/bootstrap/eks/api/v1beta2/eksconfigtemplate_webhook.go b/bootstrap/eks/api/v1beta2/eksconfigtemplate_webhook.go index fc2504eca4..d6611c40c3 100644 --- a/bootstrap/eks/api/v1beta2/eksconfigtemplate_webhook.go +++ b/bootstrap/eks/api/v1beta2/eksconfigtemplate_webhook.go @@ -42,7 +42,7 @@ func (r *EKSConfigTemplate) ValidateCreate() (admission.Warnings, error) { } // ValidateUpdate will do any extra validation when updating a EKSConfigTemplate. -func (r *EKSConfigTemplate) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { +func (r *EKSConfigTemplate) ValidateUpdate(_ runtime.Object) (admission.Warnings, error) { return nil, nil } diff --git a/bootstrap/eks/api/v1beta2/groupversion_info.go b/bootstrap/eks/api/v1beta2/groupversion_info.go index a93c42785f..7c26521b41 100644 --- a/bootstrap/eks/api/v1beta2/groupversion_info.go +++ b/bootstrap/eks/api/v1beta2/groupversion_info.go @@ -14,10 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -// package v1beta2 contains API Schema definitions for the Amazon EKS Bootstrap v1beta2 API group +// Package v1beta2 contains API Schema definitions for the Amazon EKS Bootstrap v1beta2 API group // +kubebuilder:object:generate=true // +groupName=bootstrap.cluster.x-k8s.io - package v1beta2 import ( diff --git a/bootstrap/eks/controllers/eksconfig_controller.go b/bootstrap/eks/controllers/eksconfig_controller.go index 8f1de94fc3..5aa9425dd5 100644 --- a/bootstrap/eks/controllers/eksconfig_controller.go +++ b/bootstrap/eks/controllers/eksconfig_controller.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package controllers provides a way to reconcile EKSConfig objects. package controllers import ( diff --git a/bootstrap/eks/controllers/suite_test.go b/bootstrap/eks/controllers/suite_test.go index 74cd527bd6..2b61ab258a 100644 --- a/bootstrap/eks/controllers/suite_test.go +++ b/bootstrap/eks/controllers/suite_test.go @@ -42,8 +42,6 @@ func TestMain(m *testing.M) { } func setup() { - // utilruntime.Must(bootstrapv1.AddToScheme(scheme.Scheme)) - // utilruntime.Must(clusterv1.AddToScheme(scheme.Scheme)) utilruntime.Must(ekscontrolplanev1.AddToScheme(scheme.Scheme)) testEnvConfig := helpers.NewTestEnvironmentConfiguration([]string{ path.Join("config", "crd", "bases"), diff --git a/bootstrap/eks/internal/userdata/commands.go b/bootstrap/eks/internal/userdata/commands.go index af7551d8b6..1ee0c85abf 100644 --- a/bootstrap/eks/internal/userdata/commands.go +++ b/bootstrap/eks/internal/userdata/commands.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package userdata provides a way to generate ec2 instance userdata. package userdata const ( diff --git a/bootstrap/eks/internal/userdata/node.go b/bootstrap/eks/internal/userdata/node.go index 7be304cdb7..468f15478f 100644 --- a/bootstrap/eks/internal/userdata/node.go +++ b/bootstrap/eks/internal/userdata/node.go @@ -68,6 +68,7 @@ type NodeInput struct { NTP *eksbootstrapv1.NTP } +// DockerConfigJSONEscaped returns the DockerConfigJSON escaped for use in cloud-init. func (ni *NodeInput) DockerConfigJSONEscaped() string { if ni.DockerConfigJSON == nil || len(*ni.DockerConfigJSON) == 0 { return "''" @@ -76,6 +77,7 @@ func (ni *NodeInput) DockerConfigJSONEscaped() string { return shellescape.Quote(*ni.DockerConfigJSON) } +// BootstrapCommand returns the bootstrap command to be used on a node instance. func (ni *NodeInput) BootstrapCommand() string { if ni.BootstrapCommandOverride != nil && *ni.BootstrapCommandOverride != "" { return *ni.BootstrapCommandOverride diff --git a/cmd/clusterawsadm/ami/helper.go b/cmd/clusterawsadm/ami/helper.go index e8c8a2d9ed..ebc393084c 100644 --- a/cmd/clusterawsadm/ami/helper.go +++ b/cmd/clusterawsadm/ami/helper.go @@ -241,16 +241,14 @@ func findAMI(imagesMap map[string][]*ec2.Image, baseOS, kubernetesVersion string } if val, ok := imagesMap[amiName]; ok && val != nil { return latestAMI(val) - } else { - amiName, err = ec2service.GenerateAmiName(amiNameFormat, baseOS, strings.TrimPrefix(kubernetesVersion, "v")) - if err != nil { - return nil, errors.Wrapf(err, "failed to process ami format: %q", amiNameFormat) - } - if val, ok = imagesMap[amiName]; ok && val != nil { - return latestAMI(val) - } } - + amiName, err = ec2service.GenerateAmiName(amiNameFormat, baseOS, strings.TrimPrefix(kubernetesVersion, "v")) + if err != nil { + return nil, errors.Wrapf(err, "failed to process ami format: %q", amiNameFormat) + } + if val, ok := imagesMap[amiName]; ok && val != nil { + return latestAMI(val) + } return nil, nil } diff --git a/cmd/clusterawsadm/ami/list.go b/cmd/clusterawsadm/ami/list.go index b17166f75f..2b04f81422 100644 --- a/cmd/clusterawsadm/ami/list.go +++ b/cmd/clusterawsadm/ami/list.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package ami provides a way to interact with AWS AMIs. package ami import ( diff --git a/cmd/clusterawsadm/api/ami/v1beta1/scheme/scheme.go b/cmd/clusterawsadm/api/ami/v1beta1/scheme/scheme.go index 1dc2079536..851bbead25 100644 --- a/cmd/clusterawsadm/api/ami/v1beta1/scheme/scheme.go +++ b/cmd/clusterawsadm/api/ami/v1beta1/scheme/scheme.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package scheme provides a way to generate a Scheme and CodecFactory f +// or the bootstrap.aws.infrastructure.cluster.x-k8s.io API group. package scheme import ( diff --git a/cmd/clusterawsadm/api/bootstrap/v1alpha1/scheme/scheme.go b/cmd/clusterawsadm/api/bootstrap/v1alpha1/scheme/scheme.go index fc604a190f..b320f44db3 100644 --- a/cmd/clusterawsadm/api/bootstrap/v1alpha1/scheme/scheme.go +++ b/cmd/clusterawsadm/api/bootstrap/v1alpha1/scheme/scheme.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package scheme provides a way to generate a Scheme and CodecFactory +// for the bootstrap.aws.infrastructure.cluster.x-k8s.io API group. package scheme import ( diff --git a/cmd/clusterawsadm/api/bootstrap/v1beta1/scheme/scheme.go b/cmd/clusterawsadm/api/bootstrap/v1beta1/scheme/scheme.go index d84a39aee5..f70029e383 100644 --- a/cmd/clusterawsadm/api/bootstrap/v1beta1/scheme/scheme.go +++ b/cmd/clusterawsadm/api/bootstrap/v1beta1/scheme/scheme.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package scheme provides a way to generate a Scheme and CodecFactory +// for the bootstrap.aws.infrastructure.cluster.x-k8s.io API group. package scheme import ( diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/cluster_api_controller.go b/cmd/clusterawsadm/cloudformation/bootstrap/cluster_api_controller.go index 14f8d423bb..c91939295b 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/cluster_api_controller.go +++ b/cmd/clusterawsadm/cloudformation/bootstrap/cluster_api_controller.go @@ -322,60 +322,59 @@ func (t Template) ControllersPolicy() *iamv1.PolicyDocument { // ControllersPolicyEKS creates a policy from a template for AWS Controllers. func (t Template) ControllersPolicyEKS() *iamv1.PolicyDocument { - statement := []iamv1.StatementEntry{} + statements := []iamv1.StatementEntry{} allowedIAMActions := iamv1.Actions{ "iam:GetRole", "iam:ListAttachedRolePolicies", } - statement = append(statement, iamv1.StatementEntry{ - Effect: iamv1.EffectAllow, - Resource: iamv1.Resources{ - "arn:*:ssm:*:*:parameter/aws/service/eks/optimized-ami/*", - }, - Action: iamv1.Actions{ - "ssm:GetParameter", - }, - }) - - statement = append(statement, iamv1.StatementEntry{ - Effect: iamv1.EffectAllow, - Action: iamv1.Actions{ - "iam:CreateServiceLinkedRole", - }, - Resource: iamv1.Resources{ - "arn:*:iam::*:role/aws-service-role/eks.amazonaws.com/AWSServiceRoleForAmazonEKS", - }, - Condition: iamv1.Conditions{ - iamv1.StringLike: map[string]string{"iam:AWSServiceName": "eks.amazonaws.com"}, - }, - }) - - statement = append(statement, iamv1.StatementEntry{ - Effect: iamv1.EffectAllow, - Action: iamv1.Actions{ - "iam:CreateServiceLinkedRole", - }, - Resource: iamv1.Resources{ - "arn:*:iam::*:role/aws-service-role/eks-nodegroup.amazonaws.com/AWSServiceRoleForAmazonEKSNodegroup", - }, - Condition: iamv1.Conditions{ - iamv1.StringLike: map[string]string{"iam:AWSServiceName": "eks-nodegroup.amazonaws.com"}, + statements = append(statements, + iamv1.StatementEntry{ + Effect: iamv1.EffectAllow, + Resource: iamv1.Resources{ + "arn:*:ssm:*:*:parameter/aws/service/eks/optimized-ami/*", + }, + Action: iamv1.Actions{ + "ssm:GetParameter", + }, }, - }) - - statement = append(statement, iamv1.StatementEntry{ - Effect: iamv1.EffectAllow, - Action: iamv1.Actions{ - "iam:CreateServiceLinkedRole", + iamv1.StatementEntry{ + Effect: iamv1.EffectAllow, + Action: iamv1.Actions{ + "iam:CreateServiceLinkedRole", + }, + Resource: iamv1.Resources{ + "arn:*:iam::*:role/aws-service-role/eks.amazonaws.com/AWSServiceRoleForAmazonEKS", + }, + Condition: iamv1.Conditions{ + iamv1.StringLike: map[string]string{"iam:AWSServiceName": "eks.amazonaws.com"}, + }, }, - Resource: iamv1.Resources{ - "arn:" + t.Spec.Partition + ":iam::*:role/aws-service-role/eks-fargate-pods.amazonaws.com/AWSServiceRoleForAmazonEKSForFargate", + iamv1.StatementEntry{ + Effect: iamv1.EffectAllow, + Action: iamv1.Actions{ + "iam:CreateServiceLinkedRole", + }, + Resource: iamv1.Resources{ + "arn:*:iam::*:role/aws-service-role/eks-nodegroup.amazonaws.com/AWSServiceRoleForAmazonEKSNodegroup", + }, + Condition: iamv1.Conditions{ + iamv1.StringLike: map[string]string{"iam:AWSServiceName": "eks-nodegroup.amazonaws.com"}, + }, }, - Condition: iamv1.Conditions{ - iamv1.StringLike: map[string]string{"iam:AWSServiceName": "eks-fargate.amazonaws.com"}, + iamv1.StatementEntry{ + Effect: iamv1.EffectAllow, + Action: iamv1.Actions{ + "iam:CreateServiceLinkedRole", + }, + Resource: iamv1.Resources{ + "arn:" + t.Spec.Partition + ":iam::*:role/aws-service-role/eks-fargate-pods.amazonaws.com/AWSServiceRoleForAmazonEKSForFargate", + }, + Condition: iamv1.Conditions{ + iamv1.StringLike: map[string]string{"iam:AWSServiceName": "eks-fargate.amazonaws.com"}, + }, }, - }) + ) if t.Spec.EKS.AllowIAMRoleCreation { allowedIAMActions = append(allowedIAMActions, iamv1.Actions{ @@ -386,7 +385,7 @@ func (t Template) ControllersPolicyEKS() *iamv1.PolicyDocument { "iam:AttachRolePolicy", }...) - statement = append(statement, iamv1.StatementEntry{ + statements = append(statements, iamv1.StatementEntry{ Action: iamv1.Actions{ "iam:ListOpenIDConnectProviders", "iam:GetOpenIDConnectProvider", @@ -402,7 +401,8 @@ func (t Template) ControllersPolicyEKS() *iamv1.PolicyDocument { Effect: iamv1.EffectAllow, }) } - statement = append(statement, []iamv1.StatementEntry{ + + statements = append(statements, []iamv1.StatementEntry{ { Action: allowedIAMActions, Resource: iamv1.Resources{ @@ -495,7 +495,7 @@ func (t Template) ControllersPolicyEKS() *iamv1.PolicyDocument { return &iamv1.PolicyDocument{ Version: iamv1.CurrentVersion, - Statement: statement, + Statement: statements, } } diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/iam.go b/cmd/clusterawsadm/cloudformation/bootstrap/iam.go index 1aa016606e..2a30b4ea33 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/iam.go +++ b/cmd/clusterawsadm/cloudformation/bootstrap/iam.go @@ -71,6 +71,7 @@ func (t Template) policyFunctionMap() map[PolicyName]func() *iamv1.PolicyDocumen } } +// PrintPolicyDocs prints the JSON representation of policy documents for all ManagedIAMPolicy. func (t Template) PrintPolicyDocs() error { for _, name := range ManagedIAMPolicyNames { policyDoc := t.GetPolicyDocFromPolicyName(name) diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/template.go b/cmd/clusterawsadm/cloudformation/bootstrap/template.go index 030bc248ee..c4eb4cbff7 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/template.go +++ b/cmd/clusterawsadm/cloudformation/bootstrap/template.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package bootstrap provides a way to generate a CloudFormation template for IAM policies, +// users and roles for use by Cluster API Provider AWS. package bootstrap import ( diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/template_test.go b/cmd/clusterawsadm/cloudformation/bootstrap/template_test.go index c80f2312ef..e47fbbd047 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/template_test.go +++ b/cmd/clusterawsadm/cloudformation/bootstrap/template_test.go @@ -17,6 +17,7 @@ limitations under the License. package bootstrap import ( + "bytes" "fmt" "os" "path" @@ -201,7 +202,7 @@ func TestRenderCloudformation(t *testing.T) { t.Fatal(err) } - if string(tData) != string(data) { + if !bytes.Equal(tData, data) { dmp := diffmatchpatch.New() diffs := dmp.DiffMain(string(tData), string(data), false) out := dmp.DiffPrettyText(diffs) diff --git a/cmd/clusterawsadm/cloudformation/service/service.go b/cmd/clusterawsadm/cloudformation/service/service.go index 14a27fd2e9..33db42a8d0 100644 --- a/cmd/clusterawsadm/cloudformation/service/service.go +++ b/cmd/clusterawsadm/cloudformation/service/service.go @@ -82,6 +82,7 @@ func (s *Service) ReconcileBootstrapStack(stackName string, t go_cfn.Template, t return nil } +// ReconcileBootstrapNoUpdate creates or updates bootstrap CloudFormation without updating the stack. func (s *Service) ReconcileBootstrapNoUpdate(stackName string, t go_cfn.Template, tags map[string]string) error { yaml, err := t.YAML() processedYaml := string(yaml) diff --git a/cmd/clusterawsadm/cmd/ami/ami.go b/cmd/clusterawsadm/cmd/ami/ami.go index 0992c0723c..b4959b29e5 100644 --- a/cmd/clusterawsadm/cmd/ami/ami.go +++ b/cmd/clusterawsadm/cmd/ami/ami.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package ami provides a way to generate AMI commands. package ami import ( diff --git a/cmd/clusterawsadm/cmd/ami/common/common.go b/cmd/clusterawsadm/cmd/ami/common/common.go index 14ad2babaf..c3f79ed0de 100644 --- a/cmd/clusterawsadm/cmd/ami/common/common.go +++ b/cmd/clusterawsadm/cmd/ami/common/common.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package common provides common flags and functions for the AMI commands. package common import ( diff --git a/cmd/clusterawsadm/cmd/ami/common/copy.go b/cmd/clusterawsadm/cmd/ami/common/copy.go index 406d10f015..c2c95c6448 100644 --- a/cmd/clusterawsadm/cmd/ami/common/copy.go +++ b/cmd/clusterawsadm/cmd/ami/common/copy.go @@ -89,7 +89,6 @@ func CopyAMICmd() *cobra.Command { printer.Print(ami) - // klog.V(0).Infof("Completed copying %v\n", *image.ImageId) return nil }, } diff --git a/cmd/clusterawsadm/cmd/ami/list/list.go b/cmd/clusterawsadm/cmd/ami/list/list.go index 12ee0cfc88..5e1bef32ed 100644 --- a/cmd/clusterawsadm/cmd/ami/list/list.go +++ b/cmd/clusterawsadm/cmd/ami/list/list.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package list provides a way to list AMIs from the default AWS account where AMIs are stored. package list import ( diff --git a/cmd/clusterawsadm/cmd/bootstrap/bootstrap.go b/cmd/clusterawsadm/cmd/bootstrap/bootstrap.go index 00d7322f75..cfa73aa658 100644 --- a/cmd/clusterawsadm/cmd/bootstrap/bootstrap.go +++ b/cmd/clusterawsadm/cmd/bootstrap/bootstrap.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package bootstrap provides cli commands for bootstrapping +// AWS accounts for use with the Kubernetes Cluster API Provider AWS. package bootstrap import ( diff --git a/cmd/clusterawsadm/cmd/bootstrap/credentials/credentials.go b/cmd/clusterawsadm/cmd/bootstrap/credentials/credentials.go index 2abda3f3b6..0c919d7e7e 100644 --- a/cmd/clusterawsadm/cmd/bootstrap/credentials/credentials.go +++ b/cmd/clusterawsadm/cmd/bootstrap/credentials/credentials.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package credentials provides a way to encode credentials for use with Kubernetes Cluster API Provider AWS. package credentials import ( diff --git a/cmd/clusterawsadm/cmd/bootstrap/iam/iam_doc.go b/cmd/clusterawsadm/cmd/bootstrap/iam/iam_doc.go index f518c5cc96..775187858f 100644 --- a/cmd/clusterawsadm/cmd/bootstrap/iam/iam_doc.go +++ b/cmd/clusterawsadm/cmd/bootstrap/iam/iam_doc.go @@ -44,7 +44,7 @@ func printPolicyCmd() *cobra.Command { clusterawsadm bootstrap iam print-policy --document AWSIAMManagedPolicyControllers # Print out the IAM policy for the Kubernetes Cluster API Provider AWS Controller using a given configuration file. - clusterawsadm bootstrap iam print-policy --document AWSIAMManagedPolicyControllers --config bootstrap_config.yaml + clusterawsadm bootstrap iam print-policy --document AWSIAMManagedPolicyControllers --config bootstrap_config.yaml # Print out the IAM policy for the Kubernetes AWS Cloud Provider for the control plane. clusterawsadm bootstrap iam print-policy --document AWSIAMManagedPolicyCloudProviderControlPlane diff --git a/cmd/clusterawsadm/cmd/bootstrap/iam/root.go b/cmd/clusterawsadm/cmd/bootstrap/iam/root.go index 1f9f2b9ca5..491610cd59 100644 --- a/cmd/clusterawsadm/cmd/bootstrap/iam/root.go +++ b/cmd/clusterawsadm/cmd/bootstrap/iam/root.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package iam provides a way to generate IAM policies and roles. package iam import ( diff --git a/cmd/clusterawsadm/cmd/controller/controller.go b/cmd/clusterawsadm/cmd/controller/controller.go index a8897cea08..31e018d432 100644 --- a/cmd/clusterawsadm/cmd/controller/controller.go +++ b/cmd/clusterawsadm/cmd/controller/controller.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package controller provides the controller command. package controller import ( diff --git a/cmd/clusterawsadm/cmd/controller/credentials/print.go b/cmd/clusterawsadm/cmd/controller/credentials/print.go index b88621cf25..0b4e27094a 100644 --- a/cmd/clusterawsadm/cmd/controller/credentials/print.go +++ b/cmd/clusterawsadm/cmd/controller/credentials/print.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package credentials provides a CLI utilities for AWS credentials. package credentials import ( diff --git a/cmd/clusterawsadm/cmd/controller/rollout/common.go b/cmd/clusterawsadm/cmd/controller/rollout/common.go index 37cc67b6e9..47707f3970 100644 --- a/cmd/clusterawsadm/cmd/controller/rollout/common.go +++ b/cmd/clusterawsadm/cmd/controller/rollout/common.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package rollout provides the rollout command. package rollout import ( diff --git a/cmd/clusterawsadm/cmd/eks/addons/addons.go b/cmd/clusterawsadm/cmd/eks/addons/addons.go index 3b8ae23e76..709f2f2cf3 100644 --- a/cmd/clusterawsadm/cmd/eks/addons/addons.go +++ b/cmd/clusterawsadm/cmd/eks/addons/addons.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package addons provides EKS addons commands. package addons import "github.com/spf13/cobra" diff --git a/cmd/clusterawsadm/cmd/eks/addons/list_installed.go b/cmd/clusterawsadm/cmd/eks/addons/list_installed.go index 827c944e0a..cb73ee64b5 100644 --- a/cmd/clusterawsadm/cmd/eks/addons/list_installed.go +++ b/cmd/clusterawsadm/cmd/eks/addons/list_installed.go @@ -113,10 +113,10 @@ func listInstalledAddons(region, clusterName, printerType *string) error { newIssue := issue{ Code: *addonIssue.Code, Message: *addonIssue.Message, - ResourceIds: []string{}, + ResourceIDs: []string{}, } for _, resID := range addonIssue.ResourceIds { - newIssue.ResourceIds = append(newIssue.ResourceIds, *resID) + newIssue.ResourceIDs = append(newIssue.ResourceIDs, *resID) } installedAddon.HealthIssues = append(installedAddon.HealthIssues, newIssue) } diff --git a/cmd/clusterawsadm/cmd/eks/addons/types.go b/cmd/clusterawsadm/cmd/eks/addons/types.go index a59368f8f6..9c9ae62616 100644 --- a/cmd/clusterawsadm/cmd/eks/addons/types.go +++ b/cmd/clusterawsadm/cmd/eks/addons/types.go @@ -106,7 +106,7 @@ type installedAddon struct { type issue struct { Code string Message string - ResourceIds []string + ResourceIDs []string } type installedAddonsList struct { diff --git a/cmd/clusterawsadm/cmd/eks/eks.go b/cmd/clusterawsadm/cmd/eks/eks.go index 42d271f481..8856216aa8 100644 --- a/cmd/clusterawsadm/cmd/eks/eks.go +++ b/cmd/clusterawsadm/cmd/eks/eks.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package eks provides a CLI to manage EKS clusters. package eks import ( diff --git a/cmd/clusterawsadm/cmd/flags/common.go b/cmd/clusterawsadm/cmd/flags/common.go index 096d289927..d6d7e4e808 100644 --- a/cmd/clusterawsadm/cmd/flags/common.go +++ b/cmd/clusterawsadm/cmd/flags/common.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package flags provides a way to add flags to the cli. package flags import ( diff --git a/cmd/clusterawsadm/cmd/gc/gc.go b/cmd/clusterawsadm/cmd/gc/gc.go index 0bd0344514..c9d91bf703 100644 --- a/cmd/clusterawsadm/cmd/gc/gc.go +++ b/cmd/clusterawsadm/cmd/gc/gc.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package gc provides commands related to garbage collecting external resources of clusters. package gc import ( @@ -27,10 +28,7 @@ func RootCmd() *cobra.Command { Short: "Commands related to garbage collecting external resources of clusters", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - if err := cmd.Help(); err != nil { - return err - } - return nil + return cmd.Help() }, } diff --git a/cmd/clusterawsadm/cmd/resource/list/list.go b/cmd/clusterawsadm/cmd/resource/list/list.go index 01b84e2ae4..1e65ef61ad 100644 --- a/cmd/clusterawsadm/cmd/resource/list/list.go +++ b/cmd/clusterawsadm/cmd/resource/list/list.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package list provides the list command for the resource package. package list import ( @@ -38,7 +39,7 @@ func ListAWSResourceCmd() *cobra.Command { Short: "List all AWS resources created by CAPA", Long: cmd.LongDesc(` List AWS resources directly created by CAPA based on region and cluster-name. There are some indirect resources like Cloudwatch alarms, rules, etc - which are not directly created by CAPA, so those resources are not listed here. + which are not directly created by CAPA, so those resources are not listed here. If region and cluster-name are not set, then it will throw an error. `), Example: cmd.Examples(` diff --git a/cmd/clusterawsadm/cmd/resource/resource.go b/cmd/clusterawsadm/cmd/resource/resource.go index 36e5aa3e25..c2cbde7a6a 100644 --- a/cmd/clusterawsadm/cmd/resource/resource.go +++ b/cmd/clusterawsadm/cmd/resource/resource.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package resource provides commands related to AWS resources. package resource import ( @@ -34,10 +35,7 @@ func RootCmd() *cobra.Command { # List of AWS resources created by CAPA `), RunE: func(cmd *cobra.Command, args []string) error { - if err := cmd.Help(); err != nil { - return err - } - return nil + return cmd.Help() }, } diff --git a/cmd/clusterawsadm/cmd/root.go b/cmd/clusterawsadm/cmd/root.go index dc25175824..0c0b2b5614 100644 --- a/cmd/clusterawsadm/cmd/root.go +++ b/cmd/clusterawsadm/cmd/root.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package cmd implements the clusterawsadm command line utility. package cmd import ( @@ -63,7 +64,7 @@ func RootCmd() *cobra.Command { export AWS_B64ENCODED_CREDENTIALS=$(clusterawsadm bootstrap credentials encode-as-profile) clusterctl init --infrastructure aws `), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { return cmd.Help() }, } diff --git a/cmd/clusterawsadm/cmd/util/util.go b/cmd/clusterawsadm/cmd/util/util.go index 8e714ed80c..7b974add4a 100644 --- a/cmd/clusterawsadm/cmd/util/util.go +++ b/cmd/clusterawsadm/cmd/util/util.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package util provides utility functions. package util import ( diff --git a/cmd/clusterawsadm/cmd/version/version.go b/cmd/clusterawsadm/cmd/version/version.go index db85908013..23930f6fde 100644 --- a/cmd/clusterawsadm/cmd/version/version.go +++ b/cmd/clusterawsadm/cmd/version/version.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package version provides the version information of clusterawsadm. package version import ( diff --git a/cmd/clusterawsadm/configreader/configreader.go b/cmd/clusterawsadm/configreader/configreader.go index 3047152cb6..e5b1d800cd 100644 --- a/cmd/clusterawsadm/configreader/configreader.go +++ b/cmd/clusterawsadm/configreader/configreader.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package configreader provides a way to load a bootstrapv1.AWSIAMConfiguration from a file. package configreader import ( diff --git a/cmd/clusterawsadm/controller/credentials/update_credentials.go b/cmd/clusterawsadm/controller/credentials/update_credentials.go index e4a9d1afc4..eba621cb3e 100644 --- a/cmd/clusterawsadm/controller/credentials/update_credentials.go +++ b/cmd/clusterawsadm/controller/credentials/update_credentials.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package credentials provides AWS credentials management. package credentials import ( @@ -49,7 +50,7 @@ func UpdateCredentials(input UpdateCredentialsInput) error { creds = "Cg==" } - patch := fmt.Sprintf("{\"data\":{\"credentials\": \"%s\"}}", creds) + patch := fmt.Sprintf("{\"data\":{\"credentials\": %q}}", creds) _, err = client.CoreV1().Secrets(input.Namespace).Patch( context.TODO(), controller.BootstrapCredsSecret, diff --git a/cmd/clusterawsadm/controller/helper.go b/cmd/clusterawsadm/controller/helper.go index d7ff024ff2..809678bf2b 100644 --- a/cmd/clusterawsadm/controller/helper.go +++ b/cmd/clusterawsadm/controller/helper.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package controller contains the controller logic for the capa manager. package controller import ( diff --git a/cmd/clusterawsadm/controller/rollout/rollout.go b/cmd/clusterawsadm/controller/rollout/rollout.go index 12f9f722cd..eb55e32947 100644 --- a/cmd/clusterawsadm/controller/rollout/rollout.go +++ b/cmd/clusterawsadm/controller/rollout/rollout.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package rollout provides a way to rollout the CAPA controller manager deployment. package rollout import ( diff --git a/cmd/clusterawsadm/converters/iam.go b/cmd/clusterawsadm/converters/iam.go index cecf4f5530..a571962fee 100644 --- a/cmd/clusterawsadm/converters/iam.go +++ b/cmd/clusterawsadm/converters/iam.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package converters contains the conversion functions for AWS. package converters import ( diff --git a/cmd/clusterawsadm/credentials/credentials.go b/cmd/clusterawsadm/credentials/credentials.go index 4c640dfbfe..2aa320839a 100644 --- a/cmd/clusterawsadm/credentials/credentials.go +++ b/cmd/clusterawsadm/credentials/credentials.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package credentials contains utilities for working with AWS credentials. package credentials import ( diff --git a/cmd/clusterawsadm/gc/gc.go b/cmd/clusterawsadm/gc/gc.go index 046c841be6..27a9887d41 100644 --- a/cmd/clusterawsadm/gc/gc.go +++ b/cmd/clusterawsadm/gc/gc.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package gc provides a way to handle AWS garbage collection on deletion. package gc import ( @@ -23,8 +24,8 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" - _ "k8s.io/client-go/plugin/pkg/client/auth/exec" - _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" + _ "k8s.io/client-go/plugin/pkg/client/auth/exec" // import all auth plugins + _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" // import all oidc plugins "k8s.io/client-go/tools/clientcmd" "sigs.k8s.io/controller-runtime/pkg/client" diff --git a/cmd/clusterawsadm/main.go b/cmd/clusterawsadm/main.go index bd97bc0adb..0a30981ed0 100644 --- a/cmd/clusterawsadm/main.go +++ b/cmd/clusterawsadm/main.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package main is the entrypoint for the clusterawsadm command. package main import "sigs.k8s.io/cluster-api-provider-aws/v2/cmd/clusterawsadm/cmd" diff --git a/cmd/clusterawsadm/printers/printers.go b/cmd/clusterawsadm/printers/printers.go index 4d3b6aa713..0c106aca12 100644 --- a/cmd/clusterawsadm/printers/printers.go +++ b/cmd/clusterawsadm/printers/printers.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package printers provides a wrapper for the k8s.io/cli-runtime/pkg/printers package. package printers import ( diff --git a/cmd/clusterawsadm/resource/type.go b/cmd/clusterawsadm/resource/type.go index e5b344aff3..0dda210426 100644 --- a/cmd/clusterawsadm/resource/type.go +++ b/cmd/clusterawsadm/resource/type.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package resource provides definitions for AWS resource types. package resource import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml b/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml index d67cf97022..38cc06b8f0 100644 --- a/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml +++ b/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml @@ -30,6 +30,7 @@ spec: name: v1beta2 schema: openAPIV3Schema: + description: ROSAControlPlane is the Schema for the ROSAControlPlanes API. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -44,21 +45,23 @@ spec: metadata: type: object spec: + description: RosaControlPlaneSpec defines the desired state of ROSAControlPlane. properties: - autoscaling: - description: Autoscaling specifies auto scaling behaviour for the - MachinePools. - properties: - maxReplicas: - minimum: 1 - type: integer - minReplicas: - minimum: 1 - type: integer + additionalTags: + additionalProperties: + type: string + description: AdditionalTags are user-defined tags to be added on the + AWS resources associated with the control plane. type: object + auditLogRoleARN: + description: AuditLogRoleARN defines the role that is used to forward + audit logs to AWS CloudWatch. If not set, audit log forwarding is + disabled. + type: string availabilityZones: - description: AWS AvailabilityZones of the worker nodes should match - the AvailabilityZones of the Subnets. + description: AvailabilityZones describe AWS AvailabilityZones of the + worker nodes. should match the AvailabilityZones of the provided + Subnets. a machinepool will be created for each availabilityZone. items: type: string type: array @@ -100,6 +103,59 @@ spec: type: string type: object x-kubernetes-map-type: atomic + defaultMachinePoolSpec: + description: "DefaultMachinePoolSpec defines the configuration for + the default machinepool(s) provisioned as part of the cluster creation. + One MachinePool will be created with this configuration per AvailabilityZone. + Those default machinepools are required for openshift cluster operators + to work properly. As these machinepool not created using ROSAMachinePool + CR, they will not be visible/managed by ROSA CAPI provider. `rosa + list machinepools -c ` can be used to view those + machinepools. \n This field will be removed in the future once the + current limitation is resolved." + properties: + autoscaling: + description: Autoscaling specifies auto scaling behaviour for + this MachinePool. + properties: + maxReplicas: + minimum: 1 + type: integer + minReplicas: + minimum: 1 + type: integer + type: object + instanceType: + description: The instance type to use, for example `r5.xlarge`. + Instance type ref; https://aws.amazon.com/ec2/instance-types/ + type: string + type: object + domainPrefix: + description: DomainPrefix is an optional prefix added to the cluster's + domain name. It will be used when generating a sub-domain for the + cluster on openshiftapps domain. It must be valid DNS-1035 label + consisting of lower case alphanumeric characters or '-', start with + an alphabetic character end with an alphanumeric character and have + a max length of 15 characters. + maxLength: 15 + pattern: ^[a-z]([-a-z0-9]*[a-z0-9])?$ + type: string + x-kubernetes-validations: + - message: domainPrefix is immutable + rule: self == oldSelf + endpointAccess: + default: Public + description: EndpointAccess specifies the publishing scope of cluster + endpoints. The default is Public. + enum: + - Public + - Private + type: string + etcdEncryptionKMSARN: + description: EtcdEncryptionKMSARN is the ARN of the KMS key used to + encrypt etcd. The key itself needs to be created out-of-band by + the user and tagged with `red-hat:true`. + type: string identityRef: description: IdentityRef is a reference to an identity to be used when reconciling the managed control plane. If no identity is specified, @@ -121,12 +177,8 @@ spec: - name type: object installerRoleARN: - description: 'TODO: these are to satisfy ocm sdk. Explore how to drop - them.' - type: string - instanceType: - description: The instance type to use, for example `r5.xlarge`. Instance - type ref; https://aws.amazon.com/ec2/instance-types/ + description: InstallerRoleARN is an AWS IAM role that OpenShift Cluster + Manager will assume to create the cluster.. type: string network: description: Network config for the ROSA HCP cluster. @@ -162,6 +214,13 @@ spec: oidcID: description: The ID of the OpenID Connect Provider. type: string + provisionShardID: + description: ProvisionShardID defines the shard where rosa control + plane components will be hosted. + type: string + x-kubernetes-validations: + - message: provisionShardID is immutable + rule: self == oldSelf region: description: The AWS Region the cluster lives in. type: string @@ -322,8 +381,8 @@ spec: description: Cluster name must be valid DNS-1035 label, so it must consist of lower case alphanumeric characters or '-', start with an alphabetic character, end with an alphanumeric character and - have a max length of 15 characters. - maxLength: 15 + have a max length of 54 characters. + maxLength: 54 pattern: ^[a-z]([-a-z0-9]*[a-z0-9])?$ type: string x-kubernetes-validations: @@ -337,14 +396,15 @@ spec: type: string type: array supportRoleARN: + description: SupportRoleARN is an AWS IAM role used by Red Hat SREs + to enable access to the cluster account in order to provide support. type: string version: description: OpenShift semantic version, for example "4.14.5". type: string - x-kubernetes-validations: - - message: version must be a valid semantic version - rule: self.matches('^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$') workerRoleARN: + description: WorkerRoleARN is an AWS IAM role that will be attached + to worker instances. type: string required: - availabilityZones @@ -359,9 +419,10 @@ spec: - workerRoleARN type: object status: + description: RosaControlPlaneStatus defines the observed state of ROSAControlPlane. properties: conditions: - description: Conditions specifies the cpnditions for the managed control + description: Conditions specifies the conditions for the managed control plane items: description: Condition defines an observation of a Cluster API resource @@ -433,7 +494,7 @@ spec: type: boolean oidcEndpointURL: description: OIDCEndpointURL is the endpoint url for the managed OIDC - porvider. + provider. type: string ready: default: false diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclustertemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclustertemplates.yaml index df369e0c2d..e8ef04c449 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclustertemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclustertemplates.yaml @@ -45,6 +45,8 @@ spec: description: AWSClusterTemplateSpec defines the desired state of AWSClusterTemplate. properties: template: + description: AWSClusterTemplateResource defines the desired state + of AWSClusterTemplate. properties: metadata: description: 'Standard object''s metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata' @@ -474,6 +476,8 @@ spec: description: AWSClusterTemplateSpec defines the desired state of AWSClusterTemplate. properties: template: + description: AWSClusterTemplateResource defines the desired state + of AWSClusterTemplateResource. properties: metadata: description: 'Standard object''s metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata' diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachines.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachines.yaml index e356896c1b..2ad97a52d3 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachines.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachines.yaml @@ -632,6 +632,40 @@ spec: description: Ignition defined options related to the bootstrapping systems where Ignition is used. properties: + proxy: + description: Proxy defines proxy settings for Ignition. Only valid + for Ignition versions 3.1 and above. + properties: + httpProxy: + description: HTTPProxy is the HTTP proxy to use for Ignition. + A single URL that specifies the proxy server to use for + HTTP and HTTPS requests, unless overridden by the HTTPSProxy + or NoProxy options. + type: string + httpsProxy: + description: HTTPSProxy is the HTTPS proxy to use for Ignition. + A single URL that specifies the proxy server to use for + HTTPS requests, unless overridden by the NoProxy option. + type: string + noProxy: + description: "NoProxy is the list of domains to not proxy + for Ignition. Specifies a list of strings to hosts that + should be excluded from proxying. \n Each value is represented + by: - An IP address prefix (1.2.3.4) - An IP address prefix + in CIDR notation (1.2.3.4/8) - A domain name - A domain + name matches that name and all subdomains - A domain name + with a leading . matches subdomains only - A special DNS + label (*), indicates that no proxying should be done \n + An IP address prefix and domain name can also include a + literal port number (1.2.3.4:80)." + items: + description: IgnitionNoProxy defines the list of domains + to not proxy for Ignition. + maxLength: 2048 + type: string + maxItems: 64 + type: array + type: object storageType: default: ClusterObjectStore description: "StorageType defines how to store the boostrap user @@ -654,6 +688,24 @@ spec: - ClusterObjectStore - UnencryptedUserData type: string + tls: + description: TLS defines TLS settings for Ignition. Only valid + for Ignition versions 3.1 and above. + properties: + certificateAuthorities: + description: CASources defines the list of certificate authorities + to use for Ignition. The value is the certificate bundle + (in PEM format). The bundle can contain multiple concatenated + certificates. Supported schemes are http, https, tftp, s3, + arn, gs, and `data` (RFC 2397) URL scheme. + items: + description: IgnitionCASource defines the source of the + certificate authority to use for Ignition. + maxLength: 65536 + type: string + maxItems: 64 + type: array + type: object version: default: "2.3" description: Version defines which version of Ignition will be diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinetemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinetemplates.yaml index 00b85b4969..dec0817523 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinetemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinetemplates.yaml @@ -578,6 +578,42 @@ spec: description: Ignition defined options related to the bootstrapping systems where Ignition is used. properties: + proxy: + description: Proxy defines proxy settings for Ignition. + Only valid for Ignition versions 3.1 and above. + properties: + httpProxy: + description: HTTPProxy is the HTTP proxy to use for + Ignition. A single URL that specifies the proxy + server to use for HTTP and HTTPS requests, unless + overridden by the HTTPSProxy or NoProxy options. + type: string + httpsProxy: + description: HTTPSProxy is the HTTPS proxy to use + for Ignition. A single URL that specifies the proxy + server to use for HTTPS requests, unless overridden + by the NoProxy option. + type: string + noProxy: + description: "NoProxy is the list of domains to not + proxy for Ignition. Specifies a list of strings + to hosts that should be excluded from proxying. + \n Each value is represented by: - An IP address + prefix (1.2.3.4) - An IP address prefix in CIDR + notation (1.2.3.4/8) - A domain name - A domain + name matches that name and all subdomains - A domain + name with a leading . matches subdomains only - + A special DNS label (*), indicates that no proxying + should be done \n An IP address prefix and domain + name can also include a literal port number (1.2.3.4:80)." + items: + description: IgnitionNoProxy defines the list of + domains to not proxy for Ignition. + maxLength: 2048 + type: string + maxItems: 64 + type: array + type: object storageType: default: ClusterObjectStore description: "StorageType defines how to store the boostrap @@ -602,6 +638,25 @@ spec: - ClusterObjectStore - UnencryptedUserData type: string + tls: + description: TLS defines TLS settings for Ignition. Only + valid for Ignition versions 3.1 and above. + properties: + certificateAuthorities: + description: CASources defines the list of certificate + authorities to use for Ignition. The value is the + certificate bundle (in PEM format). The bundle can + contain multiple concatenated certificates. Supported + schemes are http, https, tftp, s3, arn, gs, and + `data` (RFC 2397) URL scheme. + items: + description: IgnitionCASource defines the source + of the certificate authority to use for Ignition. + maxLength: 65536 + type: string + maxItems: 64 + type: array + type: object version: default: "2.3" description: Version defines which version of Ignition diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_rosaclusters.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_rosaclusters.yaml index 710e61955a..532f17359e 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_rosaclusters.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_rosaclusters.yaml @@ -35,6 +35,7 @@ spec: name: v1beta2 schema: openAPIV3Schema: + description: ROSACluster is the Schema for the ROSAClusters API. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -49,6 +50,7 @@ spec: metadata: type: object spec: + description: ROSAClusterSpec defines the desired state of ROSACluster. properties: controlPlaneEndpoint: description: ControlPlaneEndpoint represents the endpoint used to @@ -67,7 +69,7 @@ spec: type: object type: object status: - description: ROSAClusterStatus defines the observed state of ROSACluster + description: ROSAClusterStatus defines the observed state of ROSACluster. properties: failureDomains: additionalProperties: diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_rosamachinepools.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_rosamachinepools.yaml index d94ef3e30d..699aa25701 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_rosamachinepools.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_rosamachinepools.yaml @@ -47,6 +47,18 @@ spec: spec: description: RosaMachinePoolSpec defines the desired state of RosaMachinePool. properties: + additionalSecurityGroups: + description: AdditionalSecurityGroups is an optional set of security + groups to associate with all node instances of the machine pool. + items: + type: string + type: array + additionalTags: + additionalProperties: + type: string + description: AdditionalTags are user-defined tags to be added on the + underlying EC2 instances associated with this machine pool. + type: object autoRepair: default: false description: AutoRepair specifies whether health checks should be @@ -76,6 +88,15 @@ spec: type: string description: Labels specifies labels for the Kubernetes node objects type: object + nodeDrainGracePeriod: + description: "NodeDrainGracePeriod is grace period for how long Pod + Disruption Budget-protected workloads will be respected during upgrades. + After this grace period, any workloads protected by Pod Disruption + Budgets that have not been successfully drained from a node will + be forcibly evicted. \n Valid values are from 0 to 1 week(10080m|168h) + . 0 or empty value means that the MachinePool can be drained without + any time limitation." + type: string nodePoolName: description: NodePoolName specifies the name of the nodepool in Rosa must be a valid DNS-1035 label, so it must consist of lower case @@ -94,14 +115,49 @@ spec: type: array subnet: type: string + x-kubernetes-validations: + - message: subnet is immutable + rule: self == oldSelf + taints: + description: Taints specifies the taints to apply to the nodes of + the machine pool + items: + description: RosaTaint represents a taint to be applied to a node. + properties: + effect: + description: The effect of the taint on pods that do not tolerate + the taint. Valid effects are NoSchedule, PreferNoSchedule + and NoExecute. + enum: + - NoSchedule + - PreferNoSchedule + - NoExecute + type: string + key: + description: The taint key to be applied to a node. + type: string + value: + description: The taint value corresponding to the taint key. + pattern: ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$ + type: string + required: + - effect + - key + type: object + type: array + tuningConfigs: + description: TuningConfigs specifies the names of the tuning configs + to be applied to this MachinePool. Tuning configs must already exist. + items: + type: string + type: array version: - description: Version specifies the penshift version of the nodes associated - with this machinepool. ROSAControlPlane version is used if not set. + description: Version specifies the OpenShift version of the nodes + associated with this machinepool. ROSAControlPlane version is used + if not set. type: string - x-kubernetes-validations: - - message: version must be a valid semantic version - rule: self.matches('^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$') required: + - instanceType - nodePoolName type: object status: diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 33946b3057..9aa4801808 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -175,6 +175,12 @@ rules: - get - list - watch +- apiGroups: + - controlplane.cluster.x-k8s.io + resources: + - rosacontrolplanes/finalizers + verbs: + - update - apiGroups: - controlplane.cluster.x-k8s.io resources: @@ -409,6 +415,12 @@ rules: - patch - update - watch +- apiGroups: + - infrastructure.cluster.x-k8s.io + resources: + - rosamachinepools/finalizers + verbs: + - update - apiGroups: - infrastructure.cluster.x-k8s.io resources: diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index 9dd74404c2..5eebfae968 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -201,6 +201,28 @@ webhooks: resources: - awsmanagedmachinepools sideEffects: None +- admissionReviewVersions: + - v1 + - v1beta1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-infrastructure-cluster-x-k8s-io-v1beta2-rosamachinepool + failurePolicy: Fail + matchPolicy: Equivalent + name: default.rosamachinepool.infrastructure.cluster.x-k8s.io + rules: + - apiGroups: + - infrastructure.cluster.x-k8s.io + apiVersions: + - v1beta2 + operations: + - CREATE + - UPDATE + resources: + - rosamachinepools + sideEffects: None - admissionReviewVersions: - v1 - v1beta1 @@ -267,6 +289,28 @@ webhooks: resources: - awsmanagedcontrolplanes sideEffects: None +- admissionReviewVersions: + - v1 + - v1beta1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-controlplane-cluster-x-k8s-io-v1beta2-rosacontrolplane + failurePolicy: Fail + matchPolicy: Equivalent + name: default.rosacontrolplanes.controlplane.cluster.x-k8s.io + rules: + - apiGroups: + - controlplane.cluster.x-k8s.io + apiVersions: + - v1beta2 + operations: + - CREATE + - UPDATE + resources: + - rosacontrolplanes + sideEffects: None --- apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration @@ -493,6 +537,28 @@ webhooks: resources: - awsmanagedmachinepools sideEffects: None +- admissionReviewVersions: + - v1 + - v1beta1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-infrastructure-cluster-x-k8s-io-v1beta2-rosamachinepool + failurePolicy: Fail + matchPolicy: Equivalent + name: validation.rosamachinepool.infrastructure.cluster.x-k8s.io + rules: + - apiGroups: + - infrastructure.cluster.x-k8s.io + apiVersions: + - v1beta2 + operations: + - CREATE + - UPDATE + resources: + - rosamachinepools + sideEffects: None - admissionReviewVersions: - v1 - v1beta1 @@ -559,3 +625,25 @@ webhooks: resources: - awsmanagedcontrolplanes sideEffects: None +- admissionReviewVersions: + - v1 + - v1beta1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-controlplane-cluster-x-k8s-io-v1beta2-rosacontrolplane + failurePolicy: Fail + matchPolicy: Equivalent + name: validation.rosacontrolplanes.controlplane.cluster.x-k8s.io + rules: + - apiGroups: + - controlplane.cluster.x-k8s.io + apiVersions: + - v1beta2 + operations: + - CREATE + - UPDATE + resources: + - rosacontrolplanes + sideEffects: None diff --git a/controllers/awsmachine_controller.go b/controllers/awsmachine_controller.go index 416ba0c420..14bb9387a1 100644 --- a/controllers/awsmachine_controller.go +++ b/controllers/awsmachine_controller.go @@ -503,7 +503,7 @@ func (r *AWSMachineReconciler) reconcileNormal(_ context.Context, machineScope * // Avoid a flickering condition between InstanceProvisionStarted and InstanceProvisionFailed if there's a persistent failure with createInstance if conditions.GetReason(machineScope.AWSMachine, infrav1.InstanceReadyCondition) != infrav1.InstanceProvisionFailedReason { conditions.MarkFalse(machineScope.AWSMachine, infrav1.InstanceReadyCondition, infrav1.InstanceProvisionStartedReason, clusterv1.ConditionSeverityInfo, "") - if patchErr := machineScope.PatchObject(); err != nil { + if patchErr := machineScope.PatchObject(); patchErr != nil { machineScope.Error(patchErr, "failed to patch conditions") return ctrl.Result{}, patchErr } @@ -807,6 +807,25 @@ func (r *AWSMachineReconciler) generateIgnitionWithRemoteStorage(scope *scope.Ma }, } + if scope.AWSMachine.Spec.Ignition.Proxy != nil { + ignData.Ignition.Proxy = ignV3Types.Proxy{ + HTTPProxy: scope.AWSMachine.Spec.Ignition.Proxy.HTTPProxy, + HTTPSProxy: scope.AWSMachine.Spec.Ignition.Proxy.HTTPSProxy, + } + for _, noProxy := range scope.AWSMachine.Spec.Ignition.Proxy.NoProxy { + ignData.Ignition.Proxy.NoProxy = append(ignData.Ignition.Proxy.NoProxy, ignV3Types.NoProxyItem(noProxy)) + } + } + + if scope.AWSMachine.Spec.Ignition.TLS != nil { + for _, cert := range scope.AWSMachine.Spec.Ignition.TLS.CASources { + ignData.Ignition.Security.TLS.CertificateAuthorities = append( + ignData.Ignition.Security.TLS.CertificateAuthorities, + ignV3Types.Resource{Source: aws.String(string(cert))}, + ) + } + } + return json.Marshal(ignData) default: return nil, errors.Errorf("unsupported ignition version %q", ignVersion) @@ -912,17 +931,10 @@ func (r *AWSMachineReconciler) reconcileLBAttachment(machineScope *scope.Machine func (r *AWSMachineReconciler) registerInstanceToLBs(machineScope *scope.MachineScope, elbsvc services.ELBInterface, i *infrav1.Instance, lb *infrav1.AWSLoadBalancerSpec) error { switch lb.LoadBalancerType { - case infrav1.LoadBalancerTypeClassic: - fallthrough - case "": + case infrav1.LoadBalancerTypeClassic, "": machineScope.Debug("registering to classic load balancer") return r.registerInstanceToClassicLB(machineScope, elbsvc, i) - - case infrav1.LoadBalancerTypeELB: - fallthrough - case infrav1.LoadBalancerTypeALB: - fallthrough - case infrav1.LoadBalancerTypeNLB: + case infrav1.LoadBalancerTypeELB, infrav1.LoadBalancerTypeALB, infrav1.LoadBalancerTypeNLB: machineScope.Debug("registering to v2 load balancer") return r.registerInstanceToV2LB(machineScope, elbsvc, i, lb) } diff --git a/controllers/rosacluster_controller.go b/controllers/rosacluster_controller.go index e57cb7402a..d81716e72b 100644 --- a/controllers/rosacluster_controller.go +++ b/controllers/rosacluster_controller.go @@ -109,7 +109,6 @@ func (r *ROSAClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) // Set the values from the managed control plane rosaCluster.Status.Ready = true rosaCluster.Spec.ControlPlaneEndpoint = controlPlane.Spec.ControlPlaneEndpoint - // rosaCluster.Status.FailureDomains = controlPlane.Status.FailureDomains if err := patchHelper.Patch(ctx, rosaCluster); err != nil { return reconcile.Result{}, fmt.Errorf("failed to patch ROSACluster: %w", err) diff --git a/controlplane/eks/api/v1beta1/awsmanagedcontrolplane_types.go b/controlplane/eks/api/v1beta1/awsmanagedcontrolplane_types.go index 4f7fc33cc5..a965bef381 100644 --- a/controlplane/eks/api/v1beta1/awsmanagedcontrolplane_types.go +++ b/controlplane/eks/api/v1beta1/awsmanagedcontrolplane_types.go @@ -228,6 +228,7 @@ type OIDCProviderStatus struct { TrustPolicy string `json:"trustPolicy,omitempty"` } +// IdentityProviderStatus holds the status for associated identity provider type IdentityProviderStatus struct { // ARN holds the ARN of associated identity provider ARN string `json:"arn,omitempty"` diff --git a/controlplane/eks/api/v1beta1/conversion_test.go b/controlplane/eks/api/v1beta1/conversion_test.go index 207a6b6695..b7b360d1d1 100644 --- a/controlplane/eks/api/v1beta1/conversion_test.go +++ b/controlplane/eks/api/v1beta1/conversion_test.go @@ -19,9 +19,8 @@ package v1beta1 import ( "testing" - . "github.com/onsi/gomega" - fuzz "github.com/google/gofuzz" + . "github.com/onsi/gomega" "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" "k8s.io/apimachinery/pkg/runtime" runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" diff --git a/controlplane/eks/api/v1beta1/types.go b/controlplane/eks/api/v1beta1/types.go index a85c433303..0ca9a64ebe 100644 --- a/controlplane/eks/api/v1beta1/types.go +++ b/controlplane/eks/api/v1beta1/types.go @@ -218,8 +218,8 @@ const ( SecurityGroupCluster = infrav1.SecurityGroupRole("cluster") ) +// OIDCIdentityProviderConfig defines the configuration for an OIDC identity provider. type OIDCIdentityProviderConfig struct { - // This is also known as audience. The ID for the client application that makes // authentication requests to the OpenID identity provider. // +kubebuilder:validation:Required diff --git a/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go b/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go index 89d5e8bc2b..fa96f494d8 100644 --- a/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go +++ b/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go @@ -231,6 +231,7 @@ type OIDCProviderStatus struct { TrustPolicy string `json:"trustPolicy,omitempty"` } +// IdentityProviderStatus holds the status for associated identity provider. type IdentityProviderStatus struct { // ARN holds the ARN of associated identity provider ARN string `json:"arn,omitempty"` diff --git a/controlplane/eks/api/v1beta2/doc.go b/controlplane/eks/api/v1beta2/doc.go index b2fbc38795..8409bb024f 100644 --- a/controlplane/eks/api/v1beta2/doc.go +++ b/controlplane/eks/api/v1beta2/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// package v1beta2 contains API Schema definitions for the controlplane v1beta2 API group +// Package v1beta2 contains API Schema definitions for the controlplane v1beta2 API group // +gencrdrefdocs:force // +groupName=controlplane.cluster.x-k8s.io // +k8s:defaulter-gen=TypeMeta diff --git a/controlplane/eks/api/v1beta2/groupversion_info.go b/controlplane/eks/api/v1beta2/groupversion_info.go index fcc0abb3a5..9fc8227082 100644 --- a/controlplane/eks/api/v1beta2/groupversion_info.go +++ b/controlplane/eks/api/v1beta2/groupversion_info.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// package v1beta2 contains API Schema definitions for the controlplane v1beta2 API group +// Package v1beta2 contains API Schema definitions for the controlplane v1beta2 API group // +kubebuilder:object:generate=true // +groupName=controlplane.cluster.x-k8s.io package v1beta2 diff --git a/controlplane/eks/api/v1beta2/types.go b/controlplane/eks/api/v1beta2/types.go index acaa53b419..1ef47215ce 100644 --- a/controlplane/eks/api/v1beta2/types.go +++ b/controlplane/eks/api/v1beta2/types.go @@ -218,8 +218,8 @@ const ( SecurityGroupCluster = infrav1.SecurityGroupRole("cluster") ) +// OIDCIdentityProviderConfig represents the configuration for an OIDC identity provider. type OIDCIdentityProviderConfig struct { - // This is also known as audience. The ID for the client application that makes // authentication requests to the OpenID identity provider. // +kubebuilder:validation:Required diff --git a/controlplane/rosa/api/v1beta2/conditions_consts.go b/controlplane/rosa/api/v1beta2/conditions_consts.go index 797e04a0a5..50dc2dc5d9 100644 --- a/controlplane/rosa/api/v1beta2/conditions_consts.go +++ b/controlplane/rosa/api/v1beta2/conditions_consts.go @@ -22,6 +22,18 @@ const ( // ROSAControlPlaneReadyCondition condition reports on the successful reconciliation of ROSAControlPlane. ROSAControlPlaneReadyCondition clusterv1.ConditionType = "ROSAControlPlaneReady" + // ROSAControlPlaneValidCondition condition reports whether ROSAControlPlane configuration is valid. + ROSAControlPlaneValidCondition clusterv1.ConditionType = "ROSAControlPlaneValid" + // ROSAControlPlaneUpgradingCondition condition reports whether ROSAControlPlane is upgrading or not. ROSAControlPlaneUpgradingCondition clusterv1.ConditionType = "ROSAControlPlaneUpgrading" + + // ROSAControlPlaneReconciliationFailedReason used to report failures while reconciling ROSAControlPlane. + ROSAControlPlaneReconciliationFailedReason = "ReconciliationFailed" + + // ROSAControlPlaneDeletionFailedReason used to report failures while deleting ROSAControlPlane. + ROSAControlPlaneDeletionFailedReason = "DeletionFailed" + + // ROSAControlPlaneInvalidConfigurationReason used to report invalid user input. + ROSAControlPlaneInvalidConfigurationReason = "InvalidConfiguration" ) diff --git a/controlplane/rosa/api/v1beta2/doc.go b/controlplane/rosa/api/v1beta2/doc.go new file mode 100644 index 0000000000..9308d1fb62 --- /dev/null +++ b/controlplane/rosa/api/v1beta2/doc.go @@ -0,0 +1,21 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package v1beta2 contains API Schema definitions for the controlplane v1beta2 API group +// +gencrdrefdocs:force +// +groupName=controlplane.cluster.x-k8s.io +// +k8s:defaulter-gen=TypeMeta +package v1beta2 diff --git a/controlplane/rosa/api/v1beta2/groupversion_info.go b/controlplane/rosa/api/v1beta2/groupversion_info.go index 9eeee3d76c..ea4ec8f784 100644 --- a/controlplane/rosa/api/v1beta2/groupversion_info.go +++ b/controlplane/rosa/api/v1beta2/groupversion_info.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// package v1beta2 contains API Schema definitions for the controlplane v1beta2 API group +// Package v1beta2 contains API Schema definitions for the controlplane v1beta2 API group. // +kubebuilder:object:generate=true // +groupName=controlplane.cluster.x-k8s.io package v1beta2 diff --git a/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go b/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go index 65ecd9279b..b35e7f6bee 100644 --- a/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go +++ b/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go @@ -25,57 +25,126 @@ import ( clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" ) +// RosaEndpointAccessType specifies the publishing scope of cluster endpoints. +type RosaEndpointAccessType string + +const ( + // Public endpoint access allows public API server access and + // private node communication with the control plane. + Public RosaEndpointAccessType = "Public" + + // Private endpoint access allows only private API server access and private + // node communication with the control plane. + Private RosaEndpointAccessType = "Private" +) + +// RosaControlPlaneSpec defines the desired state of ROSAControlPlane. type RosaControlPlaneSpec struct { //nolint: maligned // Cluster name must be valid DNS-1035 label, so it must consist of lower case alphanumeric // characters or '-', start with an alphabetic character, end with an alphanumeric character - // and have a max length of 15 characters. + // and have a max length of 54 characters. // // +immutable // +kubebuilder:validation:XValidation:rule="self == oldSelf", message="rosaClusterName is immutable" - // +kubebuilder:validation:MaxLength:=15 + // +kubebuilder:validation:MaxLength:=54 // +kubebuilder:validation:Pattern:=`^[a-z]([-a-z0-9]*[a-z0-9])?$` RosaClusterName string `json:"rosaClusterName"` + // DomainPrefix is an optional prefix added to the cluster's domain name. It will be used + // when generating a sub-domain for the cluster on openshiftapps domain. It must be valid DNS-1035 label + // consisting of lower case alphanumeric characters or '-', start with an alphabetic character + // end with an alphanumeric character and have a max length of 15 characters. + // + // +immutable + // +kubebuilder:validation:XValidation:rule="self == oldSelf", message="domainPrefix is immutable" + // +kubebuilder:validation:MaxLength:=15 + // +kubebuilder:validation:Pattern:=`^[a-z]([-a-z0-9]*[a-z0-9])?$` + // +optional + DomainPrefix string `json:"domainPrefix,omitempty"` + // The Subnet IDs to use when installing the cluster. // SubnetIDs should come in pairs; two per availability zone, one private and one public. Subnets []string `json:"subnets"` - // AWS AvailabilityZones of the worker nodes - // should match the AvailabilityZones of the Subnets. + // AvailabilityZones describe AWS AvailabilityZones of the worker nodes. + // should match the AvailabilityZones of the provided Subnets. + // a machinepool will be created for each availabilityZone. AvailabilityZones []string `json:"availabilityZones"` // The AWS Region the cluster lives in. - Region *string `json:"region"` + Region string `json:"region"` // OpenShift semantic version, for example "4.14.5". - // +kubebuilder:validation:XValidation:rule=`self.matches('^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$')`, message="version must be a valid semantic version" Version string `json:"version"` - // ControlPlaneEndpoint represents the endpoint used to communicate with the control plane. - // +optional - ControlPlaneEndpoint clusterv1.APIEndpoint `json:"controlPlaneEndpoint"` - // AWS IAM roles used to perform credential requests by the openshift operators. RolesRef AWSRolesRef `json:"rolesRef"` // The ID of the OpenID Connect Provider. - OIDCID *string `json:"oidcID"` + OIDCID string `json:"oidcID"` - // TODO: these are to satisfy ocm sdk. Explore how to drop them. - InstallerRoleARN *string `json:"installerRoleARN"` - SupportRoleARN *string `json:"supportRoleARN"` - WorkerRoleARN *string `json:"workerRoleARN"` - - // +immutable - // +kubebuilder:validation:Optional - // +kubebuilder:validation:XValidation:rule="self == oldSelf", message="billingAccount is immutable" - // +kubebuilder:validation:XValidation:rule="self.matches('^[0-9]{12}$')", message="billingAccount must be a valid AWS account ID" + // InstallerRoleARN is an AWS IAM role that OpenShift Cluster Manager will assume to create the cluster.. + InstallerRoleARN string `json:"installerRoleARN"` + // SupportRoleARN is an AWS IAM role used by Red Hat SREs to enable + // access to the cluster account in order to provide support. + SupportRoleARN string `json:"supportRoleARN"` + // WorkerRoleARN is an AWS IAM role that will be attached to worker instances. + WorkerRoleARN string `json:"workerRoleARN"` // BillingAccount is an optional AWS account to use for billing the subscription fees for ROSA clusters. // The cost of running each ROSA cluster will be billed to the infrastructure account in which the cluster // is running. + // + // +kubebuilder:validation:Optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf", message="billingAccount is immutable" + // +kubebuilder:validation:XValidation:rule="self.matches('^[0-9]{12}$')", message="billingAccount must be a valid AWS account ID" + // +immutable + // +optional BillingAccount string `json:"billingAccount,omitempty"` + // DefaultMachinePoolSpec defines the configuration for the default machinepool(s) provisioned as part of the cluster creation. + // One MachinePool will be created with this configuration per AvailabilityZone. Those default machinepools are required for openshift cluster operators + // to work properly. + // As these machinepool not created using ROSAMachinePool CR, they will not be visible/managed by ROSA CAPI provider. + // `rosa list machinepools -c ` can be used to view those machinepools. + // + // This field will be removed in the future once the current limitation is resolved. + // + // +optional + DefaultMachinePoolSpec DefaultMachinePoolSpec `json:"defaultMachinePoolSpec,omitempty"` + + // Network config for the ROSA HCP cluster. + // +optional + Network *NetworkSpec `json:"network,omitempty"` + + // EndpointAccess specifies the publishing scope of cluster endpoints. The + // default is Public. + // + // +kubebuilder:validation:Enum=Public;Private + // +kubebuilder:default=Public + // +optional + EndpointAccess RosaEndpointAccessType `json:"endpointAccess,omitempty"` + + // AdditionalTags are user-defined tags to be added on the AWS resources associated with the control plane. + // +optional + AdditionalTags infrav1.Tags `json:"additionalTags,omitempty"` + + // EtcdEncryptionKMSARN is the ARN of the KMS key used to encrypt etcd. The key itself needs to be + // created out-of-band by the user and tagged with `red-hat:true`. + // +optional + EtcdEncryptionKMSARN string `json:"etcdEncryptionKMSARN,omitempty"` + + // AuditLogRoleARN defines the role that is used to forward audit logs to AWS CloudWatch. + // If not set, audit log forwarding is disabled. + // +optional + AuditLogRoleARN string `json:"auditLogRoleARN,omitempty"` + + // ProvisionShardID defines the shard where rosa control plane components will be hosted. + // + // +kubebuilder:validation:XValidation:rule="self == oldSelf", message="provisionShardID is immutable" + // +optional + ProvisionShardID string `json:"provisionShardID,omitempty"` + // CredentialsSecretRef references a secret with necessary credentials to connect to the OCM API. // The secret should contain the following data keys: // - ocmToken: eyJhbGciOiJIUzI1NiIsI.... @@ -83,28 +152,22 @@ type RosaControlPlaneSpec struct { //nolint: maligned // +optional CredentialsSecretRef *corev1.LocalObjectReference `json:"credentialsSecretRef,omitempty"` - // +optional - // IdentityRef is a reference to an identity to be used when reconciling the managed control plane. // If no identity is specified, the default identity for this controller will be used. - IdentityRef *infrav1.AWSIdentityReference `json:"identityRef,omitempty"` - - // Network config for the ROSA HCP cluster. - Network *NetworkSpec `json:"network,omitempty"` - - // The instance type to use, for example `r5.xlarge`. Instance type ref; https://aws.amazon.com/ec2/instance-types/ + // // +optional - InstanceType string `json:"instanceType,omitempty"` + IdentityRef *infrav1.AWSIdentityReference `json:"identityRef,omitempty"` - // Autoscaling specifies auto scaling behaviour for the MachinePools. + // ControlPlaneEndpoint represents the endpoint used to communicate with the control plane. // +optional - Autoscaling *expinfrav1.RosaMachinePoolAutoScaling `json:"autoscaling,omitempty"` + ControlPlaneEndpoint clusterv1.APIEndpoint `json:"controlPlaneEndpoint"` } // NetworkSpec for ROSA-HCP. type NetworkSpec struct { // IP addresses block used by OpenShift while installing the cluster, for example "10.0.0.0/16". // +kubebuilder:validation:Format=cidr + // +optional MachineCIDR string `json:"machineCIDR,omitempty"` // IP address block from which to assign pod IP addresses, for example `10.128.0.0/14`. @@ -125,9 +188,21 @@ type NetworkSpec struct { // The CNI network type default is OVNKubernetes. // +kubebuilder:validation:Enum=OVNKubernetes;Other // +kubebuilder:default=OVNKubernetes + // +optional NetworkType string `json:"networkType,omitempty"` } +// DefaultMachinePoolSpec defines the configuration for the required worker nodes provisioned as part of the cluster creation. +type DefaultMachinePoolSpec struct { + // The instance type to use, for example `r5.xlarge`. Instance type ref; https://aws.amazon.com/ec2/instance-types/ + // +optional + InstanceType string `json:"instanceType,omitempty"` + + // Autoscaling specifies auto scaling behaviour for this MachinePool. + // +optional + Autoscaling *expinfrav1.RosaMachinePoolAutoScaling `json:"autoscaling,omitempty"` +} + // AWSRolesRef contains references to various AWS IAM roles required for operators to make calls against the AWS API. type AWSRolesRef struct { // The referenced role must have a trust relationship that allows it to be assumed via web identity. @@ -506,6 +581,7 @@ type AWSRolesRef struct { KMSProviderARN string `json:"kmsProviderARN"` } +// RosaControlPlaneStatus defines the observed state of ROSAControlPlane. type RosaControlPlaneStatus struct { // ExternalManagedControlPlane indicates to cluster-api that the control plane // is managed by an external service such as AKS, EKS, GKE, etc. @@ -529,14 +605,14 @@ type RosaControlPlaneStatus struct { // // +optional FailureMessage *string `json:"failureMessage,omitempty"` - // Conditions specifies the cpnditions for the managed control plane + // Conditions specifies the conditions for the managed control plane Conditions clusterv1.Conditions `json:"conditions,omitempty"` // ID is the cluster ID given by ROSA. - ID *string `json:"id,omitempty"` + ID string `json:"id,omitempty"` // ConsoleURL is the url for the openshift console. ConsoleURL string `json:"consoleURL,omitempty"` - // OIDCEndpointURL is the endpoint url for the managed OIDC porvider. + // OIDCEndpointURL is the endpoint url for the managed OIDC provider. OIDCEndpointURL string `json:"oidcEndpointURL,omitempty"` } @@ -548,6 +624,7 @@ type RosaControlPlaneStatus struct { // +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.ready",description="Control plane infrastructure is ready for worker nodes" // +k8s:defaulter-gen=true +// ROSAControlPlane is the Schema for the ROSAControlPlanes API. type ROSAControlPlane struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -558,6 +635,7 @@ type ROSAControlPlane struct { // +kubebuilder:object:root=true +// ROSAControlPlaneList contains a list of ROSAControlPlane. type ROSAControlPlaneList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/controlplane/rosa/api/v1beta2/rosacontrolplane_webhook.go b/controlplane/rosa/api/v1beta2/rosacontrolplane_webhook.go new file mode 100644 index 0000000000..3ae8191594 --- /dev/null +++ b/controlplane/rosa/api/v1beta2/rosacontrolplane_webhook.go @@ -0,0 +1,139 @@ +package v1beta2 + +import ( + "net" + + "github.com/blang/semver" + kmsArnRegexpValidator "github.com/openshift-online/ocm-common/pkg/resource/validations" + apierrors "k8s.io/apimachinery/pkg/api/errors" + runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +// SetupWebhookWithManager will setup the webhooks for the ROSAControlPlane. +func (r *ROSAControlPlane) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} + +// +kubebuilder:webhook:verbs=create;update,path=/validate-controlplane-cluster-x-k8s-io-v1beta2-rosacontrolplane,mutating=false,failurePolicy=fail,matchPolicy=Equivalent,groups=controlplane.cluster.x-k8s.io,resources=rosacontrolplanes,versions=v1beta2,name=validation.rosacontrolplanes.controlplane.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1 +// +kubebuilder:webhook:verbs=create;update,path=/mutate-controlplane-cluster-x-k8s-io-v1beta2-rosacontrolplane,mutating=true,failurePolicy=fail,matchPolicy=Equivalent,groups=controlplane.cluster.x-k8s.io,resources=rosacontrolplanes,versions=v1beta2,name=default.rosacontrolplanes.controlplane.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1 + +var _ webhook.Defaulter = &ROSAControlPlane{} +var _ webhook.Validator = &ROSAControlPlane{} + +// ValidateCreate implements admission.Validator. +func (r *ROSAControlPlane) ValidateCreate() (warnings admission.Warnings, err error) { + var allErrs field.ErrorList + + if err := r.validateVersion(); err != nil { + allErrs = append(allErrs, err) + } + + if err := r.validateEtcdEncryptionKMSArn(); err != nil { + allErrs = append(allErrs, err) + } + + allErrs = append(allErrs, r.validateNetwork()...) + allErrs = append(allErrs, r.Spec.AdditionalTags.Validate()...) + + if len(allErrs) == 0 { + return nil, nil + } + + return nil, apierrors.NewInvalid( + r.GroupVersionKind().GroupKind(), + r.Name, + allErrs, + ) +} + +// ValidateUpdate implements admission.Validator. +func (r *ROSAControlPlane) ValidateUpdate(old runtime.Object) (warnings admission.Warnings, err error) { + var allErrs field.ErrorList + + if err := r.validateVersion(); err != nil { + allErrs = append(allErrs, err) + } + + if err := r.validateEtcdEncryptionKMSArn(); err != nil { + allErrs = append(allErrs, err) + } + + allErrs = append(allErrs, r.validateNetwork()...) + allErrs = append(allErrs, r.Spec.AdditionalTags.Validate()...) + + if len(allErrs) == 0 { + return nil, nil + } + + return nil, apierrors.NewInvalid( + r.GroupVersionKind().GroupKind(), + r.Name, + allErrs, + ) +} + +// ValidateDelete implements admission.Validator. +func (r *ROSAControlPlane) ValidateDelete() (warnings admission.Warnings, err error) { + return nil, nil +} + +func (r *ROSAControlPlane) validateVersion() *field.Error { + _, err := semver.Parse(r.Spec.Version) + if err != nil { + return field.Invalid(field.NewPath("spec.version"), r.Spec.Version, "must be a valid semantic version") + } + + return nil +} + +func (r *ROSAControlPlane) validateNetwork() field.ErrorList { + var allErrs field.ErrorList + if r.Spec.Network == nil { + return allErrs + } + + rootPath := field.NewPath("spec", "network") + + if r.Spec.Network.MachineCIDR != "" { + _, _, err := net.ParseCIDR(r.Spec.Network.MachineCIDR) + if err != nil { + allErrs = append(allErrs, field.Invalid(rootPath.Child("machineCIDR"), r.Spec.Network.MachineCIDR, "must be valid CIDR block")) + } + } + + if r.Spec.Network.PodCIDR != "" { + _, _, err := net.ParseCIDR(r.Spec.Network.PodCIDR) + if err != nil { + allErrs = append(allErrs, field.Invalid(rootPath.Child("podCIDR"), r.Spec.Network.PodCIDR, "must be valid CIDR block")) + } + } + + if r.Spec.Network.ServiceCIDR != "" { + _, _, err := net.ParseCIDR(r.Spec.Network.ServiceCIDR) + if err != nil { + allErrs = append(allErrs, field.Invalid(rootPath.Child("serviceCIDR"), r.Spec.Network.ServiceCIDR, "must be valid CIDR block")) + } + } + + return allErrs +} + +func (r *ROSAControlPlane) validateEtcdEncryptionKMSArn() *field.Error { + err := kmsArnRegexpValidator.ValidateKMSKeyARN(&r.Spec.EtcdEncryptionKMSARN) + if err != nil { + return field.Invalid(field.NewPath("spec.etcdEncryptionKMSARN"), r.Spec.EtcdEncryptionKMSARN, err.Error()) + } + + return nil +} + +// Default implements admission.Defaulter. +func (r *ROSAControlPlane) Default() { + SetObjectDefaults_ROSAControlPlane(r) +} diff --git a/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go b/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go index 41dac04354..91e37e290a 100644 --- a/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go +++ b/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go @@ -22,7 +22,7 @@ package v1beta2 import ( "k8s.io/api/core/v1" - runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime" apiv1beta2 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" expapiv1beta2 "sigs.k8s.io/cluster-api-provider-aws/v2/exp/api/v1beta2" "sigs.k8s.io/cluster-api/api/v1beta1" @@ -43,6 +43,26 @@ func (in *AWSRolesRef) DeepCopy() *AWSRolesRef { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DefaultMachinePoolSpec) DeepCopyInto(out *DefaultMachinePoolSpec) { + *out = *in + if in.Autoscaling != nil { + in, out := &in.Autoscaling, &out.Autoscaling + *out = new(expapiv1beta2.RosaMachinePoolAutoScaling) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DefaultMachinePoolSpec. +func (in *DefaultMachinePoolSpec) DeepCopy() *DefaultMachinePoolSpec { + if in == nil { + return nil + } + out := new(DefaultMachinePoolSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NetworkSpec) DeepCopyInto(out *NetworkSpec) { *out = *in @@ -130,32 +150,19 @@ func (in *RosaControlPlaneSpec) DeepCopyInto(out *RosaControlPlaneSpec) { *out = make([]string, len(*in)) copy(*out, *in) } - if in.Region != nil { - in, out := &in.Region, &out.Region - *out = new(string) - **out = **in - } - out.ControlPlaneEndpoint = in.ControlPlaneEndpoint out.RolesRef = in.RolesRef - if in.OIDCID != nil { - in, out := &in.OIDCID, &out.OIDCID - *out = new(string) - **out = **in - } - if in.InstallerRoleARN != nil { - in, out := &in.InstallerRoleARN, &out.InstallerRoleARN - *out = new(string) - **out = **in - } - if in.SupportRoleARN != nil { - in, out := &in.SupportRoleARN, &out.SupportRoleARN - *out = new(string) + in.DefaultMachinePoolSpec.DeepCopyInto(&out.DefaultMachinePoolSpec) + if in.Network != nil { + in, out := &in.Network, &out.Network + *out = new(NetworkSpec) **out = **in } - if in.WorkerRoleARN != nil { - in, out := &in.WorkerRoleARN, &out.WorkerRoleARN - *out = new(string) - **out = **in + if in.AdditionalTags != nil { + in, out := &in.AdditionalTags, &out.AdditionalTags + *out = make(apiv1beta2.Tags, len(*in)) + for key, val := range *in { + (*out)[key] = val + } } if in.CredentialsSecretRef != nil { in, out := &in.CredentialsSecretRef, &out.CredentialsSecretRef @@ -167,16 +174,7 @@ func (in *RosaControlPlaneSpec) DeepCopyInto(out *RosaControlPlaneSpec) { *out = new(apiv1beta2.AWSIdentityReference) **out = **in } - if in.Network != nil { - in, out := &in.Network, &out.Network - *out = new(NetworkSpec) - **out = **in - } - if in.Autoscaling != nil { - in, out := &in.Autoscaling, &out.Autoscaling - *out = new(expapiv1beta2.RosaMachinePoolAutoScaling) - **out = **in - } + out.ControlPlaneEndpoint = in.ControlPlaneEndpoint } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RosaControlPlaneSpec. @@ -209,11 +207,6 @@ func (in *RosaControlPlaneStatus) DeepCopyInto(out *RosaControlPlaneStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.ID != nil { - in, out := &in.ID, &out.ID - *out = new(string) - **out = **in - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RosaControlPlaneStatus. diff --git a/controlplane/rosa/api/v1beta2/zz_generated.defaults.go b/controlplane/rosa/api/v1beta2/zz_generated.defaults.go index 510687638d..60d82ff4d7 100644 --- a/controlplane/rosa/api/v1beta2/zz_generated.defaults.go +++ b/controlplane/rosa/api/v1beta2/zz_generated.defaults.go @@ -30,9 +30,17 @@ import ( // All generated defaulters are covering - they call all nested defaulters. func RegisterDefaults(scheme *runtime.Scheme) error { scheme.AddTypeDefaultingFunc(&ROSAControlPlane{}, func(obj interface{}) { SetObjectDefaults_ROSAControlPlane(obj.(*ROSAControlPlane)) }) + scheme.AddTypeDefaultingFunc(&ROSAControlPlaneList{}, func(obj interface{}) { SetObjectDefaults_ROSAControlPlaneList(obj.(*ROSAControlPlaneList)) }) return nil } func SetObjectDefaults_ROSAControlPlane(in *ROSAControlPlane) { SetDefaults_RosaControlPlaneSpec(&in.Spec) } + +func SetObjectDefaults_ROSAControlPlaneList(in *ROSAControlPlaneList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_ROSAControlPlane(a) + } +} diff --git a/controlplane/rosa/controllers/rosacontrolplane_controller.go b/controlplane/rosa/controllers/rosacontrolplane_controller.go index 69e6e31c8a..170992bdfa 100644 --- a/controlplane/rosa/controllers/rosacontrolplane_controller.go +++ b/controlplane/rosa/controllers/rosacontrolplane_controller.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package controllers provides a way to reconcile ROSA resources. package controllers import ( @@ -49,6 +50,7 @@ import ( rosacontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/rosa/api/v1beta2" expinfrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/exp/api/v1beta2" + "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/annotations" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/scope" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/logger" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/rosa" @@ -65,8 +67,12 @@ const ( rosaControlPlaneKind = "ROSAControlPlane" // ROSAControlPlaneFinalizer allows the controller to clean up resources on delete. ROSAControlPlaneFinalizer = "rosacontrolplane.controlplane.cluster.x-k8s.io" + + // ROSAControlPlaneForceDeleteAnnotation annotation can be set to force the deletion of ROSAControlPlane bypassing any deletion validations/errors. + ROSAControlPlaneForceDeleteAnnotation = "controlplane.cluster.x-k8s.io/rosacontrolplane-force-delete" ) +// ROSAControlPlaneReconciler reconciles a ROSAControlPlane object. type ROSAControlPlaneReconciler struct { client.Client WatchFilterValue string @@ -115,6 +121,7 @@ func (r *ROSAControlPlaneReconciler) SetupWithManager(ctx context.Context, mgr c // +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=machinepools,verbs=get;list;watch // +kubebuilder:rbac:groups=controlplane.cluster.x-k8s.io,resources=rosacontrolplanes,verbs=get;list;watch;update;patch;delete // +kubebuilder:rbac:groups=controlplane.cluster.x-k8s.io,resources=rosacontrolplanes/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=controlplane.cluster.x-k8s.io,resources=rosacontrolplanes/finalizers,verbs=update // Reconcile will reconcile RosaControlPlane Resources. func (r *ROSAControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, reterr error) { @@ -178,10 +185,6 @@ func (r *ROSAControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.Req func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaScope *scope.ROSAControlPlaneScope) (res ctrl.Result, reterr error) { rosaScope.Info("Reconciling ROSAControlPlane") - // if !rosaScope.Cluster.Status.InfrastructureReady { - // rosaScope.Info("Cluster infrastructure is not ready yet") - // return ctrl.Result{RequeueAfter: r.WaitInfraPeriod}, nil - //} if controllerutil.AddFinalizer(rosaScope.ControlPlane, ROSAControlPlaneFinalizer) { if err := rosaScope.PatchObject(); err != nil { return ctrl.Result{}, err @@ -199,23 +202,30 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc return ctrl.Result{}, fmt.Errorf("failed to transform caller identity to creator: %w", err) } - if validationMessage, validationError := validateControlPlaneSpec(ocmClient, rosaScope); validationError != nil { - return ctrl.Result{}, fmt.Errorf("validate ROSAControlPlane.spec: %w", validationError) - } else if validationMessage != "" { - rosaScope.ControlPlane.Status.FailureMessage = ptr.To(validationMessage) + validationMessage, err := validateControlPlaneSpec(ocmClient, rosaScope) + if err != nil { + return ctrl.Result{}, fmt.Errorf("failed to validate ROSAControlPlane.spec: %w", err) + } + + conditions.MarkTrue(rosaScope.ControlPlane, rosacontrolplanev1.ROSAControlPlaneValidCondition) + if validationMessage != "" { + conditions.MarkFalse(rosaScope.ControlPlane, + rosacontrolplanev1.ROSAControlPlaneValidCondition, + rosacontrolplanev1.ROSAControlPlaneInvalidConfigurationReason, + clusterv1.ConditionSeverityError, + validationMessage) // dont' requeue because input is invalid and manual intervention is needed. return ctrl.Result{}, nil - } else { - rosaScope.ControlPlane.Status.FailureMessage = nil } + rosaScope.ControlPlane.Status.FailureMessage = nil cluster, err := ocmClient.GetCluster(rosaScope.ControlPlane.Spec.RosaClusterName, creator) - if weberr.GetType(err) != weberr.NotFound && err != nil { + if err != nil && weberr.GetType(err) != weberr.NotFound { return ctrl.Result{}, err } if cluster != nil { - rosaScope.ControlPlane.Status.ID = ptr.To(cluster.ID()) + rosaScope.ControlPlane.Status.ID = cluster.ID() rosaScope.ControlPlane.Status.ConsoleURL = cluster.Console().URL() rosaScope.ControlPlane.Status.OIDCEndpointURL = cluster.AWS().STS().OIDCEndpointURL() rosaScope.ControlPlane.Status.Ready = false @@ -231,12 +241,16 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc } rosaScope.ControlPlane.Spec.ControlPlaneEndpoint = *apiEndpoint - if err := r.reconcileKubeconfig(ctx, rosaScope, ocmClient, cluster); err != nil { - return ctrl.Result{}, fmt.Errorf("failed to reconcile kubeconfig: %w", err) + if err := r.updateOCMCluster(rosaScope, ocmClient, cluster, creator); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to update rosa control plane: %w", err) } if err := r.reconcileClusterVersion(rosaScope, ocmClient, cluster); err != nil { return ctrl.Result{}, err } + if err := r.reconcileKubeconfig(ctx, rosaScope, ocmClient, cluster); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to reconcile kubeconfig: %w", err) + } + return ctrl.Result{}, nil case cmv1.ClusterStateError: errorMessage := cluster.Status().ProvisionErrorMessage() @@ -262,135 +276,27 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc return ctrl.Result{RequeueAfter: time.Second * 60}, nil } - billingAccount := *rosaScope.Identity.Account - if rosaScope.ControlPlane.Spec.BillingAccount != "" { - billingAccount = rosaScope.ControlPlane.Spec.BillingAccount - } - - spec := ocm.Spec{ - DryRun: ptr.To(false), - Name: rosaScope.RosaClusterName(), - Region: *rosaScope.ControlPlane.Spec.Region, - MultiAZ: true, - Version: ocm.CreateVersionID(rosaScope.ControlPlane.Spec.Version, ocm.DefaultChannelGroup), - ChannelGroup: "stable", - Expiration: time.Now().Add(1 * time.Hour), - DisableWorkloadMonitoring: ptr.To(true), - DefaultIngress: ocm.NewDefaultIngressSpec(), // n.b. this is a no-op when it's set to the default value - ComputeMachineType: rosaScope.ControlPlane.Spec.InstanceType, - - SubnetIds: rosaScope.ControlPlane.Spec.Subnets, - AvailabilityZones: rosaScope.ControlPlane.Spec.AvailabilityZones, - NetworkType: rosaScope.ControlPlane.Spec.Network.NetworkType, - IsSTS: true, - RoleARN: *rosaScope.ControlPlane.Spec.InstallerRoleARN, - SupportRoleARN: *rosaScope.ControlPlane.Spec.SupportRoleARN, - OperatorIAMRoles: getOperatorIAMRole(*rosaScope.ControlPlane), - WorkerRoleARN: *rosaScope.ControlPlane.Spec.WorkerRoleARN, - OidcConfigId: *rosaScope.ControlPlane.Spec.OIDCID, - Mode: "auto", - Hypershift: ocm.Hypershift{ - Enabled: true, - }, - BillingAccount: billingAccount, - AWSCreator: creator, - } - - _, machineCIDR, err := net.ParseCIDR(rosaScope.ControlPlane.Spec.Network.MachineCIDR) - if err == nil { - spec.MachineCIDR = *machineCIDR - } else { - // TODO: expose in status - rosaScope.Error(err, "rosacontrolplane.spec.network.machineCIDR invalid", rosaScope.ControlPlane.Spec.Network.MachineCIDR) - return ctrl.Result{}, nil - } - - if rosaScope.ControlPlane.Spec.Network.PodCIDR != "" { - _, podCIDR, err := net.ParseCIDR(rosaScope.ControlPlane.Spec.Network.PodCIDR) - if err == nil { - spec.PodCIDR = *podCIDR - } else { - // TODO: expose in status. - rosaScope.Error(err, "rosacontrolplane.spec.network.podCIDR invalid", rosaScope.ControlPlane.Spec.Network.PodCIDR) - return ctrl.Result{}, nil - } - } - - if rosaScope.ControlPlane.Spec.Network.ServiceCIDR != "" { - _, serviceCIDR, err := net.ParseCIDR(rosaScope.ControlPlane.Spec.Network.ServiceCIDR) - if err == nil { - spec.ServiceCIDR = *serviceCIDR - } else { - // TODO: expose in status. - rosaScope.Error(err, "rosacontrolplane.spec.network.serviceCIDR invalid", rosaScope.ControlPlane.Spec.Network.ServiceCIDR) - return ctrl.Result{}, nil - } - } - - // Set autoscale replica - if rosaScope.ControlPlane.Spec.Autoscaling != nil { - spec.MaxReplicas = rosaScope.ControlPlane.Spec.Autoscaling.MaxReplicas - spec.MinReplicas = rosaScope.ControlPlane.Spec.Autoscaling.MinReplicas + ocmClusterSpec, err := buildOCMClusterSpec(rosaScope.ControlPlane.Spec, creator) + if err != nil { + return ctrl.Result{}, err } - cluster, err = ocmClient.CreateCluster(spec) + cluster, err = ocmClient.CreateCluster(ocmClusterSpec) if err != nil { - // TODO: need to expose in status, as likely the spec is invalid + conditions.MarkFalse(rosaScope.ControlPlane, + rosacontrolplanev1.ROSAControlPlaneReadyCondition, + rosacontrolplanev1.ROSAControlPlaneReconciliationFailedReason, + clusterv1.ConditionSeverityError, + err.Error()) return ctrl.Result{}, fmt.Errorf("failed to create OCM cluster: %w", err) } rosaScope.Info("cluster created", "state", cluster.Status().State()) - clusterID := cluster.ID() - rosaScope.ControlPlane.Status.ID = &clusterID + rosaScope.ControlPlane.Status.ID = cluster.ID() return ctrl.Result{}, nil } -func getOperatorIAMRole(rosaControlPlane rosacontrolplanev1.ROSAControlPlane) []ocm.OperatorIAMRole { - return []ocm.OperatorIAMRole{ - { - Name: "cloud-credentials", - Namespace: "openshift-ingress-operator", - RoleARN: rosaControlPlane.Spec.RolesRef.IngressARN, - }, - { - Name: "installer-cloud-credentials", - Namespace: "openshift-image-registry", - RoleARN: rosaControlPlane.Spec.RolesRef.ImageRegistryARN, - }, - { - Name: "ebs-cloud-credentials", - Namespace: "openshift-cluster-csi-drivers", - RoleARN: rosaControlPlane.Spec.RolesRef.StorageARN, - }, - { - Name: "cloud-credentials", - Namespace: "openshift-cloud-network-config-controller", - RoleARN: rosaControlPlane.Spec.RolesRef.NetworkARN, - }, - { - Name: "kube-controller-manager", - Namespace: "kube-system", - RoleARN: rosaControlPlane.Spec.RolesRef.KubeCloudControllerARN, - }, - { - Name: "kms-provider", - Namespace: "kube-system", - RoleARN: rosaControlPlane.Spec.RolesRef.KMSProviderARN, - }, - { - Name: "control-plane-operator", - Namespace: "kube-system", - RoleARN: rosaControlPlane.Spec.RolesRef.ControlPlaneOperatorARN, - }, - { - Name: "capa-controller-manager", - Namespace: "kube-system", - RoleARN: rosaControlPlane.Spec.RolesRef.NodePoolManagementARN, - }, - } -} - func (r *ROSAControlPlaneReconciler) reconcileDelete(ctx context.Context, rosaScope *scope.ROSAControlPlaneScope) (res ctrl.Result, reterr error) { rosaScope.Info("Reconciling ROSAControlPlane delete") @@ -406,7 +312,7 @@ func (r *ROSAControlPlaneReconciler) reconcileDelete(ctx context.Context, rosaSc } cluster, err := ocmClient.GetCluster(rosaScope.ControlPlane.Spec.RosaClusterName, creator) - if weberr.GetType(err) != weberr.NotFound && err != nil { + if err != nil && weberr.GetType(err) != weberr.NotFound { return ctrl.Result{}, err } if cluster == nil { @@ -415,12 +321,29 @@ func (r *ROSAControlPlaneReconciler) reconcileDelete(ctx context.Context, rosaSc return ctrl.Result{}, nil } + bestEffort := false + if value, found := annotations.Get(rosaScope.ControlPlane, ROSAControlPlaneForceDeleteAnnotation); found && value != "false" { + bestEffort = true + } + if cluster.Status().State() != cmv1.ClusterStateUninstalling { - if _, err := ocmClient.DeleteCluster(cluster.ID(), true, creator); err != nil { + if _, err := ocmClient.DeleteCluster(cluster.ID(), bestEffort, creator); err != nil { + conditions.MarkFalse(rosaScope.ControlPlane, + rosacontrolplanev1.ROSAControlPlaneReadyCondition, + rosacontrolplanev1.ROSAControlPlaneDeletionFailedReason, + clusterv1.ConditionSeverityError, + "failed to delete ROSAControlPlane: %s; if the error can't be resolved, set '%s' annotation to force the deletion", + err.Error(), + ROSAControlPlaneForceDeleteAnnotation) return ctrl.Result{}, err } } + conditions.MarkFalse(rosaScope.ControlPlane, + rosacontrolplanev1.ROSAControlPlaneReadyCondition, + string(cluster.Status().State()), + clusterv1.ConditionSeverityInfo, + "deleting") rosaScope.ControlPlane.Status.Ready = false rosaScope.Info("waiting for cluster to be deleted") // Requeue to remove the finalizer when the cluster is fully deleted. @@ -462,6 +385,29 @@ func (r *ROSAControlPlaneReconciler) reconcileClusterVersion(rosaScope *scope.RO return nil } +func (r *ROSAControlPlaneReconciler) updateOCMCluster(rosaScope *scope.ROSAControlPlaneScope, ocmClient *ocm.Client, cluster *cmv1.Cluster, creator *rosaaws.Creator) error { + currentAuditLogRole := cluster.AWS().AuditLog().RoleArn() + if currentAuditLogRole == rosaScope.ControlPlane.Spec.AuditLogRoleARN { + return nil + } + + ocmClusterSpec := ocm.Spec{ + AuditLogRoleARN: ptr.To(rosaScope.ControlPlane.Spec.AuditLogRoleARN), + } + + // if this fails, the provided role is likely invalid or it doesn't have the required permissions. + if err := ocmClient.UpdateCluster(cluster.ID(), creator, ocmClusterSpec); err != nil { + conditions.MarkFalse(rosaScope.ControlPlane, + rosacontrolplanev1.ROSAControlPlaneValidCondition, + rosacontrolplanev1.ROSAControlPlaneInvalidConfigurationReason, + clusterv1.ConditionSeverityError, + err.Error()) + return err + } + + return nil +} + func (r *ROSAControlPlaneReconciler) reconcileKubeconfig(ctx context.Context, rosaScope *scope.ROSAControlPlaneScope, ocmClient *ocm.Client, cluster *cmv1.Cluster) error { rosaScope.Debug("Reconciling ROSA kubeconfig for cluster", "cluster-name", rosaScope.RosaClusterName()) @@ -580,7 +526,7 @@ func (r *ROSAControlPlaneReconciler) reconcileClusterAdminPassword(ctx context.C func validateControlPlaneSpec(ocmClient *ocm.Client, rosaScope *scope.ROSAControlPlaneScope) (string, error) { version := rosaScope.ControlPlane.Spec.Version - valid, err := ocmClient.ValidateHypershiftVersion(version, "stable") + valid, err := ocmClient.ValidateHypershiftVersion(version, ocm.DefaultChannelGroup) if err != nil { return "", fmt.Errorf("failed to check if version is valid: %w", err) } @@ -592,6 +538,137 @@ func validateControlPlaneSpec(ocmClient *ocm.Client, rosaScope *scope.ROSAContro return "", nil } +func buildOCMClusterSpec(controPlaneSpec rosacontrolplanev1.RosaControlPlaneSpec, creator *rosaaws.Creator) (ocm.Spec, error) { + billingAccount := controPlaneSpec.BillingAccount + if billingAccount == "" { + billingAccount = creator.AccountID + } + + ocmClusterSpec := ocm.Spec{ + DryRun: ptr.To(false), + Name: controPlaneSpec.RosaClusterName, + Region: controPlaneSpec.Region, + MultiAZ: true, + Version: ocm.CreateVersionID(controPlaneSpec.Version, ocm.DefaultChannelGroup), + ChannelGroup: ocm.DefaultChannelGroup, + DisableWorkloadMonitoring: ptr.To(true), + DefaultIngress: ocm.NewDefaultIngressSpec(), // n.b. this is a no-op when it's set to the default value + ComputeMachineType: controPlaneSpec.DefaultMachinePoolSpec.InstanceType, + AvailabilityZones: controPlaneSpec.AvailabilityZones, + Tags: controPlaneSpec.AdditionalTags, + EtcdEncryption: controPlaneSpec.EtcdEncryptionKMSARN != "", + EtcdEncryptionKMSArn: controPlaneSpec.EtcdEncryptionKMSARN, + + SubnetIds: controPlaneSpec.Subnets, + IsSTS: true, + RoleARN: controPlaneSpec.InstallerRoleARN, + SupportRoleARN: controPlaneSpec.SupportRoleARN, + WorkerRoleARN: controPlaneSpec.WorkerRoleARN, + OperatorIAMRoles: operatorIAMRoles(controPlaneSpec.RolesRef), + OidcConfigId: controPlaneSpec.OIDCID, + Mode: "auto", + Hypershift: ocm.Hypershift{ + Enabled: true, + }, + BillingAccount: billingAccount, + AWSCreator: creator, + AuditLogRoleARN: ptr.To(controPlaneSpec.AuditLogRoleARN), + } + + if controPlaneSpec.EndpointAccess == rosacontrolplanev1.Private { + ocmClusterSpec.Private = ptr.To(true) + ocmClusterSpec.PrivateLink = ptr.To(true) + } + + if networkSpec := controPlaneSpec.Network; networkSpec != nil { + if networkSpec.MachineCIDR != "" { + _, machineCIDR, err := net.ParseCIDR(networkSpec.MachineCIDR) + if err != nil { + return ocmClusterSpec, err + } + ocmClusterSpec.MachineCIDR = *machineCIDR + } + + if networkSpec.PodCIDR != "" { + _, podCIDR, err := net.ParseCIDR(networkSpec.PodCIDR) + if err != nil { + return ocmClusterSpec, err + } + ocmClusterSpec.PodCIDR = *podCIDR + } + + if networkSpec.ServiceCIDR != "" { + _, serviceCIDR, err := net.ParseCIDR(networkSpec.ServiceCIDR) + if err != nil { + return ocmClusterSpec, err + } + ocmClusterSpec.ServiceCIDR = *serviceCIDR + } + + ocmClusterSpec.HostPrefix = networkSpec.HostPrefix + ocmClusterSpec.NetworkType = networkSpec.NetworkType + } + + // Set cluster compute autoscaling replicas + if computeAutoscaling := controPlaneSpec.DefaultMachinePoolSpec.Autoscaling; computeAutoscaling != nil { + ocmClusterSpec.MaxReplicas = computeAutoscaling.MaxReplicas + ocmClusterSpec.MinReplicas = computeAutoscaling.MinReplicas + } + + if controPlaneSpec.ProvisionShardID != "" { + ocmClusterSpec.CustomProperties = map[string]string{ + "provision_shard_id": controPlaneSpec.ProvisionShardID, + } + } + + return ocmClusterSpec, nil +} + +func operatorIAMRoles(rolesRef rosacontrolplanev1.AWSRolesRef) []ocm.OperatorIAMRole { + return []ocm.OperatorIAMRole{ + { + Name: "cloud-credentials", + Namespace: "openshift-ingress-operator", + RoleARN: rolesRef.IngressARN, + }, + { + Name: "installer-cloud-credentials", + Namespace: "openshift-image-registry", + RoleARN: rolesRef.ImageRegistryARN, + }, + { + Name: "ebs-cloud-credentials", + Namespace: "openshift-cluster-csi-drivers", + RoleARN: rolesRef.StorageARN, + }, + { + Name: "cloud-credentials", + Namespace: "openshift-cloud-network-config-controller", + RoleARN: rolesRef.NetworkARN, + }, + { + Name: "kube-controller-manager", + Namespace: "kube-system", + RoleARN: rolesRef.KubeCloudControllerARN, + }, + { + Name: "kms-provider", + Namespace: "kube-system", + RoleARN: rolesRef.KMSProviderARN, + }, + { + Name: "control-plane-operator", + Namespace: "kube-system", + RoleARN: rolesRef.ControlPlaneOperatorARN, + }, + { + Name: "capa-controller-manager", + Namespace: "kube-system", + RoleARN: rolesRef.NodePoolManagementARN, + }, + } +} + func (r *ROSAControlPlaneReconciler) rosaClusterToROSAControlPlane(log *logger.Logger) handler.MapFunc { return func(ctx context.Context, o client.Object) []ctrl.Request { rosaCluster, ok := o.(*expinfrav1.ROSACluster) diff --git a/docs/book/cmd/amilist/main.go b/docs/book/cmd/amilist/main.go index 33cc0113e8..a6e5513bbe 100644 --- a/docs/book/cmd/amilist/main.go +++ b/docs/book/cmd/amilist/main.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package main provides a Lambda function to list AMIs and upload them to an S3 bucket. package main import ( diff --git a/docs/book/cmd/clusterawsadmdocs/main.go b/docs/book/cmd/clusterawsadmdocs/main.go index 05c6de2866..69c7c1d42d 100644 --- a/docs/book/cmd/clusterawsadmdocs/main.go +++ b/docs/book/cmd/clusterawsadmdocs/main.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package main provides a way to generate a command reference for clusterawsadm. package main import ( diff --git a/docs/book/src/crd/index.md b/docs/book/src/crd/index.md index 9ee3ff5135..d901e3f6b2 100644 --- a/docs/book/src/crd/index.md +++ b/docs/book/src/crd/index.md @@ -2636,6 +2636,9 @@ string

bootstrap.cluster.x-k8s.io/v1beta2

+

+

Package v1beta2 contains API Schema definitions for the Amazon EKS Bootstrap v1beta2 API group.

+

Resource Types:

DiskSetup @@ -4351,8 +4354,8 @@ AWSIdentityReference -(Optional) -

IdentityRef is a reference to a identity to be used when reconciling the managed control plane.

+

IdentityRef is a reference to an identity to be used when reconciling the managed control plane. +If no identity is specified, the default identity for this controller will be used.

@@ -4757,8 +4760,8 @@ AWSIdentityReference -(Optional) -

IdentityRef is a reference to a identity to be used when reconciling the managed control plane.

+

IdentityRef is a reference to an identity to be used when reconciling the managed control plane. +If no identity is specified, the default identity for this controller will be used.

@@ -5752,6 +5755,7 @@ bool (Appears on:AWSManagedControlPlaneStatus)

+

IdentityProviderStatus holds the status for associated identity provider

@@ -5864,6 +5868,7 @@ string (Appears on:AWSManagedControlPlaneSpec)

+

OIDCIdentityProviderConfig defines the configuration for an OIDC identity provider.

@@ -6168,7 +6173,7 @@ KubernetesMapping

controlplane.cluster.x-k8s.io/v1beta2

-

package v1beta2 contains API Schema definitions for the controlplane v1beta2 API group

+

Package v1beta2 contains API Schema definitions for the controlplane v1beta2 API group

Resource Types:
    @@ -6236,8 +6241,8 @@ AWSIdentityReference @@ -6639,8 +6644,8 @@ AWSIdentityReference @@ -7631,6 +7636,7 @@ bool (Appears on:AWSManagedControlPlaneStatus)

    +

    IdentityProviderStatus holds the status for associated identity provider.

    -(Optional) -

    IdentityRef is a reference to a identity to be used when reconciling the managed control plane.

    +

    IdentityRef is a reference to an identity to be used when reconciling the managed control plane. +If no identity is specified, the default identity for this controller will be used.

    -(Optional) -

    IdentityRef is a reference to a identity to be used when reconciling the managed control plane.

    +

    IdentityRef is a reference to an identity to be used when reconciling the managed control plane. +If no identity is specified, the default identity for this controller will be used.

    @@ -7743,6 +7749,7 @@ string (Appears on:AWSManagedControlPlaneSpec)

    +

    OIDCIdentityProviderConfig represents the configuration for an OIDC identity provider.

    @@ -8059,22 +8066,452 @@ Amazon VPC CNI addon.

    -
    -

    infrastructure.cluster.x-k8s.io/v1beta1

    +

    AWSRolesRef +

    -

    Package v1beta1 contains the v1beta1 API implementation.

    +(Appears on:RosaControlPlaneSpec)

    -Resource Types: - -

    AMIReference +

    +

    AWSRolesRef contains references to various AWS IAM roles required for operators to make calls against the AWS API.

    +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +ingressARN
    + +string + +
    +

    The referenced role must have a trust relationship that allows it to be assumed via web identity. +https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_oidc.html. +Example: +{ +“Version”: “2012-10-17”, +“Statement”: [ +{ +“Effect”: “Allow”, +“Principal”: { +“Federated”: “{{ .ProviderARN }}” +}, +“Action”: “sts:AssumeRoleWithWebIdentity”, +“Condition”: { +“StringEquals”: { +“{{ .ProviderName }}:sub”: {{ .ServiceAccounts }} +} +} +} +] +}

    +

    IngressARN is an ARN value referencing a role appropriate for the Ingress Operator.

    +

    The following is an example of a valid policy document:

    +

    { +“Version”: “2012-10-17”, +“Statement”: [ +{ +“Effect”: “Allow”, +“Action”: [ +“elasticloadbalancing:DescribeLoadBalancers”, +“tag:GetResources”, +“route53:ListHostedZones” +], +“Resource”: “*” +}, +{ +“Effect”: “Allow”, +“Action”: [ +“route53:ChangeResourceRecordSets” +], +“Resource”: [ +“arn:aws:route53:::PUBLIC_ZONE_ID”, +“arn:aws:route53:::PRIVATE_ZONE_ID” +] +} +] +}

    +
    +imageRegistryARN
    + +string + +
    +

    ImageRegistryARN is an ARN value referencing a role appropriate for the Image Registry Operator.

    +

    The following is an example of a valid policy document:

    +

    { +“Version”: “2012-10-17”, +“Statement”: [ +{ +“Effect”: “Allow”, +“Action”: [ +“s3:CreateBucket”, +“s3:DeleteBucket”, +“s3:PutBucketTagging”, +“s3:GetBucketTagging”, +“s3:PutBucketPublicAccessBlock”, +“s3:GetBucketPublicAccessBlock”, +“s3:PutEncryptionConfiguration”, +“s3:GetEncryptionConfiguration”, +“s3:PutLifecycleConfiguration”, +“s3:GetLifecycleConfiguration”, +“s3:GetBucketLocation”, +“s3:ListBucket”, +“s3:GetObject”, +“s3:PutObject”, +“s3:DeleteObject”, +“s3:ListBucketMultipartUploads”, +“s3:AbortMultipartUpload”, +“s3:ListMultipartUploadParts” +], +“Resource”: “*” +} +] +}

    +
    +storageARN
    + +string + +
    +

    StorageARN is an ARN value referencing a role appropriate for the Storage Operator.

    +

    The following is an example of a valid policy document:

    +

    { +“Version”: “2012-10-17”, +“Statement”: [ +{ +“Effect”: “Allow”, +“Action”: [ +“ec2:AttachVolume”, +“ec2:CreateSnapshot”, +“ec2:CreateTags”, +“ec2:CreateVolume”, +“ec2:DeleteSnapshot”, +“ec2:DeleteTags”, +“ec2:DeleteVolume”, +“ec2:DescribeInstances”, +“ec2:DescribeSnapshots”, +“ec2:DescribeTags”, +“ec2:DescribeVolumes”, +“ec2:DescribeVolumesModifications”, +“ec2:DetachVolume”, +“ec2:ModifyVolume” +], +“Resource”: “*” +} +] +}

    +
    +networkARN
    + +string + +
    +

    NetworkARN is an ARN value referencing a role appropriate for the Network Operator.

    +

    The following is an example of a valid policy document:

    +

    { +“Version”: “2012-10-17”, +“Statement”: [ +{ +“Effect”: “Allow”, +“Action”: [ +“ec2:DescribeInstances”, +“ec2:DescribeInstanceStatus”, +“ec2:DescribeInstanceTypes”, +“ec2:UnassignPrivateIpAddresses”, +“ec2:AssignPrivateIpAddresses”, +“ec2:UnassignIpv6Addresses”, +“ec2:AssignIpv6Addresses”, +“ec2:DescribeSubnets”, +“ec2:DescribeNetworkInterfaces” +], +“Resource”: “*” +} +] +}

    +
    +kubeCloudControllerARN
    + +string + +
    +

    KubeCloudControllerARN is an ARN value referencing a role appropriate for the KCM/KCC. +Source: https://cloud-provider-aws.sigs.k8s.io/prerequisites/#iam-policies

    +

    The following is an example of a valid policy document:

    +

    { +“Version”: “2012-10-17”, +“Statement”: [ +{ +“Action”: [ +“autoscaling:DescribeAutoScalingGroups”, +“autoscaling:DescribeLaunchConfigurations”, +“autoscaling:DescribeTags”, +“ec2:DescribeAvailabilityZones”, +“ec2:DescribeInstances”, +“ec2:DescribeImages”, +“ec2:DescribeRegions”, +“ec2:DescribeRouteTables”, +“ec2:DescribeSecurityGroups”, +“ec2:DescribeSubnets”, +“ec2:DescribeVolumes”, +“ec2:CreateSecurityGroup”, +“ec2:CreateTags”, +“ec2:CreateVolume”, +“ec2:ModifyInstanceAttribute”, +“ec2:ModifyVolume”, +“ec2:AttachVolume”, +“ec2:AuthorizeSecurityGroupIngress”, +“ec2:CreateRoute”, +“ec2:DeleteRoute”, +“ec2:DeleteSecurityGroup”, +“ec2:DeleteVolume”, +“ec2:DetachVolume”, +“ec2:RevokeSecurityGroupIngress”, +“ec2:DescribeVpcs”, +“elasticloadbalancing:AddTags”, +“elasticloadbalancing:AttachLoadBalancerToSubnets”, +“elasticloadbalancing:ApplySecurityGroupsToLoadBalancer”, +“elasticloadbalancing:CreateLoadBalancer”, +“elasticloadbalancing:CreateLoadBalancerPolicy”, +“elasticloadbalancing:CreateLoadBalancerListeners”, +“elasticloadbalancing:ConfigureHealthCheck”, +“elasticloadbalancing:DeleteLoadBalancer”, +“elasticloadbalancing:DeleteLoadBalancerListeners”, +“elasticloadbalancing:DescribeLoadBalancers”, +“elasticloadbalancing:DescribeLoadBalancerAttributes”, +“elasticloadbalancing:DetachLoadBalancerFromSubnets”, +“elasticloadbalancing:DeregisterInstancesFromLoadBalancer”, +“elasticloadbalancing:ModifyLoadBalancerAttributes”, +“elasticloadbalancing:RegisterInstancesWithLoadBalancer”, +“elasticloadbalancing:SetLoadBalancerPoliciesForBackendServer”, +“elasticloadbalancing:AddTags”, +“elasticloadbalancing:CreateListener”, +“elasticloadbalancing:CreateTargetGroup”, +“elasticloadbalancing:DeleteListener”, +“elasticloadbalancing:DeleteTargetGroup”, +“elasticloadbalancing:DeregisterTargets”, +“elasticloadbalancing:DescribeListeners”, +“elasticloadbalancing:DescribeLoadBalancerPolicies”, +“elasticloadbalancing:DescribeTargetGroups”, +“elasticloadbalancing:DescribeTargetHealth”, +“elasticloadbalancing:ModifyListener”, +“elasticloadbalancing:ModifyTargetGroup”, +“elasticloadbalancing:RegisterTargets”, +“elasticloadbalancing:SetLoadBalancerPoliciesOfListener”, +“iam:CreateServiceLinkedRole”, +“kms:DescribeKey” +], +“Resource”: [ +“*” +], +“Effect”: “Allow” +} +] +}

    +
    +nodePoolManagementARN
    + +string + +
    +

    NodePoolManagementARN is an ARN value referencing a role appropriate for the CAPI Controller.

    +

    The following is an example of a valid policy document:

    +

    { +“Version”: “2012-10-17”, +“Statement”: [ +{ +“Action”: [ +“ec2:AssociateRouteTable”, +“ec2:AttachInternetGateway”, +“ec2:AuthorizeSecurityGroupIngress”, +“ec2:CreateInternetGateway”, +“ec2:CreateNatGateway”, +“ec2:CreateRoute”, +“ec2:CreateRouteTable”, +“ec2:CreateSecurityGroup”, +“ec2:CreateSubnet”, +“ec2:CreateTags”, +“ec2:DeleteInternetGateway”, +“ec2:DeleteNatGateway”, +“ec2:DeleteRouteTable”, +“ec2:DeleteSecurityGroup”, +“ec2:DeleteSubnet”, +“ec2:DeleteTags”, +“ec2:DescribeAccountAttributes”, +“ec2:DescribeAddresses”, +“ec2:DescribeAvailabilityZones”, +“ec2:DescribeImages”, +“ec2:DescribeInstances”, +“ec2:DescribeInternetGateways”, +“ec2:DescribeNatGateways”, +“ec2:DescribeNetworkInterfaces”, +“ec2:DescribeNetworkInterfaceAttribute”, +“ec2:DescribeRouteTables”, +“ec2:DescribeSecurityGroups”, +“ec2:DescribeSubnets”, +“ec2:DescribeVpcs”, +“ec2:DescribeVpcAttribute”, +“ec2:DescribeVolumes”, +“ec2:DetachInternetGateway”, +“ec2:DisassociateRouteTable”, +“ec2:DisassociateAddress”, +“ec2:ModifyInstanceAttribute”, +“ec2:ModifyNetworkInterfaceAttribute”, +“ec2:ModifySubnetAttribute”, +“ec2:RevokeSecurityGroupIngress”, +“ec2:RunInstances”, +“ec2:TerminateInstances”, +“tag:GetResources”, +“ec2:CreateLaunchTemplate”, +“ec2:CreateLaunchTemplateVersion”, +“ec2:DescribeLaunchTemplates”, +“ec2:DescribeLaunchTemplateVersions”, +“ec2:DeleteLaunchTemplate”, +“ec2:DeleteLaunchTemplateVersions” +], +“Resource”: [ +“” +], +“Effect”: “Allow” +}, +{ +“Condition”: { +“StringLike”: { +“iam:AWSServiceName”: “elasticloadbalancing.amazonaws.com” +} +}, +“Action”: [ +“iam:CreateServiceLinkedRole” +], +“Resource”: [ +“arn::iam:::role/aws-service-role/elasticloadbalancing.amazonaws.com/AWSServiceRoleForElasticLoadBalancing” +], +“Effect”: “Allow” +}, +{ +“Action”: [ +“iam:PassRole” +], +“Resource”: [ +“arn::iam:::role/-worker-role” +], +“Effect”: “Allow” +}, +{ +“Effect”: “Allow”, +“Action”: [ +“kms:Decrypt”, +“kms:ReEncrypt”, +“kms:GenerateDataKeyWithoutPlainText”, +“kms:DescribeKey” +], +“Resource”: “” +}, +{ +“Effect”: “Allow”, +“Action”: [ +“kms:CreateGrant” +], +“Resource”: “”, +“Condition”: { +“Bool”: { +“kms:GrantIsForAWSResource”: true +} +} +} +] +}

    +
    +controlPlaneOperatorARN
    + +string + +
    +

    ControlPlaneOperatorARN is an ARN value referencing a role appropriate for the Control Plane Operator.

    +

    The following is an example of a valid policy document:

    +

    { +“Version”: “2012-10-17”, +“Statement”: [ +{ +“Effect”: “Allow”, +“Action”: [ +“ec2:CreateVpcEndpoint”, +“ec2:DescribeVpcEndpoints”, +“ec2:ModifyVpcEndpoint”, +“ec2:DeleteVpcEndpoints”, +“ec2:CreateTags”, +“route53:ListHostedZones”, +“ec2:CreateSecurityGroup”, +“ec2:AuthorizeSecurityGroupIngress”, +“ec2:AuthorizeSecurityGroupEgress”, +“ec2:DeleteSecurityGroup”, +“ec2:RevokeSecurityGroupIngress”, +“ec2:RevokeSecurityGroupEgress”, +“ec2:DescribeSecurityGroups”, +“ec2:DescribeVpcs”, +], +“Resource”: “*” +}, +{ +“Effect”: “Allow”, +“Action”: [ +“route53:ChangeResourceRecordSets”, +“route53:ListResourceRecordSets” +], +“Resource”: “arn:aws:route53:::%s” +} +] +}

    +
    +kmsProviderARN
    + +string + +
    +
    +

    DefaultMachinePoolSpec

    -(Appears on:AWSMachineSpec) +(Appears on:RosaControlPlaneSpec)

    -

    AMIReference is a reference to a specific AWS resource by ID, ARN, or filters. -Only one of ID, ARN or Filters may be specified. Specifying more than one will result in -a validation error.

    +

    DefaultMachinePoolSpec defines the configuration for the required worker nodes provisioned as part of the cluster creation.

    @@ -8086,36 +8523,114 @@ a validation error.

    -id
    +instanceType
    string
    (Optional) -

    ID of resource

    +

    The instance type to use, for example r5.xlarge. Instance type ref; https://aws.amazon.com/ec2/instance-types/

    -eksLookupType
    +autoscaling
    - -EKSAMILookupType + +RosaMachinePoolAutoScaling
    (Optional) -

    EKSOptimizedLookupType If specified, will look up an EKS Optimized image in SSM Parameter store

    +

    Autoscaling specifies auto scaling behaviour for this MachinePool.

    -

    AWSCluster +

    NetworkSpec

    -

    AWSCluster is the schema for Amazon EC2 based Kubernetes Cluster API.

    +(Appears on:RosaControlPlaneSpec) +

    +

    +

    NetworkSpec for ROSA-HCP.

    +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +machineCIDR
    + +string + +
    +(Optional) +

    IP addresses block used by OpenShift while installing the cluster, for example “10.0.0.0/16”.

    +
    +podCIDR
    + +string + +
    +(Optional) +

    IP address block from which to assign pod IP addresses, for example 10.128.0.0/14.

    +
    +serviceCIDR
    + +string + +
    +(Optional) +

    IP address block from which to assign service IP addresses, for example 172.30.0.0/16.

    +
    +hostPrefix
    + +int + +
    +(Optional) +

    Network host prefix which is defaulted to 23 if not specified.

    +
    +networkType
    + +string + +
    +(Optional) +

    The CNI network type default is OVNKubernetes.

    +
    +

    ROSAControlPlane +

    +

    +

    ROSAControlPlane is the Schema for the ROSAControlPlanes API.

    @@ -8143,8 +8658,8 @@ Refer to the Kubernetes API documentation for the fields of the @@ -8154,250 +8669,1083 @@ AWSClusterSpec
    spec
    - -AWSClusterSpec + +RosaControlPlaneSpec
    - -
    -network
    +rosaClusterName
    - -NetworkSpec - +string
    -

    NetworkSpec encapsulates all things related to AWS network.

    +

    Cluster name must be valid DNS-1035 label, so it must consist of lower case alphanumeric +characters or ‘-’, start with an alphabetic character, end with an alphanumeric character +and have a max length of 54 characters.

    -region
    +domainPrefix
    string
    -

    The AWS Region the cluster lives in.

    +(Optional) +

    DomainPrefix is an optional prefix added to the cluster’s domain name. It will be used +when generating a sub-domain for the cluster on openshiftapps domain. It must be valid DNS-1035 label +consisting of lower case alphanumeric characters or ‘-’, start with an alphabetic character +end with an alphanumeric character and have a max length of 15 characters.

    -sshKeyName
    +subnets
    -string +[]string
    -(Optional) -

    SSHKeyName is the name of the ssh key to attach to the bastion host. Valid values are empty string (do not use SSH keys), a valid SSH key name, or omitted (use the default SSH key name)

    +

    The Subnet IDs to use when installing the cluster. +SubnetIDs should come in pairs; two per availability zone, one private and one public.

    -controlPlaneEndpoint
    +availabilityZones
    - -Cluster API api/v1beta1.APIEndpoint - +[]string
    -(Optional) -

    ControlPlaneEndpoint represents the endpoint used to communicate with the control plane.

    +

    AvailabilityZones describe AWS AvailabilityZones of the worker nodes. +should match the AvailabilityZones of the provided Subnets. +a machinepool will be created for each availabilityZone.

    -additionalTags
    +region
    - -Tags - +string
    -(Optional) -

    AdditionalTags is an optional set of tags to add to AWS resources managed by the AWS provider, in addition to the -ones added by default.

    +

    The AWS Region the cluster lives in.

    -controlPlaneLoadBalancer
    +version
    - -AWSLoadBalancerSpec - +string
    -(Optional) -

    ControlPlaneLoadBalancer is optional configuration for customizing control plane behavior.

    +

    OpenShift semantic version, for example “4.14.5”.

    -imageLookupFormat
    +rolesRef
    -string + +AWSRolesRef +
    -(Optional) -

    ImageLookupFormat is the AMI naming format to look up machine images when -a machine does not specify an AMI. When set, this will be used for all -cluster machines unless a machine specifies a different ImageLookupOrg. -Supports substitutions for {{.BaseOS}} and {{.K8sVersion}} with the base -OS and kubernetes version, respectively. The BaseOS will be the value in -ImageLookupBaseOS or ubuntu (the default), and the kubernetes version as -defined by the packages produced by kubernetes/release without v as a -prefix: 1.13.0, 1.12.5-mybuild.1, or 1.17.3. For example, the default -image format of capa-ami-{{.BaseOS}}-?{{.K8sVersion}}-* will end up -searching for AMIs that match the pattern capa-ami-ubuntu-?1.18.0-* for a -Machine that is targeting kubernetes v1.18.0 and the ubuntu base OS. See -also: https://golang.org/pkg/text/template/

    +

    AWS IAM roles used to perform credential requests by the openshift operators.

    -imageLookupOrg
    +oidcID
    string
    -(Optional) -

    ImageLookupOrg is the AWS Organization ID to look up machine images when a -machine does not specify an AMI. When set, this will be used for all -cluster machines unless a machine specifies a different ImageLookupOrg.

    +

    The ID of the OpenID Connect Provider.

    -imageLookupBaseOS
    +installerRoleARN
    string
    -

    ImageLookupBaseOS is the name of the base operating system used to look -up machine images when a machine does not specify an AMI. When set, this -will be used for all cluster machines unless a machine specifies a -different ImageLookupBaseOS.

    +

    InstallerRoleARN is an AWS IAM role that OpenShift Cluster Manager will assume to create the cluster..

    -bastion
    +supportRoleARN
    - -Bastion - +string
    -(Optional) -

    Bastion contains options to configure the bastion host.

    +

    SupportRoleARN is an AWS IAM role used by Red Hat SREs to enable +access to the cluster account in order to provide support.

    -identityRef
    +workerRoleARN
    - -AWSIdentityReference - +string
    -(Optional) -

    IdentityRef is a reference to a identity to be used when reconciling this cluster

    +

    WorkerRoleARN is an AWS IAM role that will be attached to worker instances.

    -s3Bucket
    +billingAccount
    - -S3Bucket - +string
    (Optional) -

    S3Bucket contains options to configure a supporting S3 bucket for this -cluster - currently used for nodes requiring Ignition -(https://coreos.github.io/ignition/) for bootstrapping (requires -BootstrapFormatIgnition feature flag to be enabled).

    -
    +

    BillingAccount is an optional AWS account to use for billing the subscription fees for ROSA clusters. +The cost of running each ROSA cluster will be billed to the infrastructure account in which the cluster +is running.

    -status
    +defaultMachinePoolSpec
    - -AWSClusterStatus + +DefaultMachinePoolSpec +(Optional) +

    DefaultMachinePoolSpec defines the configuration for the default machinepool(s) provisioned as part of the cluster creation. +One MachinePool will be created with this configuration per AvailabilityZone. Those default machinepools are required for openshift cluster operators +to work properly. +As these machinepool not created using ROSAMachinePool CR, they will not be visible/managed by ROSA CAPI provider. +rosa list machinepools -c <rosaClusterName> can be used to view those machinepools.

    +

    This field will be removed in the future once the current limitation is resolved.

    - - -

    AWSClusterControllerIdentity -

    -

    -

    AWSClusterControllerIdentity is the Schema for the awsclustercontrolleridentities API -It is used to grant access to use Cluster API Provider AWS Controller credentials.

    -

    - - - - - - - - + + + + + + +
    FieldDescription
    -metadata
    +network
    - -Kubernetes meta/v1.ObjectMeta + +NetworkSpec
    -Refer to the Kubernetes API documentation for the fields of the -metadata field. +(Optional) +

    Network config for the ROSA HCP cluster.

    -spec
    +endpointAccess
    - -AWSClusterControllerIdentitySpec + +RosaEndpointAccessType
    -

    Spec for this AWSClusterControllerIdentity.

    -
    -
    - +(Optional) +

    EndpointAccess specifies the publishing scope of cluster endpoints. The +default is Public.

    + + + + + + + + + + + + + + + + + + + + + + + +
    -AWSClusterIdentitySpec
    +additionalTags
    - -AWSClusterIdentitySpec + +Tags
    -

    -(Members of AWSClusterIdentitySpec are embedded into this type.) +(Optional) +

    AdditionalTags are user-defined tags to be added on the AWS resources associated with the control plane.

    +
    +etcdEncryptionKMSARN
    + +string + +
    +(Optional) +

    EtcdEncryptionKMSARN is the ARN of the KMS key used to encrypt etcd. The key itself needs to be +created out-of-band by the user and tagged with red-hat:true.

    +
    +auditLogRoleARN
    + +string + +
    +(Optional) +

    AuditLogRoleARN defines the role that is used to forward audit logs to AWS CloudWatch. +If not set, audit log forwarding is disabled.

    +
    +credentialsSecretRef
    + + +Kubernetes core/v1.LocalObjectReference + + +
    +(Optional) +

    CredentialsSecretRef references a secret with necessary credentials to connect to the OCM API. +The secret should contain the following data keys: +- ocmToken: eyJhbGciOiJIUzI1NiIsI…. +- ocmApiUrl: Optional, defaults to ‘https://api.openshift.com’

    +
    +identityRef
    + + +AWSIdentityReference + + +
    +(Optional) +

    IdentityRef is a reference to an identity to be used when reconciling the managed control plane. +If no identity is specified, the default identity for this controller will be used.

    +
    +controlPlaneEndpoint
    + + +Cluster API api/v1beta1.APIEndpoint + + +
    +(Optional) +

    ControlPlaneEndpoint represents the endpoint used to communicate with the control plane.

    +
    +
    +status
    + + +RosaControlPlaneStatus + + +
    +
    +

    RosaControlPlaneSpec +

    +

    +(Appears on:ROSAControlPlane) +

    +

    +

    RosaControlPlaneSpec defines the desired state of ROSAControlPlane.

    +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +rosaClusterName
    + +string + +
    +

    Cluster name must be valid DNS-1035 label, so it must consist of lower case alphanumeric +characters or ‘-’, start with an alphabetic character, end with an alphanumeric character +and have a max length of 54 characters.

    +
    +domainPrefix
    + +string + +
    +(Optional) +

    DomainPrefix is an optional prefix added to the cluster’s domain name. It will be used +when generating a sub-domain for the cluster on openshiftapps domain. It must be valid DNS-1035 label +consisting of lower case alphanumeric characters or ‘-’, start with an alphabetic character +end with an alphanumeric character and have a max length of 15 characters.

    +
    +subnets
    + +[]string + +
    +

    The Subnet IDs to use when installing the cluster. +SubnetIDs should come in pairs; two per availability zone, one private and one public.

    +
    +availabilityZones
    + +[]string + +
    +

    AvailabilityZones describe AWS AvailabilityZones of the worker nodes. +should match the AvailabilityZones of the provided Subnets. +a machinepool will be created for each availabilityZone.

    +
    +region
    + +string + +
    +

    The AWS Region the cluster lives in.

    +
    +version
    + +string + +
    +

    OpenShift semantic version, for example “4.14.5”.

    +
    +rolesRef
    + + +AWSRolesRef + + +
    +

    AWS IAM roles used to perform credential requests by the openshift operators.

    +
    +oidcID
    + +string + +
    +

    The ID of the OpenID Connect Provider.

    +
    +installerRoleARN
    + +string + +
    +

    InstallerRoleARN is an AWS IAM role that OpenShift Cluster Manager will assume to create the cluster..

    +
    +supportRoleARN
    + +string + +
    +

    SupportRoleARN is an AWS IAM role used by Red Hat SREs to enable +access to the cluster account in order to provide support.

    +
    +workerRoleARN
    + +string + +
    +

    WorkerRoleARN is an AWS IAM role that will be attached to worker instances.

    +
    +billingAccount
    + +string + +
    +(Optional) +

    BillingAccount is an optional AWS account to use for billing the subscription fees for ROSA clusters. +The cost of running each ROSA cluster will be billed to the infrastructure account in which the cluster +is running.

    +
    +defaultMachinePoolSpec
    + + +DefaultMachinePoolSpec + + +
    +(Optional) +

    DefaultMachinePoolSpec defines the configuration for the default machinepool(s) provisioned as part of the cluster creation. +One MachinePool will be created with this configuration per AvailabilityZone. Those default machinepools are required for openshift cluster operators +to work properly. +As these machinepool not created using ROSAMachinePool CR, they will not be visible/managed by ROSA CAPI provider. +rosa list machinepools -c <rosaClusterName> can be used to view those machinepools.

    +

    This field will be removed in the future once the current limitation is resolved.

    +
    +network
    + + +NetworkSpec + + +
    +(Optional) +

    Network config for the ROSA HCP cluster.

    +
    +endpointAccess
    + + +RosaEndpointAccessType + + +
    +(Optional) +

    EndpointAccess specifies the publishing scope of cluster endpoints. The +default is Public.

    +
    +additionalTags
    + + +Tags + + +
    +(Optional) +

    AdditionalTags are user-defined tags to be added on the AWS resources associated with the control plane.

    +
    +etcdEncryptionKMSARN
    + +string + +
    +(Optional) +

    EtcdEncryptionKMSARN is the ARN of the KMS key used to encrypt etcd. The key itself needs to be +created out-of-band by the user and tagged with red-hat:true.

    +
    +auditLogRoleARN
    + +string + +
    +(Optional) +

    AuditLogRoleARN defines the role that is used to forward audit logs to AWS CloudWatch. +If not set, audit log forwarding is disabled.

    +
    +credentialsSecretRef
    + + +Kubernetes core/v1.LocalObjectReference + + +
    +(Optional) +

    CredentialsSecretRef references a secret with necessary credentials to connect to the OCM API. +The secret should contain the following data keys: +- ocmToken: eyJhbGciOiJIUzI1NiIsI…. +- ocmApiUrl: Optional, defaults to ‘https://api.openshift.com’

    +
    +identityRef
    + + +AWSIdentityReference + + +
    +(Optional) +

    IdentityRef is a reference to an identity to be used when reconciling the managed control plane. +If no identity is specified, the default identity for this controller will be used.

    +
    +controlPlaneEndpoint
    + + +Cluster API api/v1beta1.APIEndpoint + + +
    +(Optional) +

    ControlPlaneEndpoint represents the endpoint used to communicate with the control plane.

    +
    +

    RosaControlPlaneStatus +

    +

    +(Appears on:ROSAControlPlane) +

    +

    +

    RosaControlPlaneStatus defines the observed state of ROSAControlPlane.

    +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +externalManagedControlPlane
    + +bool + +
    +

    ExternalManagedControlPlane indicates to cluster-api that the control plane +is managed by an external service such as AKS, EKS, GKE, etc.

    +
    +initialized
    + +bool + +
    +(Optional) +

    Initialized denotes whether or not the control plane has the +uploaded kubernetes config-map.

    +
    +ready
    + +bool + +
    +

    Ready denotes that the ROSAControlPlane API Server is ready to receive requests.

    +
    +failureMessage
    + +string + +
    +(Optional) +

    FailureMessage will be set in the event that there is a terminal problem +reconciling the state and will be set to a descriptive error message.

    +

    This field should not be set for transitive errors that a controller +faces that are expected to be fixed automatically over +time (like service outages), but instead indicate that something is +fundamentally wrong with the spec or the configuration of +the controller, and that manual intervention is required.

    +
    +conditions
    + + +Cluster API api/v1beta1.Conditions + + +
    +

    Conditions specifies the conditions for the managed control plane

    +
    +id
    + +string + +
    +

    ID is the cluster ID given by ROSA.

    +
    +consoleURL
    + +string + +
    +

    ConsoleURL is the url for the openshift console.

    +
    +oidcEndpointURL
    + +string + +
    +

    OIDCEndpointURL is the endpoint url for the managed OIDC provider.

    +
    +

    RosaEndpointAccessType +(string alias)

    +

    +(Appears on:RosaControlPlaneSpec) +

    +

    +

    RosaEndpointAccessType specifies the publishing scope of cluster endpoints.

    +

    + + + + + + + + + + + + +
    ValueDescription

    "Private"

    Private endpoint access allows only private API server access and private +node communication with the control plane.

    +

    "Public"

    Public endpoint access allows public API server access and +private node communication with the control plane.

    +
    +
    +

    infrastructure.cluster.x-k8s.io/v1beta1

    +

    +

    Package v1beta1 contains the v1beta1 API implementation.

    +

    +Resource Types: + +

    AMIReference +

    +

    +(Appears on:AWSMachineSpec) +

    +

    +

    AMIReference is a reference to a specific AWS resource by ID, ARN, or filters. +Only one of ID, ARN or Filters may be specified. Specifying more than one will result in +a validation error.

    +

    + + + + + + + + + + + + + + + + + +
    FieldDescription
    +id
    + +string + +
    +(Optional) +

    ID of resource

    +
    +eksLookupType
    + + +EKSAMILookupType + + +
    +(Optional) +

    EKSOptimizedLookupType If specified, will look up an EKS Optimized image in SSM Parameter store

    +
    +

    AWSCluster +

    +

    +

    AWSCluster is the schema for Amazon EC2 based Kubernetes Cluster API.

    +

    + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +metadata
    + + +Kubernetes meta/v1.ObjectMeta + + +
    +Refer to the Kubernetes API documentation for the fields of the +metadata field. +
    +spec
    + + +AWSClusterSpec + + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +network
    + + +NetworkSpec + + +
    +

    NetworkSpec encapsulates all things related to AWS network.

    +
    +region
    + +string + +
    +

    The AWS Region the cluster lives in.

    +
    +sshKeyName
    + +string + +
    +(Optional) +

    SSHKeyName is the name of the ssh key to attach to the bastion host. Valid values are empty string (do not use SSH keys), a valid SSH key name, or omitted (use the default SSH key name)

    +
    +controlPlaneEndpoint
    + + +Cluster API api/v1beta1.APIEndpoint + + +
    +(Optional) +

    ControlPlaneEndpoint represents the endpoint used to communicate with the control plane.

    +
    +additionalTags
    + + +Tags + + +
    +(Optional) +

    AdditionalTags is an optional set of tags to add to AWS resources managed by the AWS provider, in addition to the +ones added by default.

    +
    +controlPlaneLoadBalancer
    + + +AWSLoadBalancerSpec + + +
    +(Optional) +

    ControlPlaneLoadBalancer is optional configuration for customizing control plane behavior.

    +
    +imageLookupFormat
    + +string + +
    +(Optional) +

    ImageLookupFormat is the AMI naming format to look up machine images when +a machine does not specify an AMI. When set, this will be used for all +cluster machines unless a machine specifies a different ImageLookupOrg. +Supports substitutions for {{.BaseOS}} and {{.K8sVersion}} with the base +OS and kubernetes version, respectively. The BaseOS will be the value in +ImageLookupBaseOS or ubuntu (the default), and the kubernetes version as +defined by the packages produced by kubernetes/release without v as a +prefix: 1.13.0, 1.12.5-mybuild.1, or 1.17.3. For example, the default +image format of capa-ami-{{.BaseOS}}-?{{.K8sVersion}}-* will end up +searching for AMIs that match the pattern capa-ami-ubuntu-?1.18.0-* for a +Machine that is targeting kubernetes v1.18.0 and the ubuntu base OS. See +also: https://golang.org/pkg/text/template/

    +
    +imageLookupOrg
    + +string + +
    +(Optional) +

    ImageLookupOrg is the AWS Organization ID to look up machine images when a +machine does not specify an AMI. When set, this will be used for all +cluster machines unless a machine specifies a different ImageLookupOrg.

    +
    +imageLookupBaseOS
    + +string + +
    +

    ImageLookupBaseOS is the name of the base operating system used to look +up machine images when a machine does not specify an AMI. When set, this +will be used for all cluster machines unless a machine specifies a +different ImageLookupBaseOS.

    +
    +bastion
    + + +Bastion + + +
    +(Optional) +

    Bastion contains options to configure the bastion host.

    +
    +identityRef
    + + +AWSIdentityReference + + +
    +

    IdentityRef is a reference to an identity to be used when reconciling the managed control plane. +If no identity is specified, the default identity for this controller will be used.

    +
    +s3Bucket
    + + +S3Bucket + + +
    +(Optional) +

    S3Bucket contains options to configure a supporting S3 bucket for this +cluster - currently used for nodes requiring Ignition +(https://coreos.github.io/ignition/) for bootstrapping (requires +BootstrapFormatIgnition feature flag to be enabled).

    +
    +
    +status
    + + +AWSClusterStatus + + +
    +
    +

    AWSClusterControllerIdentity +

    +

    +

    AWSClusterControllerIdentity is the Schema for the awsclustercontrolleridentities API +It is used to grant access to use Cluster API Provider AWS Controller credentials.

    +

    + + + + + + + + + + + + + + + @@ -17662,6 +19040,19 @@ the cluster subnet will be used.

    + + + + + + + +
    FieldDescription
    +metadata
    + + +Kubernetes meta/v1.ObjectMeta + + +
    +Refer to the Kubernetes API documentation for the fields of the +metadata field. +
    +spec
    + + +AWSClusterControllerIdentitySpec + + +
    +

    Spec for this AWSClusterControllerIdentity.

    +
    +
    + + + + @@ -8838,8 +10186,8 @@ AWSIdentityReference @@ -9125,6 +10473,7 @@ AWSClusterTemplateResource (Appears on:AWSClusterTemplateSpec)

    +

    AWSClusterTemplateResource defines the desired state of AWSClusterTemplate.

    +AWSClusterIdentitySpec
    + + +AWSClusterIdentitySpec + + +
    +

    +(Members of AWSClusterIdentitySpec are embedded into this type.)

    -(Optional) -

    IdentityRef is a reference to a identity to be used when reconciling this cluster

    +

    IdentityRef is a reference to an identity to be used when reconciling the managed control plane. +If no identity is specified, the default identity for this controller will be used.

    @@ -9318,8 +10667,8 @@ AWSIdentityReference @@ -12373,6 +13722,7 @@ string (Appears on:AWSClusterSpec)

    +

    S3Bucket defines a supporting S3 bucket for the cluster, currently can be optionally used for Ignition.

    -(Optional) -

    IdentityRef is a reference to a identity to be used when reconciling this cluster

    +

    IdentityRef is a reference to an identity to be used when reconciling the managed control plane. +If no identity is specified, the default identity for this controller will be used.

    @@ -15470,7 +16820,7 @@ percentage of nodes will be updated in parallel, up to 100 nodes at once.


    infrastructure.cluster.x-k8s.io/v1beta2

    -

    package v1beta2 contains the v1beta2 API implementation.

    +

    Package v1beta2 contains the v1beta2 API implementation.

    Resource Types:
      @@ -15742,8 +17092,8 @@ AWSIdentityReference @@ -16302,8 +17652,8 @@ AWSIdentityReference @@ -16589,6 +17939,7 @@ AWSClusterTemplateResource (Appears on:AWSClusterTemplateSpec)

      +

      AWSClusterTemplateResource defines the desired state of AWSClusterTemplateResource.

      -(Optional) -

      IdentityRef is a reference to a identity to be used when reconciling this cluster

      +

      IdentityRef is a reference to an identity to be used when reconciling the managed control plane. +If no identity is specified, the default identity for this controller will be used.

      -(Optional) -

      IdentityRef is a reference to a identity to be used when reconciling this cluster

      +

      IdentityRef is a reference to an identity to be used when reconciling the managed control plane. +If no identity is specified, the default identity for this controller will be used.

      @@ -16810,8 +18161,8 @@ AWSIdentityReference @@ -16877,7 +18228,7 @@ AWSClusterTemplateResource

      AWSIdentityReference

      -(Appears on:AWSClusterRoleIdentitySpec, AWSClusterSpec, AWSManagedControlPlaneSpec, AWSManagedControlPlaneSpec) +(Appears on:AWSClusterRoleIdentitySpec, AWSClusterSpec, AWSManagedControlPlaneSpec, AWSManagedControlPlaneSpec, RosaControlPlaneSpec)

      AWSIdentityReference specifies a identity.

      @@ -17309,6 +18660,19 @@ the cluster subnet will be used.

      + + + + + + + +
      -(Optional) -

      IdentityRef is a reference to a identity to be used when reconciling this cluster

      +

      IdentityRef is a reference to an identity to be used when reconciling the managed control plane. +If no identity is specified, the default identity for this controller will be used.

      +securityGroupOverrides
      + +map[sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2.SecurityGroupRole]string + +
      +(Optional) +

      SecurityGroupOverrides is an optional set of security groups to use for the node. +This is optional - if not provided security groups from the cluster will be used.

      +
      sshKeyName
      string @@ -17441,6 +18805,20 @@ string

      Tenancy indicates if instance should run on shared or single-tenant hardware.

      +privateDnsName
      + + +PrivateDNSName + + +
      +(Optional) +

      PrivateDNSName is the options for the instance hostname.

      +
      +securityGroupOverrides
      + +map[sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2.SecurityGroupRole]string + +
      +(Optional) +

      SecurityGroupOverrides is an optional set of security groups to use for the node. +This is optional - if not provided security groups from the cluster will be used.

      +
      sshKeyName
      string @@ -17794,6 +19185,20 @@ string

      Tenancy indicates if instance should run on shared or single-tenant hardware.

      +privateDnsName
      + + +PrivateDNSName + + +
      +(Optional) +

      PrivateDNSName is the options for the instance hostname.

      +

      AWSMachineStatus @@ -18231,6 +19636,19 @@ the cluster subnet will be used.

      +securityGroupOverrides
      + +map[sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2.SecurityGroupRole]string + + + +(Optional) +

      SecurityGroupOverrides is an optional set of security groups to use for the node. +This is optional - if not provided security groups from the cluster will be used.

      + + + + sshKeyName
      string @@ -18363,6 +19781,20 @@ string

      Tenancy indicates if instance should run on shared or single-tenant hardware.

      + + +privateDnsName
      + + +PrivateDNSName + + + + +(Optional) +

      PrivateDNSName is the options for the instance hostname.

      + + @@ -19396,6 +20828,7 @@ string

      GCTask (string alias)

      +

      GCTask defines a task to be executed by the garbage collector.

      HTTPTokensState (string alias)

      @@ -19536,7 +20969,8 @@ Mutually exclusive with CidrBlock.

      (Appears on:AWSMachineSpec)

      -

      Ignition defines options related to the bootstrapping systems where Ignition is used.

      +

      Ignition defines options related to the bootstrapping systems where Ignition is used. +For more information on Ignition configuration, see https://coreos.github.io/butane/specs/

      @@ -19558,6 +20992,187 @@ string

      Version defines which version of Ignition will be used to generate bootstrap data.

      + + + + + + + + + + + + + +
      +storageType
      + + +IgnitionStorageTypeOption + + +
      +(Optional) +

      StorageType defines how to store the boostrap user data for Ignition. +This can be used to instruct Ignition from where to fetch the user data to bootstrap an instance.

      +

      When omitted, the storage option will default to ClusterObjectStore.

      +

      When set to “ClusterObjectStore”, if the capability is available and a Cluster ObjectStore configuration +is correctly provided in the Cluster object (under .spec.s3Bucket), +an object store will be used to store bootstrap user data.

      +

      When set to “UnencryptedUserData”, EC2 Instance User Data will be used to store the machine bootstrap user data, unencrypted. +This option is considered less secure than others as user data may contain sensitive informations (keys, certificates, etc.) +and users with ec2:DescribeInstances permission or users running pods +that can access the ec2 metadata service have access to this sensitive information. +So this is only to be used at ones own risk, and only when other more secure options are not viable.

      +
      +proxy
      + + +IgnitionProxy + + +
      +(Optional) +

      Proxy defines proxy settings for Ignition. +Only valid for Ignition versions 3.1 and above.

      +
      +tls
      + + +IgnitionTLS + + +
      +(Optional) +

      TLS defines TLS settings for Ignition. +Only valid for Ignition versions 3.1 and above.

      +
      +

      IgnitionCASource +(string alias)

      +

      +(Appears on:IgnitionTLS) +

      +

      +

      IgnitionCASource defines the source of the certificate authority to use for Ignition.

      +

      +

      IgnitionNoProxy +(string alias)

      +

      +(Appears on:IgnitionProxy) +

      +

      +

      IgnitionNoProxy defines the list of domains to not proxy for Ignition.

      +

      +

      IgnitionProxy +

      +

      +(Appears on:Ignition) +

      +

      +

      IgnitionProxy defines proxy settings for Ignition.

      +

      + + + + + + + + + + + + + + + + + + + + + +
      FieldDescription
      +httpProxy
      + +string + +
      +(Optional) +

      HTTPProxy is the HTTP proxy to use for Ignition. +A single URL that specifies the proxy server to use for HTTP and HTTPS requests, +unless overridden by the HTTPSProxy or NoProxy options.

      +
      +httpsProxy
      + +string + +
      +(Optional) +

      HTTPSProxy is the HTTPS proxy to use for Ignition. +A single URL that specifies the proxy server to use for HTTPS requests, +unless overridden by the NoProxy option.

      +
      +noProxy
      + + +[]IgnitionNoProxy + + +
      +(Optional) +

      NoProxy is the list of domains to not proxy for Ignition. +Specifies a list of strings to hosts that should be excluded from proxying.

      +

      Each value is represented by: +- An IP address prefix (1.2.3.4) +- An IP address prefix in CIDR notation (1.2.3.48) +- A domain name +- A domain name matches that name and all subdomains +- A domain name with a leading . matches subdomains only +- A special DNS label (*), indicates that no proxying should be done

      +

      An IP address prefix and domain name can also include a literal port number (1.2.3.4:80).

      +
      +

      IgnitionStorageTypeOption +(string alias)

      +

      +(Appears on:Ignition) +

      +

      +

      IgnitionStorageTypeOption defines the different storage types for Ignition.

      +

      +

      IgnitionTLS +

      +

      +(Appears on:Ignition) +

      +

      +

      IgnitionTLS defines TLS settings for Ignition.

      +

      + + + + + + + + + + + +
      FieldDescription
      +certificateAuthorities
      + + +[]IgnitionCASource + + +
      +(Optional) +

      CASources defines the list of certificate authorities to use for Ignition. +The value is the certificate bundle (in PEM format). The bundle can contain multiple concatenated certificates. +Supported schemes are http, https, tftp, s3, arn, gs, and data (RFC 2397) URL scheme.

      +

      IngressRule @@ -19981,6 +21596,20 @@ InstanceMetadataOptions

      InstanceMetadataOptions is the metadata options for the EC2 instance.

      + + +privateDnsName
      + + +PrivateDNSName + + + + +(Optional) +

      PrivateDNSName is the options for the instance hostname.

      + +

      InstanceMetadataOptions @@ -20335,6 +21964,7 @@ LoadBalancerType (Appears on:AWSLoadBalancerSpec, LoadBalancer)

      +

      LoadBalancerType defines the type of load balancer to use.

      NetworkSpec

      @@ -20491,6 +22121,60 @@ LoadBalancer +

      PrivateDNSName +

      +

      +(Appears on:AWSMachineSpec, Instance, AWSLaunchTemplate) +

      +

      +

      PrivateDNSName is the options for the instance hostname.

      +

      + + + + + + + + + + + + + + + + + + + + + +
      FieldDescription
      +enableResourceNameDnsAAAARecord
      + +bool + +
      +(Optional) +

      EnableResourceNameDNSAAAARecord indicates whether to respond to DNS queries for instance hostnames with DNS AAAA records.

      +
      +enableResourceNameDnsARecord
      + +bool + +
      +(Optional) +

      EnableResourceNameDNSARecord indicates whether to respond to DNS queries for instance hostnames with DNS A records.

      +
      +hostnameType
      + +string + +
      +(Optional) +

      The type of hostname to assign to an instance.

      +

      ResourceLifecycle (string alias)

      @@ -20530,6 +22214,7 @@ string (Appears on:AWSClusterSpec)

      +

      S3Bucket defines a supporting S3 bucket for the cluster, currently can be optionally used for Ignition.

      @@ -20873,7 +22558,7 @@ Tags

      Tags (map[string]string alias)

      -(Appears on:AWSClusterSpec, AWSMachineSpec, BuildParams, SecurityGroup, SubnetSpec, VPCSpec, AWSIAMRoleSpec, BootstrapUser, AWSIAMRoleSpec, BootstrapUser, AWSManagedControlPlaneSpec, OIDCIdentityProviderConfig, AWSManagedControlPlaneSpec, OIDCIdentityProviderConfig, AWSMachinePoolSpec, AWSManagedMachinePoolSpec, AutoScalingGroup, FargateProfileSpec, AWSMachinePoolSpec, AWSManagedMachinePoolSpec, AutoScalingGroup, FargateProfileSpec) +(Appears on:AWSClusterSpec, AWSMachineSpec, BuildParams, SecurityGroup, SubnetSpec, VPCSpec, AWSIAMRoleSpec, BootstrapUser, AWSIAMRoleSpec, BootstrapUser, AWSManagedControlPlaneSpec, OIDCIdentityProviderConfig, AWSManagedControlPlaneSpec, OIDCIdentityProviderConfig, RosaControlPlaneSpec, AWSMachinePoolSpec, AWSManagedMachinePoolSpec, AutoScalingGroup, FargateProfileSpec, AWSMachinePoolSpec, AWSManagedMachinePoolSpec, AutoScalingGroup, FargateProfileSpec, RosaMachinePoolSpec)

      Tags defines a map of tags.

      @@ -21166,17 +22851,31 @@ Defaults to Ordered

      + + + + @@ -21634,6 +23333,20 @@ InstanceMetadataOptions

      InstanceMetadataOptions defines the behavior for applying metadata to instances.

      + + + +
      emptyRoutesDefaultVPCSecurityGroup
      -bool +bool + +
      +(Optional) +

      EmptyRoutesDefaultVPCSecurityGroup specifies whether the default VPC security group ingress +and egress rules should be removed.

      +

      By default, when creating a VPC, AWS creates a security group called default with ingress and egress +rules that allow traffic from anywhere. The group could be used as a potential surface attack and +it’s generally suggested that the group rules are removed or modified appropriately.

      +

      NOTE: This only applies when the VPC is managed by the Cluster API AWS controller.

      +
      +privateDnsHostnameTypeOnLaunch
      + +string
      (Optional) -

      EmptyRoutesDefaultVPCSecurityGroup specifies whether the default VPC security group ingress -and egress rules should be removed.

      -

      By default, when creating a VPC, AWS creates a security group called default with ingress and egress -rules that allow traffic from anywhere. The group could be used as a potential surface attack and -it’s generally suggested that the group rules are removed or modified appropriately.

      -

      NOTE: This only applies when the VPC is managed by the Cluster API AWS controller.

      +

      PrivateDNSHostnameTypeOnLaunch is the type of hostname to assign to instances in the subnet at launch. +For IPv4-only and dual-stack (IPv4 and IPv6) subnets, an instance DNS name can be based on the instance IPv4 address (ip-name) +or the instance ID (resource-name). For IPv6 only subnets, an instance DNS name must be based on the instance ID (resource-name).

      +privateDnsName
      + + +PrivateDNSName + + +
      +(Optional) +

      PrivateDNSName is the options for the instance hostname.

      +

      AWSMachinePool @@ -21820,6 +23533,23 @@ If no value is supplied by user a default value of 300 seconds is set

      +defaultInstanceWarmup
      + + +Kubernetes meta/v1.Duration + + + + +(Optional) +

      The amount of time, in seconds, until a new instance is considered to +have finished initializing and resource consumption to become stable +after it enters the InService state. +If no value is supplied by user a default value of 300 seconds is set

      + + + + refreshPreferences
      @@ -22077,6 +23807,23 @@ If no value is supplied by user a default value of 300 seconds is set

      +defaultInstanceWarmup
      + +
      +Kubernetes meta/v1.Duration + + + + +(Optional) +

      The amount of time, in seconds, until a new instance is considered to +have finished initializing and resource consumption to become stable +after it enters the InService state. +If no value is supplied by user a default value of 300 seconds is set

      + + + + refreshPreferences
      @@ -23141,6 +24888,18 @@ Kubernetes meta/v1.Duration +defaultInstanceWarmup
      + +
      +Kubernetes meta/v1.Duration + + + + + + + + capacityRebalance
      bool @@ -23918,6 +25677,7 @@ bool

      ROSACluster

      +

      ROSACluster is the Schema for the ROSAClusters API.

      @@ -23991,6 +25751,7 @@ ROSAClusterStatus (Appears on:ROSACluster)

      +

      ROSAClusterSpec defines the desired state of ROSACluster.

      @@ -24022,7 +25783,7 @@ Cluster API api/v1beta1.APIEndpoint (Appears on:ROSACluster)

      -

      ROSAClusterStatus defines the observed state of ROSACluster

      +

      ROSAClusterStatus defines the observed state of ROSACluster.

      @@ -24114,6 +25875,19 @@ must be a valid DNS-1035 label, so it must consist of lower case alphanumeric an + + + + + + + + + + + + + + + + + + + + + + + +
      +version
      + +string + +
      +(Optional) +

      Version specifies the OpenShift version of the nodes associated with this machinepool. +ROSAControlPlane version is used if not set.

      +
      availabilityZone
      string @@ -24150,6 +25924,34 @@ map[string]string
      +taints
      + + +[]RosaTaint + + +
      +(Optional) +

      Taints specifies the taints to apply to the nodes of the machine pool

      +
      +additionalTags
      + + +Tags + + +
      +(Optional) +

      AdditionalTags are user-defined tags to be added on the underlying EC2 instances associated with this machine pool.

      +
      autoRepair
      bool @@ -24189,6 +25991,32 @@ required if Replicas is not configured

      +tuningConfigs
      + +[]string + +
      +(Optional) +

      TuningConfigs specifies the names of the tuning configs to be applied to this MachinePool. +Tuning configs must already exist.

      +
      +additionalSecurityGroups
      + +[]string + +
      +(Optional) +

      AdditionalSecurityGroups is an optional set of security groups to associate +with all node instances of the machine pool.

      +
      providerIDList
      []string @@ -24199,6 +26027,24 @@ required if Replicas is not configured

      ProviderIDList contain a ProviderID for each machine instance that’s currently managed by this machine pool.

      +nodeDrainGracePeriod
      + + +Kubernetes meta/v1.Duration + + +
      +(Optional) +

      NodeDrainGracePeriod is grace period for how long Pod Disruption Budget-protected workloads will be +respected during upgrades. After this grace period, any workloads protected by Pod Disruption +Budgets that have not been successfully drained from a node will be forcibly evicted.

      +

      Valid values are from 0 to 1 week(10080m|168h) . +0 or empty value means that the MachinePool can be drained without any time limitation.

      +
      @@ -24291,7 +26137,7 @@ during an instance refresh. The default is 90.

      RosaMachinePoolAutoScaling

      -(Appears on:RosaMachinePoolSpec) +(Appears on:DefaultMachinePoolSpec, RosaMachinePoolSpec)

      RosaMachinePoolAutoScaling specifies scaling options.

      @@ -24356,6 +26202,19 @@ must be a valid DNS-1035 label, so it must consist of lower case alphanumeric an +version
      + +string + + + +(Optional) +

      Version specifies the OpenShift version of the nodes associated with this machinepool. +ROSAControlPlane version is used if not set.

      + + + + availabilityZone
      string @@ -24392,6 +26251,34 @@ map[string]string +taints
      + + +[]RosaTaint + + + + +(Optional) +

      Taints specifies the taints to apply to the nodes of the machine pool

      + + + + +additionalTags
      + + +Tags + + + + +(Optional) +

      AdditionalTags are user-defined tags to be added on the underlying EC2 instances associated with this machine pool.

      + + + + autoRepair
      bool @@ -24431,6 +26318,32 @@ required if Replicas is not configured

      +tuningConfigs
      + +[]string + + + +(Optional) +

      TuningConfigs specifies the names of the tuning configs to be applied to this MachinePool. +Tuning configs must already exist.

      + + + + +additionalSecurityGroups
      + +[]string + + + +(Optional) +

      AdditionalSecurityGroups is an optional set of security groups to associate +with all node instances of the machine pool.

      + + + + providerIDList
      []string @@ -24441,6 +26354,24 @@ required if Replicas is not configured

      ProviderIDList contain a ProviderID for each machine instance that’s currently managed by this machine pool.

      + + +nodeDrainGracePeriod
      + + +Kubernetes meta/v1.Duration + + + + +(Optional) +

      NodeDrainGracePeriod is grace period for how long Pod Disruption Budget-protected workloads will be +respected during upgrades. After this grace period, any workloads protected by Pod Disruption +Budgets that have not been successfully drained from a node will be forcibly evicted.

      +

      Valid values are from 0 to 1 week(10080m|168h) . +0 or empty value means that the MachinePool can be drained without any time limitation.

      + +

      RosaMachinePoolStatus @@ -24499,6 +26430,24 @@ Cluster API api/v1beta1.Conditions +failureMessage
      + +string + + + +(Optional) +

      FailureMessage will be set in the event that there is a terminal problem +reconciling the state and will be set to a descriptive error message.

      +

      This field should not be set for transitive errors that a controller +faces that are expected to be fixed automatically over +time (like service outages), but instead indicate that something is +fundamentally wrong with the spec or the configuration of +the controller, and that manual intervention is required.

      + + + + id
      string @@ -24510,6 +26459,61 @@ string +

      RosaTaint +

      +

      +(Appears on:RosaMachinePoolSpec) +

      +

      +

      RosaTaint represents a taint to be applied to a node.

      +

      + + + + + + + + + + + + + + + + + + + + + +
      FieldDescription
      +key
      + +string + +
      +

      The taint key to be applied to a node.

      +
      +value
      + +string + +
      +(Optional) +

      The taint value corresponding to the taint key.

      +
      +effect
      + + +Kubernetes core/v1.TaintEffect + + +
      +

      The effect of the taint on pods that do not tolerate the taint. +Valid effects are NoSchedule, PreferNoSchedule and NoExecute.

      +

      SpotAllocationStrategy (string alias)

      diff --git a/docs/book/src/development/releasing.md b/docs/book/src/development/releasing.md index 7a5c2d3761..3884fdce8b 100644 --- a/docs/book/src/development/releasing.md +++ b/docs/book/src/development/releasing.md @@ -7,14 +7,18 @@ ## Create tag, and build staging container images -1. Create a new local repository of (e.g. using `git clone`). +1. Please fork and clone your own repository with e.g. `git clone git@github.com:YourGitHubUsername/cluster-api-provider-aws.git`. `kpromo` uses the fork to build images from. +1. Add a git remote to the upstream project. `git remote add upstream git@github.com:kubernetes-sigs/cluster-api-provider-aws.git` 1. If this is a major or minor release, create a new release branch and push to GitHub, otherwise switch to it, e.g. `git checkout release-1.5`. 1. If this is a major or minor release, update `metadata.yaml` by adding a new section with the version, and make a commit. -1. Update the release branch on the repository, e.g. `git push origin HEAD:release-1.5`. +1. Update the release branch on the repository, e.g. `git push origin HEAD:release-1.5`. `origin` refers to the remote git reference to your fork. +1. Update the release branch on the repository, e.g. `git push upstream HEAD:release-1.5`. `upstream` refers to the upstream git reference. 1. Make sure your repo is clean by git standards. 1. Set environment variable `GITHUB_TOKEN` to a GitHub personal access token. The token must have write access to the `kubernetes-sigs/cluster-api-provider-aws` repository. 1. Set environment variables `PREVIOUS_VERSION` which is the last release tag and `VERSION` which is the current release version, e.g. `export PREVIOUS_VERSION=v1.4.0 VERSION=v1.5.0`, or `export PREVIOUS_VERSION=v1.5.0 VERSION=v1.5.1`). _**Note**_: the version MUST contain a `v` in front. + _**Note**_: you must have a gpg signing configured with git and registered with GitHub. + 1. Create a tag `git tag -s -m $VERSION $VERSION`. `-s` flag is for GNU Privacy Guard (GPG) signing. 1. Make sure you have push permissions to the upstream CAPA repo. Push tag you've just created (`git push $VERSION`). 1. A prow job will start running to push images to the staging repo, can be seen [here](https://testgrid.k8s.io/sig-cluster-lifecycle-image-pushes#post-cluster-api-provider-aws-push-images). The job is called "post-cluster-api-provider-aws-push-images," and is defined in . diff --git a/docs/triage-party/triage-party-deployment.go b/docs/triage-party/triage-party-deployment.go index d7c278521d..53af6beac9 100644 --- a/docs/triage-party/triage-party-deployment.go +++ b/docs/triage-party/triage-party-deployment.go @@ -18,11 +18,9 @@ package main import ( "fmt" - - "github.com/aws/aws-cdk-go/awscdk" - "os" + "github.com/aws/aws-cdk-go/awscdk" "github.com/aws/aws-cdk-go/awscdk/awsecs" "github.com/aws/aws-cdk-go/awscdk/awsecspatterns" "github.com/aws/aws-cdk-go/awscdk/awselasticloadbalancingv2" diff --git a/exp/api/v1beta1/conversion.go b/exp/api/v1beta1/conversion.go index ff55f3b930..16cf651fdf 100644 --- a/exp/api/v1beta1/conversion.go +++ b/exp/api/v1beta1/conversion.go @@ -18,12 +18,11 @@ package v1beta1 import ( apiconversion "k8s.io/apimachinery/pkg/conversion" - utilconversion "sigs.k8s.io/cluster-api/util/conversion" - "sigs.k8s.io/controller-runtime/pkg/conversion" - infrav1beta1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta1" infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" infrav1exp "sigs.k8s.io/cluster-api-provider-aws/v2/exp/api/v1beta2" + utilconversion "sigs.k8s.io/cluster-api/util/conversion" + "sigs.k8s.io/controller-runtime/pkg/conversion" ) // ConvertTo converts the v1beta1 AWSMachinePool receiver to a v1beta2 AWSMachinePool. diff --git a/exp/api/v1beta1/conversion_test.go b/exp/api/v1beta1/conversion_test.go index 5992c664be..3cedcf3342 100644 --- a/exp/api/v1beta1/conversion_test.go +++ b/exp/api/v1beta1/conversion_test.go @@ -21,7 +21,6 @@ import ( . "github.com/onsi/gomega" "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/cluster-api-provider-aws/v2/exp/api/v1beta2" utilconversion "sigs.k8s.io/cluster-api/util/conversion" ) diff --git a/exp/api/v1beta2/awsmachinepool_webhook.go b/exp/api/v1beta2/awsmachinepool_webhook.go index 41af26b9e9..ab434ffb4b 100644 --- a/exp/api/v1beta2/awsmachinepool_webhook.go +++ b/exp/api/v1beta2/awsmachinepool_webhook.go @@ -141,7 +141,7 @@ func (r *AWSMachinePool) ValidateCreate() (admission.Warnings, error) { } // ValidateUpdate will do any extra validation when updating a AWSMachinePool. -func (r *AWSMachinePool) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { +func (r *AWSMachinePool) ValidateUpdate(_ runtime.Object) (admission.Warnings, error) { var allErrs field.ErrorList allErrs = append(allErrs, r.validateDefaultCoolDown()...) diff --git a/exp/api/v1beta2/conditions_consts.go b/exp/api/v1beta2/conditions_consts.go index 45e8bf3923..2d052fae53 100644 --- a/exp/api/v1beta2/conditions_consts.go +++ b/exp/api/v1beta2/conditions_consts.go @@ -108,7 +108,11 @@ const ( RosaMachinePoolReadyCondition clusterv1.ConditionType = "RosaMchinePoolReady" // RosaMachinePoolUpgradingCondition condition reports whether ROSAMachinePool is upgrading or not. RosaMachinePoolUpgradingCondition clusterv1.ConditionType = "RosaMchinePoolUpgrading" + // WaitingForRosaControlPlaneReason used when the machine pool is waiting for // ROSA control plane infrastructure to be ready before proceeding. WaitingForRosaControlPlaneReason = "WaitingForRosaControlPlane" + + // RosaMachinePoolReconciliationFailedReason used to report failures while reconciling ROSAMachinePool. + RosaMachinePoolReconciliationFailedReason = "ReconciliationFailed" ) diff --git a/exp/api/v1beta2/groupversion_info.go b/exp/api/v1beta2/groupversion_info.go index a54b837a42..c1a5f0bed2 100644 --- a/exp/api/v1beta2/groupversion_info.go +++ b/exp/api/v1beta2/groupversion_info.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// package v1beta2 contains API Schema definitions for experimental v1beta2 API group +// Package v1beta2 contains API Schema definitions for experimental v1beta2 API group // +kubebuilder:object:generate=true // +groupName=infrastructure.cluster.x-k8s.io package v1beta2 diff --git a/exp/api/v1beta2/rosacluster_types.go b/exp/api/v1beta2/rosacluster_types.go index ed08317c50..1b3ffa5d77 100644 --- a/exp/api/v1beta2/rosacluster_types.go +++ b/exp/api/v1beta2/rosacluster_types.go @@ -22,13 +22,14 @@ import ( clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" ) +// ROSAClusterSpec defines the desired state of ROSACluster. type ROSAClusterSpec struct { // ControlPlaneEndpoint represents the endpoint used to communicate with the control plane. // +optional ControlPlaneEndpoint clusterv1.APIEndpoint `json:"controlPlaneEndpoint"` } -// ROSAClusterStatus defines the observed state of ROSACluster +// ROSAClusterStatus defines the observed state of ROSACluster. type ROSAClusterStatus struct { // Ready is when the ROSAControlPlane has a API server URL. // +optional @@ -47,6 +48,7 @@ type ROSAClusterStatus struct { // +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.ready",description="Control plane infrastructure is ready for worker nodes" // +kubebuilder:printcolumn:name="Endpoint",type="string",JSONPath=".spec.controlPlaneEndpoint.host",description="API Endpoint",priority=1 +// ROSACluster is the Schema for the ROSAClusters API. type ROSACluster struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/exp/api/v1beta2/rosamachinepool_types.go b/exp/api/v1beta2/rosamachinepool_types.go index 9ac13ac7ce..8db3d5a380 100644 --- a/exp/api/v1beta2/rosamachinepool_types.go +++ b/exp/api/v1beta2/rosamachinepool_types.go @@ -17,8 +17,10 @@ limitations under the License. package v1beta2 import ( + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" ) @@ -33,11 +35,10 @@ type RosaMachinePoolSpec struct { // +kubebuilder:validation:Pattern:=`^[a-z]([-a-z0-9]*[a-z0-9])?$` NodePoolName string `json:"nodePoolName"` - // Version specifies the penshift version of the nodes associated with this machinepool. + // Version specifies the OpenShift version of the nodes associated with this machinepool. // ROSAControlPlane version is used if not set. // // +optional - // +kubebuilder:validation:XValidation:rule=`self.matches('^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$')`, message="version must be a valid semantic version" Version string `json:"version,omitempty"` // AvailabilityZone is an optinal field specifying the availability zone where instances of this machine pool should run @@ -45,6 +46,8 @@ type RosaMachinePoolSpec struct { // +optional AvailabilityZone string `json:"availabilityZone,omitempty"` + // +kubebuilder:validation:XValidation:rule="self == oldSelf", message="subnet is immutable" + // +immutable // +optional Subnet string `json:"subnet,omitempty"` @@ -52,28 +55,75 @@ type RosaMachinePoolSpec struct { // +optional Labels map[string]string `json:"labels,omitempty"` + // Taints specifies the taints to apply to the nodes of the machine pool + // +optional + Taints []RosaTaint `json:"taints,omitempty"` + + // AdditionalTags are user-defined tags to be added on the underlying EC2 instances associated with this machine pool. + // +immutable + // +optional + AdditionalTags infrav1.Tags `json:"additionalTags,omitempty"` + // AutoRepair specifies whether health checks should be enabled for machines // in the NodePool. The default is false. - // +optional // +kubebuilder:default=false + // +optional AutoRepair bool `json:"autoRepair,omitempty"` // InstanceType specifies the AWS instance type - InstanceType string `json:"instanceType,omitempty"` + // + // +kubebuilder:validation:Required + InstanceType string `json:"instanceType"` // Autoscaling specifies auto scaling behaviour for this MachinePool. // required if Replicas is not configured // +optional Autoscaling *RosaMachinePoolAutoScaling `json:"autoscaling,omitempty"` - // TODO(alberto): Enable and propagate this API input. - // Taints []*Taint `json:"taints,omitempty"` - // TuningConfigs []string `json:"tuningConfigs,omitempty"` - // Version *Version `json:"version,omitempty"` + // TuningConfigs specifies the names of the tuning configs to be applied to this MachinePool. + // Tuning configs must already exist. + // +optional + TuningConfigs []string `json:"tuningConfigs,omitempty"` + + // AdditionalSecurityGroups is an optional set of security groups to associate + // with all node instances of the machine pool. + // + // +immutable + // +optional + AdditionalSecurityGroups []string `json:"additionalSecurityGroups,omitempty"` // ProviderIDList contain a ProviderID for each machine instance that's currently managed by this machine pool. // +optional ProviderIDList []string `json:"providerIDList,omitempty"` + + // NodeDrainGracePeriod is grace period for how long Pod Disruption Budget-protected workloads will be + // respected during upgrades. After this grace period, any workloads protected by Pod Disruption + // Budgets that have not been successfully drained from a node will be forcibly evicted. + // + // Valid values are from 0 to 1 week(10080m|168h) . + // 0 or empty value means that the MachinePool can be drained without any time limitation. + // + // +optional + NodeDrainGracePeriod *metav1.Duration `json:"nodeDrainGracePeriod,omitempty"` +} + +// RosaTaint represents a taint to be applied to a node. +type RosaTaint struct { + // The taint key to be applied to a node. + // + // +kubebuilder:validation:Required + Key string `json:"key"` + // The taint value corresponding to the taint key. + // + // +kubebuilder:validation:Pattern:=`^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$` + // +optional + Value string `json:"value,omitempty"` + // The effect of the taint on pods that do not tolerate the taint. + // Valid effects are NoSchedule, PreferNoSchedule and NoExecute. + // + // +kubebuilder:validation:Required + // +kubebuilder:validation:Enum=NoSchedule;PreferNoSchedule;NoExecute + Effect corev1.TaintEffect `json:"effect"` } // RosaMachinePoolAutoScaling specifies scaling options. diff --git a/exp/api/v1beta2/rosamachinepool_webhook.go b/exp/api/v1beta2/rosamachinepool_webhook.go new file mode 100644 index 0000000000..acae78576e --- /dev/null +++ b/exp/api/v1beta2/rosamachinepool_webhook.go @@ -0,0 +1,130 @@ +package v1beta2 + +import ( + "github.com/blang/semver" + "github.com/google/go-cmp/cmp" + "github.com/pkg/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" + runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +// SetupWebhookWithManager will setup the webhooks for the ROSAMachinePool. +func (r *ROSAMachinePool) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} + +// +kubebuilder:webhook:verbs=create;update,path=/validate-infrastructure-cluster-x-k8s-io-v1beta2-rosamachinepool,mutating=false,failurePolicy=fail,matchPolicy=Equivalent,groups=infrastructure.cluster.x-k8s.io,resources=rosamachinepools,versions=v1beta2,name=validation.rosamachinepool.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1 +// +kubebuilder:webhook:verbs=create;update,path=/mutate-infrastructure-cluster-x-k8s-io-v1beta2-rosamachinepool,mutating=true,failurePolicy=fail,matchPolicy=Equivalent,groups=infrastructure.cluster.x-k8s.io,resources=rosamachinepools,versions=v1beta2,name=default.rosamachinepool.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1 + +var _ webhook.Defaulter = &ROSAMachinePool{} +var _ webhook.Validator = &ROSAMachinePool{} + +// ValidateCreate implements admission.Validator. +func (r *ROSAMachinePool) ValidateCreate() (warnings admission.Warnings, err error) { + var allErrs field.ErrorList + + if err := r.validateVersion(); err != nil { + allErrs = append(allErrs, err) + } + + if err := r.validateNodeDrainGracePeriod(); err != nil { + allErrs = append(allErrs, err) + } + + allErrs = append(allErrs, r.Spec.AdditionalTags.Validate()...) + + if len(allErrs) == 0 { + return nil, nil + } + + return nil, apierrors.NewInvalid( + r.GroupVersionKind().GroupKind(), + r.Name, + allErrs, + ) +} + +// ValidateUpdate implements admission.Validator. +func (r *ROSAMachinePool) ValidateUpdate(old runtime.Object) (warnings admission.Warnings, err error) { + oldPool, ok := old.(*ROSAMachinePool) + if !ok { + return nil, apierrors.NewInvalid(GroupVersion.WithKind("ROSAMachinePool").GroupKind(), r.Name, field.ErrorList{ + field.InternalError(nil, errors.New("failed to convert old ROSAMachinePool to object")), + }) + } + + var allErrs field.ErrorList + if err := r.validateVersion(); err != nil { + allErrs = append(allErrs, err) + } + + if err := r.validateNodeDrainGracePeriod(); err != nil { + allErrs = append(allErrs, err) + } + + allErrs = append(allErrs, validateImmutable(oldPool.Spec.AdditionalSecurityGroups, r.Spec.AdditionalSecurityGroups, "additionalSecurityGroups")...) + allErrs = append(allErrs, validateImmutable(oldPool.Spec.AdditionalTags, r.Spec.AdditionalTags, "additionalTags")...) + + if len(allErrs) == 0 { + return nil, nil + } + + return nil, apierrors.NewInvalid( + r.GroupVersionKind().GroupKind(), + r.Name, + allErrs, + ) +} + +// ValidateDelete implements admission.Validator. +func (r *ROSAMachinePool) ValidateDelete() (warnings admission.Warnings, err error) { + return nil, nil +} + +func (r *ROSAMachinePool) validateVersion() *field.Error { + if r.Spec.Version == "" { + return nil + } + _, err := semver.Parse(r.Spec.Version) + if err != nil { + return field.Invalid(field.NewPath("spec.version"), r.Spec.Version, "must be a valid semantic version") + } + + return nil +} + +func (r *ROSAMachinePool) validateNodeDrainGracePeriod() *field.Error { + if r.Spec.NodeDrainGracePeriod == nil { + return nil + } + + if r.Spec.NodeDrainGracePeriod.Minutes() > 10080 { + return field.Invalid(field.NewPath("spec.nodeDrainGracePeriod"), r.Spec.NodeDrainGracePeriod, + "max supported duration is 1 week (10080m|168h)") + } + + return nil +} + +func validateImmutable(old, updated interface{}, name string) field.ErrorList { + var allErrs field.ErrorList + + if !cmp.Equal(old, updated) { + allErrs = append( + allErrs, + field.Invalid(field.NewPath("spec", name), updated, "field is immutable"), + ) + } + + return allErrs +} + +// Default implements admission.Defaulter. +func (r *ROSAMachinePool) Default() { +} diff --git a/exp/api/v1beta2/zz_generated.deepcopy.go b/exp/api/v1beta2/zz_generated.deepcopy.go index 867324e441..a916ebc059 100644 --- a/exp/api/v1beta2/zz_generated.deepcopy.go +++ b/exp/api/v1beta2/zz_generated.deepcopy.go @@ -21,6 +21,7 @@ limitations under the License. package v1beta2 import ( + "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" apiv1beta2 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" "sigs.k8s.io/cluster-api/api/v1beta1" @@ -1090,16 +1091,43 @@ func (in *RosaMachinePoolSpec) DeepCopyInto(out *RosaMachinePoolSpec) { (*out)[key] = val } } + if in.Taints != nil { + in, out := &in.Taints, &out.Taints + *out = make([]RosaTaint, len(*in)) + copy(*out, *in) + } + if in.AdditionalTags != nil { + in, out := &in.AdditionalTags, &out.AdditionalTags + *out = make(apiv1beta2.Tags, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } if in.Autoscaling != nil { in, out := &in.Autoscaling, &out.Autoscaling *out = new(RosaMachinePoolAutoScaling) **out = **in } + if in.TuningConfigs != nil { + in, out := &in.TuningConfigs, &out.TuningConfigs + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.AdditionalSecurityGroups != nil { + in, out := &in.AdditionalSecurityGroups, &out.AdditionalSecurityGroups + *out = make([]string, len(*in)) + copy(*out, *in) + } if in.ProviderIDList != nil { in, out := &in.ProviderIDList, &out.ProviderIDList *out = make([]string, len(*in)) copy(*out, *in) } + if in.NodeDrainGracePeriod != nil { + in, out := &in.NodeDrainGracePeriod, &out.NodeDrainGracePeriod + *out = new(v1.Duration) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RosaMachinePoolSpec. @@ -1139,6 +1167,21 @@ func (in *RosaMachinePoolStatus) DeepCopy() *RosaMachinePoolStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RosaTaint) DeepCopyInto(out *RosaTaint) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RosaTaint. +func (in *RosaTaint) DeepCopy() *RosaTaint { + if in == nil { + return nil + } + out := new(RosaTaint) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SuspendProcessesTypes) DeepCopyInto(out *SuspendProcessesTypes) { *out = *in diff --git a/exp/controlleridentitycreator/awscontrolleridentity_controller.go b/exp/controlleridentitycreator/awscontrolleridentity_controller.go index 0060d712de..bc3a557529 100644 --- a/exp/controlleridentitycreator/awscontrolleridentity_controller.go +++ b/exp/controlleridentitycreator/awscontrolleridentity_controller.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package controlleridentitycreator provides a way to reconcile AWSClusterControllerIdentity instance. package controlleridentitycreator import ( diff --git a/exp/controllers/awsmachinepool_controller.go b/exp/controllers/awsmachinepool_controller.go index 1a30c90314..8114604c7a 100644 --- a/exp/controllers/awsmachinepool_controller.go +++ b/exp/controllers/awsmachinepool_controller.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package controllers provides experimental API controllers. package controllers import ( diff --git a/exp/controllers/awsmachinepool_controller_test.go b/exp/controllers/awsmachinepool_controller_test.go index 447ecd3fab..4448fb94f3 100644 --- a/exp/controllers/awsmachinepool_controller_test.go +++ b/exp/controllers/awsmachinepool_controller_test.go @@ -773,7 +773,7 @@ func TestAWSMachinePoolReconciler(t *testing.T) { }) } -//TODO: This was taken from awsmachine_controller_test, i think it should be moved to elsewhere in both locations like test/helpers +//TODO: This was taken from awsmachine_controller_test, i think it should be moved to elsewhere in both locations like test/helpers. type conditionAssertion struct { conditionType clusterv1.ConditionType diff --git a/exp/controllers/rosamachinepool_controller.go b/exp/controllers/rosamachinepool_controller.go index 6e837b257a..f809f83b3f 100644 --- a/exp/controllers/rosamachinepool_controller.go +++ b/exp/controllers/rosamachinepool_controller.go @@ -5,15 +5,20 @@ import ( "fmt" "time" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" "github.com/blang/semver" + "github.com/google/go-cmp/cmp" cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" "github.com/openshift/rosa/pkg/ocm" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/tools/record" "k8s.io/klog/v2" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" @@ -35,7 +40,7 @@ import ( "sigs.k8s.io/cluster-api/util/predicates" ) -// ROSAMachinePoolReconciler reconciles a RosaMachinePool object. +// ROSAMachinePoolReconciler reconciles a ROSAMachinePool object. type ROSAMachinePoolReconciler struct { client.Client Recorder record.EventRecorder @@ -49,7 +54,7 @@ func (r *ROSAMachinePoolReconciler) SetupWithManager(ctx context.Context, mgr ct gvk, err := apiutil.GVKForObject(new(expinfrav1.ROSAMachinePool), mgr.GetScheme()) if err != nil { - return errors.Wrapf(err, "failed to find GVK for RosaMachinePool") + return errors.Wrapf(err, "failed to find GVK for ROSAMachinePool") } rosaControlPlaneToRosaMachinePoolMap := rosaControlPlaneToRosaMachinePoolMapFunc(r.Client, gvk, log) return ctrl.NewControllerManagedBy(mgr). @@ -72,8 +77,9 @@ func (r *ROSAMachinePoolReconciler) SetupWithManager(ctx context.Context, mgr ct // +kubebuilder:rbac:groups=controlplane.cluster.x-k8s.io,resources=rosacontrolplanes;rosacontrolplanes/status,verbs=get;list;watch // +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=rosamachinepools,verbs=get;list;watch;update;patch;delete // +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=rosamachinepools/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=rosamachinepools/finalizers,verbs=update -// Reconcile reconciles RosaMachinePool. +// Reconcile reconciles ROSAMachinePool. func (r *ROSAMachinePoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) { log := logger.FromContext(ctx) @@ -128,6 +134,7 @@ func (r *ROSAMachinePoolReconciler) Reconcile(ctx context.Context, req ctrl.Requ MachinePool: machinePool, RosaMachinePool: rosaMachinePool, Logger: log, + Endpoints: r.Endpoints, }) if err != nil { return ctrl.Result{}, errors.Wrap(err, "failed to create scope") @@ -138,6 +145,7 @@ func (r *ROSAMachinePoolReconciler) Reconcile(ctx context.Context, req ctrl.Requ Cluster: cluster, ControlPlane: controlPlane, ControllerName: "rosaControlPlane", + Endpoints: r.Endpoints, }) if err != nil { return ctrl.Result{}, errors.Wrap(err, "failed to create control plane scope") @@ -168,7 +176,7 @@ func (r *ROSAMachinePoolReconciler) reconcileNormal(ctx context.Context, machinePoolScope *scope.RosaMachinePoolScope, rosaControlPlaneScope *scope.ROSAControlPlaneScope, ) (ctrl.Result, error) { - machinePoolScope.Info("Reconciling RosaMachinePool") + machinePoolScope.Info("Reconciling ROSAMachinePool") if controllerutil.AddFinalizer(machinePoolScope.RosaMachinePool, expinfrav1.RosaMachinePoolFinalizer) { if err := machinePoolScope.PatchObject(); err != nil { @@ -190,26 +198,56 @@ func (r *ROSAMachinePoolReconciler) reconcileNormal(ctx context.Context, machinePoolScope.RosaMachinePool.Status.FailureMessage = failureMessage // dont' requeue because input is invalid and manual intervention is needed. return ctrl.Result{}, nil - } else { - machinePoolScope.RosaMachinePool.Status.FailureMessage = nil } + machinePoolScope.RosaMachinePool.Status.FailureMessage = nil rosaMachinePool := machinePoolScope.RosaMachinePool machinePool := machinePoolScope.MachinePool - controlPlane := machinePoolScope.ControlPlane - createdNodePool, found, err := ocmClient.GetNodePool(*controlPlane.Status.ID, rosaMachinePool.Spec.NodePoolName) + if rosaMachinePool.Spec.Autoscaling != nil && !annotations.ReplicasManagedByExternalAutoscaler(machinePool) { + // make sure cluster.x-k8s.io/replicas-managed-by annotation is set on CAPI MachinePool when autoscaling is enabled. + annotations.AddAnnotations(machinePool, map[string]string{ + clusterv1.ReplicasManagedByAnnotation: "rosa", + }) + if err := machinePoolScope.PatchCAPIMachinePoolObject(ctx); err != nil { + return ctrl.Result{}, err + } + } + + nodePool, found, err := ocmClient.GetNodePool(machinePoolScope.ControlPlane.Status.ID, rosaMachinePool.Spec.NodePoolName) if err != nil { return ctrl.Result{}, err } + if found { - // TODO (alberto): discover and store providerIDs from aws so the CAPI controller can match then to Nodes and report readiness. - rosaMachinePool.Status.Replicas = int32(createdNodePool.Status().CurrentReplicas()) - if createdNodePool.Replicas() == createdNodePool.Status().CurrentReplicas() && createdNodePool.Status().Message() == "" { + nodePool, err := r.updateNodePool(machinePoolScope, ocmClient, nodePool) + if err != nil { + return ctrl.Result{}, fmt.Errorf("failed to ensure rosaMachinePool: %w", err) + } + + currentReplicas := int32(nodePool.Status().CurrentReplicas()) + if annotations.ReplicasManagedByExternalAutoscaler(machinePool) { + // Set MachinePool replicas to rosa autoscaling replicas + if *machinePool.Spec.Replicas != currentReplicas { + machinePoolScope.Info("Setting MachinePool replicas to rosa autoscaling replicas", + "local", *machinePool.Spec.Replicas, + "external", currentReplicas) + machinePool.Spec.Replicas = ¤tReplicas + if err := machinePoolScope.PatchCAPIMachinePoolObject(ctx); err != nil { + return ctrl.Result{}, err + } + } + } + if err := r.reconcileProviderIDList(ctx, machinePoolScope, nodePool); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to reconcile ProviderIDList: %w", err) + } + + rosaMachinePool.Status.Replicas = currentReplicas + if rosa.IsNodePoolReady(nodePool) { conditions.MarkTrue(rosaMachinePool, expinfrav1.RosaMachinePoolReadyCondition) rosaMachinePool.Status.Ready = true - if err := r.reconcileMachinePoolVersion(machinePoolScope, ocmClient, createdNodePool); err != nil { + if err := r.reconcileMachinePoolVersion(machinePoolScope, ocmClient, nodePool); err != nil { return ctrl.Result{}, err } @@ -218,54 +256,32 @@ func (r *ROSAMachinePoolReconciler) reconcileNormal(ctx context.Context, conditions.MarkFalse(rosaMachinePool, expinfrav1.RosaMachinePoolReadyCondition, - createdNodePool.Status().Message(), + nodePool.Status().Message(), clusterv1.ConditionSeverityInfo, "") - machinePoolScope.Info("waiting for NodePool to become ready", "state", createdNodePool.Status().Message()) + machinePoolScope.Info("waiting for NodePool to become ready", "state", nodePool.Status().Message()) // Requeue so that status.ready is set to true when the nodepool is fully created. return ctrl.Result{RequeueAfter: time.Second * 60}, nil } - npBuilder := cmv1.NewNodePool() - npBuilder.ID(rosaMachinePool.Spec.NodePoolName). - Labels(rosaMachinePool.Spec.Labels). - AutoRepair(rosaMachinePool.Spec.AutoRepair) - - if rosaMachinePool.Spec.Autoscaling != nil { - npBuilder = npBuilder.Autoscaling( - cmv1.NewNodePoolAutoscaling(). - MinReplica(rosaMachinePool.Spec.Autoscaling.MinReplicas). - MaxReplica(rosaMachinePool.Spec.Autoscaling.MaxReplicas)) - } else { - replicas := 1 - if machinePool.Spec.Replicas != nil { - replicas = int(*machinePool.Spec.Replicas) - } - npBuilder = npBuilder.Replicas(replicas) - } - - if rosaMachinePool.Spec.Subnet != "" { - npBuilder.Subnet(rosaMachinePool.Spec.Subnet) - } - - npBuilder.AWSNodePool(cmv1.NewAWSNodePool().InstanceType(rosaMachinePool.Spec.InstanceType)) - if rosaMachinePool.Spec.Version != "" { - npBuilder.Version(cmv1.NewVersion().ID(ocm.CreateVersionID(rosaMachinePool.Spec.Version, ocm.DefaultChannelGroup))) - } - + npBuilder := nodePoolBuilder(rosaMachinePool.Spec, machinePool.Spec) nodePoolSpec, err := npBuilder.Build() if err != nil { return ctrl.Result{}, fmt.Errorf("failed to build rosa nodepool: %w", err) } - createdNodePool, err = ocmClient.CreateNodePool(*controlPlane.Status.ID, nodePoolSpec) + nodePool, err = ocmClient.CreateNodePool(machinePoolScope.ControlPlane.Status.ID, nodePoolSpec) if err != nil { + conditions.MarkFalse(rosaMachinePool, + expinfrav1.RosaMachinePoolReadyCondition, + expinfrav1.RosaMachinePoolReconciliationFailedReason, + clusterv1.ConditionSeverityError, + "failed to create ROSAMachinePool: %s", err.Error()) return ctrl.Result{}, fmt.Errorf("failed to create nodepool: %w", err) } - machinePoolScope.RosaMachinePool.Status.ID = createdNodePool.ID() - + machinePoolScope.RosaMachinePool.Status.ID = nodePool.ID() return ctrl.Result{}, nil } @@ -281,12 +297,12 @@ func (r *ROSAMachinePoolReconciler) reconcileDelete( return fmt.Errorf("failed to create OCM client: %w", err) } - nodePool, found, err := ocmClient.GetNodePool(*machinePoolScope.ControlPlane.Status.ID, machinePoolScope.NodePoolName()) + nodePool, found, err := ocmClient.GetNodePool(machinePoolScope.ControlPlane.Status.ID, machinePoolScope.NodePoolName()) if err != nil { return err } if found { - if err := ocmClient.DeleteNodePool(*machinePoolScope.ControlPlane.Status.ID, nodePool.ID()); err != nil { + if err := ocmClient.DeleteNodePool(machinePoolScope.ControlPlane.Status.ID, nodePool.ID()); err != nil { return err } } @@ -298,36 +314,19 @@ func (r *ROSAMachinePoolReconciler) reconcileDelete( func (r *ROSAMachinePoolReconciler) reconcileMachinePoolVersion(machinePoolScope *scope.RosaMachinePoolScope, ocmClient *ocm.Client, nodePool *cmv1.NodePool) error { version := machinePoolScope.RosaMachinePool.Spec.Version - if version == "" { - version = machinePoolScope.ControlPlane.Spec.Version - } - - if version == rosa.RawVersionID(nodePool.Version()) { + if version == "" || version == rosa.RawVersionID(nodePool.Version()) { conditions.MarkFalse(machinePoolScope.RosaMachinePool, expinfrav1.RosaMachinePoolUpgradingCondition, "upgraded", clusterv1.ConditionSeverityInfo, "") return nil } - clusterID := *machinePoolScope.ControlPlane.Status.ID + clusterID := machinePoolScope.ControlPlane.Status.ID _, scheduledUpgrade, err := ocmClient.GetHypershiftNodePoolUpgrade(clusterID, machinePoolScope.ControlPlane.Spec.RosaClusterName, nodePool.ID()) if err != nil { return fmt.Errorf("failed to get existing scheduled upgrades: %w", err) } if scheduledUpgrade == nil { - policy, err := ocmClient.BuildNodeUpgradePolicy(version, nodePool.ID(), ocm.UpgradeScheduling{ - AutomaticUpgrades: false, - // The OCM API places guardrails around the minimum and maximum delay that a user can request, - // for the next run of the upgrade, which is [5min,6mo]. Set our next run request to something - // slightly longer than 5min to make sure we account for the latency between when we send this - // request and when the server processes it. - // https://gitlab.cee.redhat.com/service/uhc-clusters-service/-/blob/master/cmd/clusters-service/servecmd/apiserver/upgrade_policy_handlers.go - NextRun: time.Now().Add(6 * time.Minute), - }) - if err != nil { - return fmt.Errorf("failed to create nodePool upgrade schedule to version %s: %w", version, err) - } - - scheduledUpgrade, err = ocmClient.ScheduleNodePoolUpgrade(clusterID, nodePool.ID(), policy) + scheduledUpgrade, err = rosa.ScheduleNodePoolUpgrade(ocmClient, clusterID, nodePool, version, time.Now()) if err != nil { return fmt.Errorf("failed to schedule nodePool upgrade to version %s: %w", version, err) } @@ -349,6 +348,42 @@ func (r *ROSAMachinePoolReconciler) reconcileMachinePoolVersion(machinePoolScope return nil } +func (r *ROSAMachinePoolReconciler) updateNodePool(machinePoolScope *scope.RosaMachinePoolScope, ocmClient *ocm.Client, nodePool *cmv1.NodePool) (*cmv1.NodePool, error) { + desiredSpec := machinePoolScope.RosaMachinePool.Spec.DeepCopy() + + currentSpec := nodePoolToRosaMachinePoolSpec(nodePool) + currentSpec.ProviderIDList = desiredSpec.ProviderIDList // providerIDList is set by the controller and shouldn't be compared here. + currentSpec.Version = desiredSpec.Version // Version changes are reconciled separately and shouldn't be compared here. + + if cmp.Equal(desiredSpec, currentSpec) { + // no changes detected. + return nodePool, nil + } + + // zero-out fields that shouldn't be part of the update call. + desiredSpec.Version = "" + desiredSpec.AdditionalSecurityGroups = nil + desiredSpec.AdditionalTags = nil + + npBuilder := nodePoolBuilder(*desiredSpec, machinePoolScope.MachinePool.Spec) + nodePoolSpec, err := npBuilder.Build() + if err != nil { + return nil, fmt.Errorf("failed to build nodePool spec: %w", err) + } + + updatedNodePool, err := ocmClient.UpdateNodePool(machinePoolScope.ControlPlane.Status.ID, nodePoolSpec) + if err != nil { + conditions.MarkFalse(machinePoolScope.RosaMachinePool, + expinfrav1.RosaMachinePoolReadyCondition, + expinfrav1.RosaMachinePoolReconciliationFailedReason, + clusterv1.ConditionSeverityError, + "failed to update ROSAMachinePool: %s", err.Error()) + return nil, fmt.Errorf("failed to update nodePool: %w", err) + } + + return updatedNodePool, nil +} + func validateMachinePoolSpec(machinePoolScope *scope.RosaMachinePoolScope) (*string, error) { if machinePoolScope.RosaMachinePool.Spec.Version == "" { return nil, nil @@ -372,6 +407,151 @@ func validateMachinePoolSpec(machinePoolScope *scope.RosaMachinePoolScope) (*str return nil, nil } +func nodePoolBuilder(rosaMachinePoolSpec expinfrav1.RosaMachinePoolSpec, machinePoolSpec expclusterv1.MachinePoolSpec) *cmv1.NodePoolBuilder { + npBuilder := cmv1.NewNodePool().ID(rosaMachinePoolSpec.NodePoolName). + Labels(rosaMachinePoolSpec.Labels). + AutoRepair(rosaMachinePoolSpec.AutoRepair) + + if rosaMachinePoolSpec.TuningConfigs != nil { + npBuilder = npBuilder.TuningConfigs(rosaMachinePoolSpec.TuningConfigs...) + } + + if len(rosaMachinePoolSpec.Taints) > 0 { + taintBuilders := []*cmv1.TaintBuilder{} + for _, taint := range rosaMachinePoolSpec.Taints { + newTaintBuilder := cmv1.NewTaint().Key(taint.Key).Value(taint.Value).Effect(string(taint.Effect)) + taintBuilders = append(taintBuilders, newTaintBuilder) + } + npBuilder = npBuilder.Taints(taintBuilders...) + } + + if rosaMachinePoolSpec.Autoscaling != nil { + npBuilder = npBuilder.Autoscaling( + cmv1.NewNodePoolAutoscaling(). + MinReplica(rosaMachinePoolSpec.Autoscaling.MinReplicas). + MaxReplica(rosaMachinePoolSpec.Autoscaling.MaxReplicas)) + } else { + replicas := 1 + if machinePoolSpec.Replicas != nil { + replicas = int(*machinePoolSpec.Replicas) + } + npBuilder = npBuilder.Replicas(replicas) + } + + if rosaMachinePoolSpec.Subnet != "" { + npBuilder.Subnet(rosaMachinePoolSpec.Subnet) + } + + awsNodePool := cmv1.NewAWSNodePool().InstanceType(rosaMachinePoolSpec.InstanceType) + if rosaMachinePoolSpec.AdditionalSecurityGroups != nil { + awsNodePool = awsNodePool.AdditionalSecurityGroupIds(rosaMachinePoolSpec.AdditionalSecurityGroups...) + } + if rosaMachinePoolSpec.AdditionalTags != nil { + awsNodePool = awsNodePool.Tags(rosaMachinePoolSpec.AdditionalTags) + } + npBuilder.AWSNodePool(awsNodePool) + + if rosaMachinePoolSpec.Version != "" { + npBuilder.Version(cmv1.NewVersion().ID(ocm.CreateVersionID(rosaMachinePoolSpec.Version, ocm.DefaultChannelGroup))) + } + + if rosaMachinePoolSpec.NodeDrainGracePeriod != nil { + valueBuilder := cmv1.NewValue().Value(rosaMachinePoolSpec.NodeDrainGracePeriod.Minutes()).Unit("minutes") + npBuilder.NodeDrainGracePeriod(valueBuilder) + } + + return npBuilder +} + +func nodePoolToRosaMachinePoolSpec(nodePool *cmv1.NodePool) expinfrav1.RosaMachinePoolSpec { + spec := expinfrav1.RosaMachinePoolSpec{ + NodePoolName: nodePool.ID(), + Version: rosa.RawVersionID(nodePool.Version()), + AvailabilityZone: nodePool.AvailabilityZone(), + Subnet: nodePool.Subnet(), + Labels: nodePool.Labels(), + AdditionalTags: nodePool.AWSNodePool().Tags(), + AutoRepair: nodePool.AutoRepair(), + InstanceType: nodePool.AWSNodePool().InstanceType(), + TuningConfigs: nodePool.TuningConfigs(), + AdditionalSecurityGroups: nodePool.AWSNodePool().AdditionalSecurityGroupIds(), + } + + if nodePool.Autoscaling() != nil { + spec.Autoscaling = &expinfrav1.RosaMachinePoolAutoScaling{ + MinReplicas: nodePool.Autoscaling().MinReplica(), + MaxReplicas: nodePool.Autoscaling().MaxReplica(), + } + } + if nodePool.Taints() != nil { + rosaTaints := make([]expinfrav1.RosaTaint, len(nodePool.Taints())) + for _, taint := range nodePool.Taints() { + rosaTaints = append(rosaTaints, expinfrav1.RosaTaint{ + Key: taint.Key(), + Value: taint.Value(), + Effect: corev1.TaintEffect(taint.Effect()), + }) + } + spec.Taints = rosaTaints + } + if nodePool.NodeDrainGracePeriod() != nil { + spec.NodeDrainGracePeriod = &metav1.Duration{ + Duration: time.Minute * time.Duration(nodePool.NodeDrainGracePeriod().Value()), + } + } + + return spec +} + +func (r *ROSAMachinePoolReconciler) reconcileProviderIDList(ctx context.Context, machinePoolScope *scope.RosaMachinePoolScope, nodePool *cmv1.NodePool) error { + tags := nodePool.AWSNodePool().Tags() + if len(tags) == 0 { + // can't identify EC2 instances belonging to this NodePool without tags. + return nil + } + + ec2Svc := scope.NewEC2Client(machinePoolScope, machinePoolScope, &machinePoolScope.Logger, machinePoolScope.InfraCluster()) + response, err := ec2Svc.DescribeInstancesWithContext(ctx, &ec2.DescribeInstancesInput{ + Filters: buildEC2FiltersFromTags(tags), + }) + if err != nil { + return err + } + + var providerIDList []string + for _, reservation := range response.Reservations { + for _, instance := range reservation.Instances { + providerID := scope.GenerateProviderID(*instance.Placement.AvailabilityZone, *instance.InstanceId) + providerIDList = append(providerIDList, providerID) + } + } + + machinePoolScope.RosaMachinePool.Spec.ProviderIDList = providerIDList + return nil +} + +func buildEC2FiltersFromTags(tags map[string]string) []*ec2.Filter { + filters := make([]*ec2.Filter, len(tags)+1) + for key, value := range tags { + filters = append(filters, &ec2.Filter{ + Name: ptr.To(fmt.Sprintf("tag:%s", key)), + Values: aws.StringSlice([]string{ + value, + }), + }) + } + + // only list instances that are running or just started + filters = append(filters, &ec2.Filter{ + Name: ptr.To("instance-state-name"), + Values: aws.StringSlice([]string{ + "running", "pending", + }), + }) + + return filters +} + func rosaControlPlaneToRosaMachinePoolMapFunc(c client.Client, gvk schema.GroupVersionKind, log logger.Wrapper) handler.MapFunc { return func(ctx context.Context, o client.Object) []reconcile.Request { rosaControlPlane, ok := o.(*rosacontrolplanev1.ROSAControlPlane) diff --git a/exp/controllers/rosamachinepool_controller_test.go b/exp/controllers/rosamachinepool_controller_test.go new file mode 100644 index 0000000000..58f1963ed4 --- /dev/null +++ b/exp/controllers/rosamachinepool_controller_test.go @@ -0,0 +1,46 @@ +package controllers + +import ( + "testing" + "time" + + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + + infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" + expinfrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/exp/api/v1beta2" + expclusterv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1" +) + +func TestNodePoolToRosaMachinePoolSpec(t *testing.T) { + g := NewWithT(t) + + rosaMachinePoolSpec := expinfrav1.RosaMachinePoolSpec{ + NodePoolName: "test-nodepool", + Version: "4.14.5", + Subnet: "subnet-id", + AutoRepair: true, + InstanceType: "m5.large", + TuningConfigs: []string{"config1"}, + NodeDrainGracePeriod: &metav1.Duration{ + Duration: time.Minute * 10, + }, + AdditionalTags: infrav1.Tags{ + "tag1": "value1", + }, + } + + machinePoolSpec := expclusterv1.MachinePoolSpec{ + Replicas: ptr.To[int32](2), + } + + nodePoolBuilder := nodePoolBuilder(rosaMachinePoolSpec, machinePoolSpec) + + nodePoolSpec, err := nodePoolBuilder.Build() + g.Expect(err).ToNot(HaveOccurred()) + + expectedSpec := nodePoolToRosaMachinePoolSpec(nodePoolSpec) + + g.Expect(expectedSpec).To(Equal(rosaMachinePoolSpec)) +} diff --git a/exp/doc.go b/exp/doc.go index 1c9b3ddc0b..84020d8a62 100644 --- a/exp/doc.go +++ b/exp/doc.go @@ -14,4 +14,5 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package exp provides experimental code that is not ready for production use. package exp diff --git a/exp/instancestate/awsinstancestate_controller.go b/exp/instancestate/awsinstancestate_controller.go index c04f8687c4..15464eae61 100644 --- a/exp/instancestate/awsinstancestate_controller.go +++ b/exp/instancestate/awsinstancestate_controller.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package instancestate provides a controller that listens +// for EC2 instance state change notifications and updates the corresponding AWSMachine's status. package instancestate import ( diff --git a/feature/feature.go b/feature/feature.go index 8180138e2b..916aebb640 100644 --- a/feature/feature.go +++ b/feature/feature.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package feature provides a feature-gate implementation for capa. package feature import ( diff --git a/go.mod b/go.mod index 9c3e40900d..54d3a6882a 100644 --- a/go.mod +++ b/go.mod @@ -29,8 +29,8 @@ require ( github.com/onsi/ginkgo/v2 v2.13.1 github.com/onsi/gomega v1.30.0 github.com/openshift-online/ocm-common v0.0.0-20240129111424-ff8c6c11d909 - github.com/openshift-online/ocm-sdk-go v0.1.404 - github.com/openshift/rosa v1.2.35-rc1.0.20240229115423-42874686e22d + github.com/openshift-online/ocm-sdk-go v0.1.409 + github.com/openshift/rosa v1.2.35-rc1.0.20240301152457-ad986cecd364 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.18.0 github.com/sergi/go-diff v1.3.1 diff --git a/go.sum b/go.sum index cc1e09bc69..3cbb352ea6 100644 --- a/go.sum +++ b/go.sum @@ -481,10 +481,10 @@ github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1 github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= github.com/openshift-online/ocm-common v0.0.0-20240129111424-ff8c6c11d909 h1:WV67GNazQuGDaLX3kBbz0859NYPOQCsDCY5XUScF85M= github.com/openshift-online/ocm-common v0.0.0-20240129111424-ff8c6c11d909/go.mod h1:7FaAb07S63RF4sFMLSLtQaJLvPdaRnhAT4dBLD8/5kM= -github.com/openshift-online/ocm-sdk-go v0.1.404 h1:rcmqyUYowD1yr1f7NQmoTqa1d/6zW0ZAxqYcpHpV2dk= -github.com/openshift-online/ocm-sdk-go v0.1.404/go.mod h1:tke8vKcE7eHKyRbkJv6qo4ljo919zhx04uyQTcgF5cQ= -github.com/openshift/rosa v1.2.35-rc1.0.20240229115423-42874686e22d h1:5LHaLb2YhmAaSkODnPLT/jbTE5oYmkrbkJ4jkYi7tpE= -github.com/openshift/rosa v1.2.35-rc1.0.20240229115423-42874686e22d/go.mod h1:CR7cWdXZLWpsyskvqTWtAZBG5PaWrzYHy9tbaubbXIA= +github.com/openshift-online/ocm-sdk-go v0.1.409 h1:M7GB1iURdXTFJ6N5cvMxLz0L7wWwdnUvwDOCDFZ7L1s= +github.com/openshift-online/ocm-sdk-go v0.1.409/go.mod h1:8ECJertR5BiblaX5f2siXHXpi+ydYZjoROlVMppmmV4= +github.com/openshift/rosa v1.2.35-rc1.0.20240301152457-ad986cecd364 h1:j1aGLgZhO5xXpYgGAjmraioHTvCK7+gXZXoN9cnpnkw= +github.com/openshift/rosa v1.2.35-rc1.0.20240301152457-ad986cecd364/go.mod h1:kSNsBW8P9KfLCsZYGIrr/aKbLDct8I5gW0e4cCRrr0o= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= diff --git a/hack/boilerplate/test/fail.go b/hack/boilerplate/test/fail.go index fd911e499c..fa814ad151 100644 --- a/hack/boilerplate/test/fail.go +++ b/hack/boilerplate/test/fail.go @@ -16,4 +16,5 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package test provides a test package for boilerplate. package test diff --git a/hack/tools/Makefile b/hack/tools/Makefile index c530239c9b..15df974f60 100644 --- a/hack/tools/Makefile +++ b/hack/tools/Makefile @@ -165,15 +165,6 @@ YQ := $(BIN_DIR)/$(YQ_BIN) $(YQ): CGO_ENABLED=0 go build -tags=tools -o $@ github.com/mikefarah/yq/v4 -GOLANGCI_LINT_BIN := golangci-lint -GOLANGCI_LINT := $(BIN_DIR)/$(GOLANGCI_LINT_BIN) -GOLANGCI_LINT_PKG := github.com/golangci/golangci-lint/cmd/golangci-lint -.PHONY: $(GOLANGCI_LINT_BIN) -$(GOLANGCI_LINT_BIN): $(GOLANGCI_LINT) ## Build a local copy of golangci-lint. - -$(GOLANGCI_LINT): # Build golangci-lint from tools folder. - GOBIN=$(abspath $(BIN_DIR)) go install $(GOLANGCI_LINT_PKG)@$(GOLANGCI_LINT_VERSION) - RELEASE_NOTES_BIN := release-notes RELEASE_NOTES := $(BIN_DIR)/$(RELEASE_NOTES_BIN) RELEASE_NOTES_PKG := k8s.io/release/cmd/release-notes diff --git a/hack/tools/go.mod b/hack/tools/go.mod index 05fc77a216..e2c803f1c4 100644 --- a/hack/tools/go.mod +++ b/hack/tools/go.mod @@ -13,7 +13,7 @@ require ( github.com/golang/mock v1.6.0 github.com/itchyny/gojq v0.12.14 github.com/joelanford/go-apidiff v0.8.2 - github.com/mikefarah/yq/v4 v4.40.5 + github.com/mikefarah/yq/v4 v4.42.1 github.com/spf13/pflag v1.0.5 k8s.io/apimachinery v0.29.1 k8s.io/code-generator v0.28.4 @@ -22,7 +22,7 @@ require ( sigs.k8s.io/cluster-api/hack/tools v0.0.0-20221121093230-b1688621953c sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20211110210527-619e6b92dab9 sigs.k8s.io/controller-tools v0.13.0 - sigs.k8s.io/kind v0.21.0 + sigs.k8s.io/kind v0.22.0 sigs.k8s.io/kustomize/kustomize/v4 v4.5.7 sigs.k8s.io/promo-tools/v4 v4.0.5 sigs.k8s.io/testing_frameworks v0.1.2 @@ -147,7 +147,7 @@ require ( github.com/gobuffalo/flect v1.0.2 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-json v0.10.2 // indirect - github.com/goccy/go-yaml v1.11.2 // indirect + github.com/goccy/go-yaml v1.11.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -215,7 +215,7 @@ require ( github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pborman/uuid v1.2.1 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.17.0 // indirect @@ -277,14 +277,14 @@ require ( go.step.sm/crypto v0.38.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/crypto v0.18.0 // indirect + golang.org/x/crypto v0.19.0 // indirect golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.20.0 // indirect + golang.org/x/net v0.21.0 // indirect golang.org/x/oauth2 v0.16.0 // indirect golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/term v0.16.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/term v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.17.0 // indirect diff --git a/hack/tools/go.sum b/hack/tools/go.sum index 3c9b3aa71f..7e1517b86c 100644 --- a/hack/tools/go.sum +++ b/hack/tools/go.sum @@ -132,8 +132,8 @@ github.com/alecthomas/assert/v2 v2.3.0 h1:mAsH2wmvjsuvyBvAmCtm7zFsBlb8mIHx5ySLVd github.com/alecthomas/assert/v2 v2.3.0/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8= github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c= -github.com/alecthomas/repr v0.3.0 h1:NeYzUPfjjlqHY4KtzgKJiWd6sVq2eNUPTi34PiFGjY8= -github.com/alecthomas/repr v0.3.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.2/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= @@ -453,8 +453,8 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-yaml v1.11.2 h1:joq77SxuyIs9zzxEjgyLBugMQ9NEgTWxXfz2wVqwAaQ= -github.com/goccy/go-yaml v1.11.2/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= +github.com/goccy/go-yaml v1.11.3 h1:B3W9IdWbvrUu2OYQGwvU1nZtvMQJPBKgBUuweJjLj6I= +github.com/goccy/go-yaml v1.11.3/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -710,8 +710,8 @@ github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60 github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/mikefarah/yq/v4 v4.40.5 h1:7gDj+GlXINEIB4wv30XR/UkH400kJHauiwxKwIXqgRc= -github.com/mikefarah/yq/v4 v4.40.5/go.mod h1:y2lpkZypzZrJ2kr098cL0PfzdqEwVCJHPW8bH8HNQI8= +github.com/mikefarah/yq/v4 v4.42.1 h1:wqxJzQnKJoU3vFshezkxfLdg+zvjeqZN0BoSj2FX2QM= +github.com/mikefarah/yq/v4 v4.42.1/go.mod h1:7EyF8IDMc9y+jMK5Cp0kTmCgY1071idUm8m+AoVKh3g= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= @@ -778,8 +778,8 @@ github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtb github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= -github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= +github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= @@ -1037,8 +1037,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1128,8 +1128,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1217,8 +1217,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -1226,8 +1226,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= -golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= -golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1517,8 +1517,8 @@ sigs.k8s.io/controller-tools v0.13.0 h1:NfrvuZ4bxyolhDBt/rCZhDnx3M2hzlhgo5n3Iv2R sigs.k8s.io/controller-tools v0.13.0/go.mod h1:5vw3En2NazbejQGCeWKRrE7q4P+CW8/klfVqP8QZkgA= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/kind v0.21.0 h1:QgkVrW35dMXNLkWlUkq2uFQNQbPLr0Z6RgRH5P/NzZU= -sigs.k8s.io/kind v0.21.0/go.mod h1:aBlbxg08cauDgZ612shr017/rZwqd7AS563FvpWKPVs= +sigs.k8s.io/kind v0.22.0 h1:z/+yr/azoOfzsfooqRsPw1wjJlqT/ukXP0ShkHwNlsI= +sigs.k8s.io/kind v0.22.0/go.mod h1:aBlbxg08cauDgZ612shr017/rZwqd7AS563FvpWKPVs= sigs.k8s.io/kubebuilder/docs/book/utils v0.0.0-20211028165026-57688c578b5d h1:KLiQzLW3RZJR19+j4pw2h5iioyAyqCkDBEAFdnGa3N8= sigs.k8s.io/kubebuilder/docs/book/utils v0.0.0-20211028165026-57688c578b5d/go.mod h1:NRdZafr4zSCseLQggdvIMXa7umxf+Q+PJzrj3wFwiGE= sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= diff --git a/hack/tools/third_party/conversion-gen/generators/conversion.go b/hack/tools/third_party/conversion-gen/generators/conversion.go index f6d6c782c4..a4b127aea4 100644 --- a/hack/tools/third_party/conversion-gen/generators/conversion.go +++ b/hack/tools/third_party/conversion-gen/generators/conversion.go @@ -25,15 +25,13 @@ import ( "sort" "strings" + conversionargs "k8s.io/code-generator/cmd/conversion-gen/args" + genutil "k8s.io/code-generator/pkg/util" "k8s.io/gengo/args" "k8s.io/gengo/generator" "k8s.io/gengo/namer" "k8s.io/gengo/types" - "k8s.io/klog/v2" - - conversionargs "k8s.io/code-generator/cmd/conversion-gen/args" - genutil "k8s.io/code-generator/pkg/util" ) // These are the comment tags that carry parameters for conversion generation. diff --git a/hack/tools/third_party/conversion-gen/main.go b/hack/tools/third_party/conversion-gen/main.go index eae9db7eac..c8d859612d 100644 --- a/hack/tools/third_party/conversion-gen/main.go +++ b/hack/tools/third_party/conversion-gen/main.go @@ -63,7 +63,7 @@ limitations under the License. // fundamentally differently typed fields. // // `conversion-gen` will scan its `--input-dirs`, looking at the -// package defined in each of those directories for comment tags that +// Package defined in each of those directories for comment tags that // define a conversion code generation task. A package requests // conversion code generation by including one or more comment in the // package's `doc.go` file (currently anywhere in that file is @@ -73,7 +73,7 @@ limitations under the License. // // +k8s:conversion-gen= // // This introduces a conversion task, for which the destination -// package is the one containing the file with the tag and the tag +// Package is the one containing the file with the tag and the tag // identifies a package containing internal types. If there is also a // tag of the form // @@ -98,9 +98,8 @@ import ( "flag" "github.com/spf13/pflag" - "k8s.io/klog/v2" - generatorargs "k8s.io/code-generator/cmd/conversion-gen/args" + "k8s.io/klog/v2" "sigs.k8s.io/cluster-api-provider-aws/hack/tools/third_party/conversion-gen/generators" ) diff --git a/iam/api/v1beta1/types.go b/iam/api/v1beta1/types.go index 3147969066..527c857be9 100644 --- a/iam/api/v1beta1/types.go +++ b/iam/api/v1beta1/types.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package v1beta1 contains API Schema definitions for the iam v1beta1 API group. // +k8s:deepcopy-gen=package,register // +k8s:defaulter-gen=TypeMeta // +groupName=iam.aws.infrastructure.cluster.x-k8s.io diff --git a/main.go b/main.go index 32954e6dfd..35d324dcc6 100644 --- a/main.go +++ b/main.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package main contains the main entrypoint for the AWS provider components. package main import ( @@ -254,6 +255,16 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "ROSAMachinePool") os.Exit(1) } + + if err := (&rosacontrolplanev1.ROSAControlPlane{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "ROSAControlPlane") + os.Exit(1) + } + + if err := (&expinfrav1.ROSAMachinePool{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "ROSAMachinePool") + os.Exit(1) + } } // +kubebuilder:scaffold:builder diff --git a/pkg/annotations/annotations.go b/pkg/annotations/annotations.go index debcd25153..8bc4a00ff3 100644 --- a/pkg/annotations/annotations.go +++ b/pkg/annotations/annotations.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package annotations provides utility functions for working with annotations. package annotations import ( diff --git a/pkg/cloud/awserrors/errors.go b/pkg/cloud/awserrors/errors.go index b7ff53b654..5312e4fe42 100644 --- a/pkg/cloud/awserrors/errors.go +++ b/pkg/cloud/awserrors/errors.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package awserrors provides a way to generate AWS errors. package awserrors import ( @@ -102,6 +103,7 @@ func NewConflict(msg string) error { } } +// IsBucketAlreadyOwnedByYou checks if the bucket is already owned. func IsBucketAlreadyOwnedByYou(err error) bool { if code, ok := Code(err); ok { return code == BucketAlreadyOwnedByYou diff --git a/pkg/cloud/converters/eks.go b/pkg/cloud/converters/eks.go index d9bc45d8a8..d9985f4693 100644 --- a/pkg/cloud/converters/eks.go +++ b/pkg/cloud/converters/eks.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package converters provides conversion functions for AWS SDK types to CAPA types. package converters import ( @@ -146,6 +147,7 @@ func TaintEffectFromSDK(effect string) (expinfrav1.TaintEffect, error) { } } +// ConvertSDKToIdentityProvider is used to convert an AWS SDK OIDCIdentityProviderConfig to a CAPA OidcIdentityProviderConfig. func ConvertSDKToIdentityProvider(in *ekscontrolplanev1.OIDCIdentityProviderConfig) *identityprovider.OidcIdentityProviderConfig { if in != nil { if in.RequiredClaims == nil { diff --git a/pkg/cloud/endpoints/endpoints.go b/pkg/cloud/endpoints/endpoints.go index e7092ec714..33a87b11cc 100644 --- a/pkg/cloud/endpoints/endpoints.go +++ b/pkg/cloud/endpoints/endpoints.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package endpoints contains aws endpoint related utilities. package endpoints import ( diff --git a/pkg/cloud/filter/types.go b/pkg/cloud/filter/types.go index 3193efc74b..3c704200d3 100644 --- a/pkg/cloud/filter/types.go +++ b/pkg/cloud/filter/types.go @@ -14,4 +14,5 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package filter contains the ec2 sdk related filters. package filter diff --git a/pkg/cloud/identity/identity.go b/pkg/cloud/identity/identity.go index c14a667e24..29a57a7337 100644 --- a/pkg/cloud/identity/identity.go +++ b/pkg/cloud/identity/identity.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package identity provides the AWSPrincipalTypeProvider interface and its implementations. package identity import ( @@ -79,7 +80,7 @@ func GetAssumeRoleCredentials(roleIdentityProvider *AWSRolePrincipalTypeProvider } // NewAWSRolePrincipalTypeProvider will create a new AWSRolePrincipalTypeProvider from an AWSClusterRoleIdentity. -func NewAWSRolePrincipalTypeProvider(identity *infrav1.AWSClusterRoleIdentity, sourceProvider *AWSPrincipalTypeProvider, log logger.Wrapper) *AWSRolePrincipalTypeProvider { +func NewAWSRolePrincipalTypeProvider(identity *infrav1.AWSClusterRoleIdentity, sourceProvider AWSPrincipalTypeProvider, log logger.Wrapper) *AWSRolePrincipalTypeProvider { return &AWSRolePrincipalTypeProvider{ credentials: nil, stsClient: nil, @@ -129,7 +130,7 @@ func (p *AWSStaticPrincipalTypeProvider) IsExpired() bool { type AWSRolePrincipalTypeProvider struct { Principal *infrav1.AWSClusterRoleIdentity credentials *credentials.Credentials - sourceProvider *AWSPrincipalTypeProvider + sourceProvider AWSPrincipalTypeProvider log logger.Wrapper stsClient stsiface.STSAPI } @@ -155,7 +156,7 @@ func (p *AWSRolePrincipalTypeProvider) Retrieve() (credentials.Value, error) { if p.credentials == nil || p.IsExpired() { awsConfig := aws.NewConfig() if p.sourceProvider != nil { - sourceCreds, err := (*p.sourceProvider).Retrieve() + sourceCreds, err := p.sourceProvider.Retrieve() if err != nil { return credentials.Value{}, err } diff --git a/pkg/cloud/identity/identity_test.go b/pkg/cloud/identity/identity_test.go index 29cd0ee826..8c204be9f4 100644 --- a/pkg/cloud/identity/identity_test.go +++ b/pkg/cloud/identity/identity_test.go @@ -45,7 +45,7 @@ func TestAWSStaticPrincipalTypeProvider(t *testing.T) { }, } - var staticProvider AWSPrincipalTypeProvider = NewAWSStaticPrincipalTypeProvider(&infrav1.AWSClusterStaticIdentity{}, secret) + staticProvider := NewAWSStaticPrincipalTypeProvider(&infrav1.AWSClusterStaticIdentity{}, secret) stsMock := mock_stsiface.NewMockSTSAPI(mockCtrl) roleIdentity := &infrav1.AWSClusterRoleIdentity{ @@ -58,10 +58,10 @@ func TestAWSStaticPrincipalTypeProvider(t *testing.T) { }, } - var roleProvider AWSPrincipalTypeProvider = &AWSRolePrincipalTypeProvider{ + roleProvider := &AWSRolePrincipalTypeProvider{ credentials: nil, Principal: roleIdentity, - sourceProvider: &staticProvider, + sourceProvider: staticProvider, stsClient: stsMock, } @@ -75,10 +75,10 @@ func TestAWSStaticPrincipalTypeProvider(t *testing.T) { }, } - var roleProvider2 AWSPrincipalTypeProvider = &AWSRolePrincipalTypeProvider{ + roleProvider2 := &AWSRolePrincipalTypeProvider{ credentials: nil, Principal: roleIdentity2, - sourceProvider: &roleProvider, + sourceProvider: roleProvider, stsClient: stsMock, } @@ -167,8 +167,8 @@ func TestAWSStaticPrincipalTypeProvider(t *testing.T) { name: "Role provider with role provider source fails to retrieve when the source's source cannot assume source", provider: roleProvider2, expect: func(m *mock_stsiface.MockSTSAPIMockRecorder) { - roleProvider.(*AWSRolePrincipalTypeProvider).credentials.Expire() - roleProvider2.(*AWSRolePrincipalTypeProvider).credentials.Expire() + roleProvider.credentials.Expire() + roleProvider2.credentials.Expire() // AssumeRoleWithContext() call is not needed for roleIdentity as it has unexpired credentials m.AssumeRoleWithContext(gomock.Any(), &sts.AssumeRoleInput{ RoleArn: aws.String(roleIdentity.Spec.RoleArn), diff --git a/pkg/cloud/interfaces.go b/pkg/cloud/interfaces.go index 751d9603ea..0ebc12e383 100644 --- a/pkg/cloud/interfaces.go +++ b/pkg/cloud/interfaces.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package cloud contains interfaces for working with AWS resources. package cloud import ( diff --git a/pkg/cloud/logs/logs.go b/pkg/cloud/logs/logs.go index d20c657347..af22708f12 100644 --- a/pkg/cloud/logs/logs.go +++ b/pkg/cloud/logs/logs.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package logs provides a wrapper for the logr.Logger to be used as an AWS Logger. package logs import ( diff --git a/pkg/cloud/metrics/metrics.go b/pkg/cloud/metrics/metrics.go index b2c763ee78..4c3e5e988d 100644 --- a/pkg/cloud/metrics/metrics.go +++ b/pkg/cloud/metrics/metrics.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package metrics provides a way to capture request metrics. package metrics import ( diff --git a/pkg/cloud/scope/cluster.go b/pkg/cloud/scope/cluster.go index 399b07f6e0..aa988f8825 100644 --- a/pkg/cloud/scope/cluster.go +++ b/pkg/cloud/scope/cluster.go @@ -184,6 +184,7 @@ func (s *ClusterScope) ControlPlaneLoadBalancer() *infrav1.AWSLoadBalancerSpec { return s.AWSCluster.Spec.ControlPlaneLoadBalancer } +// ControlPlaneLoadBalancers returns load balancers configured for the control plane. func (s *ClusterScope) ControlPlaneLoadBalancers() []*infrav1.AWSLoadBalancerSpec { return []*infrav1.AWSLoadBalancerSpec{ s.AWSCluster.Spec.ControlPlaneLoadBalancer, @@ -192,6 +193,7 @@ func (s *ClusterScope) ControlPlaneLoadBalancers() []*infrav1.AWSLoadBalancerSpe } // ControlPlaneLoadBalancerScheme returns the Classic ELB scheme (public or internal facing). +// Deprecated: This method is going to be removed in a future release. Use LoadBalancer.Scheme. func (s *ClusterScope) ControlPlaneLoadBalancerScheme() infrav1.ELBScheme { if s.ControlPlaneLoadBalancer() != nil && s.ControlPlaneLoadBalancer().Scheme != nil { return *s.ControlPlaneLoadBalancer().Scheme @@ -199,6 +201,7 @@ func (s *ClusterScope) ControlPlaneLoadBalancerScheme() infrav1.ELBScheme { return infrav1.ELBSchemeInternetFacing } +// ControlPlaneLoadBalancerName returns the name of the control plane load balancer. func (s *ClusterScope) ControlPlaneLoadBalancerName() *string { if s.AWSCluster.Spec.ControlPlaneLoadBalancer != nil { return s.AWSCluster.Spec.ControlPlaneLoadBalancer.Name @@ -206,10 +209,12 @@ func (s *ClusterScope) ControlPlaneLoadBalancerName() *string { return nil } +// ControlPlaneEndpoint returns the cluster control plane endpoint. func (s *ClusterScope) ControlPlaneEndpoint() clusterv1.APIEndpoint { return s.AWSCluster.Spec.ControlPlaneEndpoint } +// Bucket returns the cluster bucket configuration. func (s *ClusterScope) Bucket() *infrav1.S3Bucket { return s.AWSCluster.Spec.S3Bucket } diff --git a/pkg/cloud/scope/elb.go b/pkg/cloud/scope/elb.go index 53b3d6db99..3d588f665b 100644 --- a/pkg/cloud/scope/elb.go +++ b/pkg/cloud/scope/elb.go @@ -43,6 +43,7 @@ type ELBScope interface { ControlPlaneLoadBalancer() *infrav1.AWSLoadBalancerSpec // ControlPlaneLoadBalancerScheme returns the Classic ELB scheme (public or internal facing) + // Deprecated: This method is going to be removed in a future release. Use LoadBalancer.Scheme. ControlPlaneLoadBalancerScheme() infrav1.ELBScheme // ControlPlaneLoadBalancerName returns the Classic ELB name diff --git a/pkg/cloud/scope/global.go b/pkg/cloud/scope/global.go index cd02a81eef..2ecc9dbf50 100644 --- a/pkg/cloud/scope/global.go +++ b/pkg/cloud/scope/global.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package scope provides a global scope for CAPA controllers. package scope import ( diff --git a/pkg/cloud/scope/launchtemplate.go b/pkg/cloud/scope/launchtemplate.go index 676e255365..fb2df8b59f 100644 --- a/pkg/cloud/scope/launchtemplate.go +++ b/pkg/cloud/scope/launchtemplate.go @@ -51,11 +51,13 @@ type LaunchTemplateScope interface { logger.Wrapper } +// ResourceServiceToUpdate is a struct that contains the resource ID and the resource service to update. type ResourceServiceToUpdate struct { ResourceID *string ResourceService ResourceService } +// ResourceService defines the interface for resources. type ResourceService interface { UpdateResourceTags(resourceID *string, create, remove map[string]string) error } diff --git a/pkg/cloud/scope/machine.go b/pkg/cloud/scope/machine.go index f69fa572a5..f547f284cb 100644 --- a/pkg/cloud/scope/machine.go +++ b/pkg/cloud/scope/machine.go @@ -19,7 +19,6 @@ package scope import ( "context" "encoding/base64" - "fmt" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" @@ -142,7 +141,7 @@ func (m *MachineScope) GetProviderID() string { // SetProviderID sets the AWSMachine providerID in spec. func (m *MachineScope) SetProviderID(instanceID, availabilityZone string) { - providerID := fmt.Sprintf("aws:///%s/%s", availabilityZone, instanceID) + providerID := GenerateProviderID(availabilityZone, instanceID) m.AWSMachine.Spec.ProviderID = ptr.To[string](providerID) } @@ -195,6 +194,7 @@ func (m *MachineScope) UseSecretsManager(userDataFormat string) bool { return !m.AWSMachine.Spec.CloudInit.InsecureSkipSecretsManager && !m.UseIgnition(userDataFormat) } +// UseIgnition returns true if the AWSMachine should use Ignition. func (m *MachineScope) UseIgnition(userDataFormat string) bool { return userDataFormat == "ignition" || (m.AWSMachine.Spec.Ignition != nil) } @@ -265,6 +265,7 @@ func (m *MachineScope) GetRawBootstrapData() ([]byte, error) { return data, err } +// GetRawBootstrapDataWithFormat returns the bootstrap data from the secret in the Machine's bootstrap.dataSecretName. func (m *MachineScope) GetRawBootstrapDataWithFormat() ([]byte, string, error) { if m.Machine.Spec.Bootstrap.DataSecretName == nil { return nil, "", errors.New("error retrieving bootstrap data: linked Machine's bootstrap.dataSecretName is nil") diff --git a/pkg/cloud/scope/machinepool.go b/pkg/cloud/scope/machinepool.go index 069c76a41b..00e8abeadc 100644 --- a/pkg/cloud/scope/machinepool.go +++ b/pkg/cloud/scope/machinepool.go @@ -234,34 +234,40 @@ func (m *MachinePoolScope) SetASGStatus(v expinfrav1.ASGStatus) { m.AWSMachinePool.Status.ASGStatus = &v } +// GetObjectMeta returns the AWSMachinePool ObjectMeta. func (m *MachinePoolScope) GetObjectMeta() *metav1.ObjectMeta { return &m.AWSMachinePool.ObjectMeta } +// GetSetter returns the AWSMachinePool object setter. func (m *MachinePoolScope) GetSetter() conditions.Setter { return m.AWSMachinePool } +// GetEC2Scope returns the EC2 scope. func (m *MachinePoolScope) GetEC2Scope() EC2Scope { return m.InfraCluster } +// GetLaunchTemplateIDStatus returns the launch template ID status. func (m *MachinePoolScope) GetLaunchTemplateIDStatus() string { return m.AWSMachinePool.Status.LaunchTemplateID } +// SetLaunchTemplateIDStatus sets the launch template ID status. func (m *MachinePoolScope) SetLaunchTemplateIDStatus(id string) { m.AWSMachinePool.Status.LaunchTemplateID = id } +// GetLaunchTemplateLatestVersionStatus returns the launch template latest version status. func (m *MachinePoolScope) GetLaunchTemplateLatestVersionStatus() string { if m.AWSMachinePool.Status.LaunchTemplateVersion != nil { return *m.AWSMachinePool.Status.LaunchTemplateVersion - } else { - return "" } + return "" } +// SetLaunchTemplateLatestVersionStatus sets the launch template latest version status. func (m *MachinePoolScope) SetLaunchTemplateLatestVersionStatus(version string) { m.AWSMachinePool.Status.LaunchTemplateVersion = &version } @@ -370,18 +376,22 @@ func nodeIsReady(node corev1.Node) bool { return false } +// GetLaunchTemplate returns the launch template. func (m *MachinePoolScope) GetLaunchTemplate() *expinfrav1.AWSLaunchTemplate { return &m.AWSMachinePool.Spec.AWSLaunchTemplate } +// GetMachinePool returns the machine pool object. func (m *MachinePoolScope) GetMachinePool() *expclusterv1.MachinePool { return m.MachinePool } +// LaunchTemplateName returns the name of the launch template. func (m *MachinePoolScope) LaunchTemplateName() string { return m.Name() } +// GetRuntimeObject returns the AWSMachinePool object, in runtime.Object form. func (m *MachinePoolScope) GetRuntimeObject() runtime.Object { return m.AWSMachinePool } diff --git a/pkg/cloud/scope/managedcontrolplane.go b/pkg/cloud/scope/managedcontrolplane.go index 948f3fa511..bc93cd49ba 100644 --- a/pkg/cloud/scope/managedcontrolplane.go +++ b/pkg/cloud/scope/managedcontrolplane.go @@ -407,6 +407,7 @@ func (s *ManagedControlPlaneScope) VpcCni() ekscontrolplanev1.VpcCni { return s.ControlPlane.Spec.VpcCni } +// OIDCIdentityProviderConfig returns the OIDC identity provider config. func (s *ManagedControlPlaneScope) OIDCIdentityProviderConfig() *ekscontrolplanev1.OIDCIdentityProviderConfig { return s.ControlPlane.Spec.OIDCIdentityProviderConfig } diff --git a/pkg/cloud/scope/managednodegroup.go b/pkg/cloud/scope/managednodegroup.go index 1950ea0221..e9421d7282 100644 --- a/pkg/cloud/scope/managednodegroup.go +++ b/pkg/cloud/scope/managednodegroup.go @@ -315,14 +315,17 @@ func (s *ManagedMachinePoolScope) NodegroupName() string { return s.ManagedMachinePool.Spec.EKSNodegroupName } +// Name returns the name of the AWSManagedMachinePool. func (s *ManagedMachinePoolScope) Name() string { return s.ManagedMachinePool.Name } +// Namespace returns the namespace of the AWSManagedMachinePool. func (s *ManagedMachinePoolScope) Namespace() string { return s.ManagedMachinePool.Namespace } +// GetRawBootstrapData returns the raw bootstrap data from the linked Machine's bootstrap.dataSecretName. func (s *ManagedMachinePoolScope) GetRawBootstrapData() ([]byte, *types.NamespacedName, error) { if s.MachinePool.Spec.Template.Spec.Bootstrap.DataSecretName == nil { return nil, nil, errors.New("error retrieving bootstrap data: linked Machine's bootstrap.dataSecretName is nil") @@ -343,58 +346,68 @@ func (s *ManagedMachinePoolScope) GetRawBootstrapData() ([]byte, *types.Namespac return value, &key, nil } +// GetObjectMeta returns the ObjectMeta for the AWSManagedMachinePool. func (s *ManagedMachinePoolScope) GetObjectMeta() *metav1.ObjectMeta { return &s.ManagedMachinePool.ObjectMeta } +// GetSetter returns the condition setter. func (s *ManagedMachinePoolScope) GetSetter() conditions.Setter { return s.ManagedMachinePool } +// GetEC2Scope returns the EC2Scope. func (s *ManagedMachinePoolScope) GetEC2Scope() EC2Scope { return s.EC2Scope } +// IsEKSManaged returns true if the control plane is managed by EKS. func (s *ManagedMachinePoolScope) IsEKSManaged() bool { return true } +// GetLaunchTemplateIDStatus returns the launch template ID status. func (s *ManagedMachinePoolScope) GetLaunchTemplateIDStatus() string { if s.ManagedMachinePool.Status.LaunchTemplateID != nil { return *s.ManagedMachinePool.Status.LaunchTemplateID - } else { - return "" } + return "" } +// SetLaunchTemplateIDStatus sets the launch template ID status. func (s *ManagedMachinePoolScope) SetLaunchTemplateIDStatus(id string) { s.ManagedMachinePool.Status.LaunchTemplateID = &id } +// GetLaunchTemplateLatestVersionStatus returns the launch template latest version status. func (s *ManagedMachinePoolScope) GetLaunchTemplateLatestVersionStatus() string { if s.ManagedMachinePool.Status.LaunchTemplateVersion != nil { return *s.ManagedMachinePool.Status.LaunchTemplateVersion - } else { - return "" } + return "" } +// SetLaunchTemplateLatestVersionStatus sets the launch template latest version status. func (s *ManagedMachinePoolScope) SetLaunchTemplateLatestVersionStatus(version string) { s.ManagedMachinePool.Status.LaunchTemplateVersion = &version } +// GetLaunchTemplate returns the launch template. func (s *ManagedMachinePoolScope) GetLaunchTemplate() *expinfrav1.AWSLaunchTemplate { return s.ManagedMachinePool.Spec.AWSLaunchTemplate } +// GetMachinePool returns the machine pool. func (s *ManagedMachinePoolScope) GetMachinePool() *expclusterv1.MachinePool { return s.MachinePool } +// LaunchTemplateName returns the launch template name. func (s *ManagedMachinePoolScope) LaunchTemplateName() string { return fmt.Sprintf("%s-%s", s.ControlPlane.Name, s.ManagedMachinePool.Name) } +// GetRuntimeObject returns the AWSManagedMachinePool, in runtime.Object form. func (s *ManagedMachinePoolScope) GetRuntimeObject() runtime.Object { return s.ManagedMachinePool } diff --git a/pkg/cloud/scope/providerid.go b/pkg/cloud/scope/providerid.go index ecf3feea19..1b11135ce4 100644 --- a/pkg/cloud/scope/providerid.go +++ b/pkg/cloud/scope/providerid.go @@ -17,6 +17,7 @@ limitations under the License. package scope import ( + "fmt" "regexp" "strings" @@ -124,3 +125,14 @@ func (p *ProviderID) Validate() bool { func (p *ProviderID) IndexKey() string { return p.String() } + +// ProviderIDPrefix is the prefix of AWS resource IDs to form the Kubernetes Provider ID. +// NOTE: this format matches the 2 slashes format used in cloud-provider and cluster-autoscaler. +const ProviderIDPrefix = "aws://" + +// GenerateProviderID generates a valid AWS Node/Machine ProviderID field. +// +// By default, the last id provided is used as identifier (last part). +func GenerateProviderID(ids ...string) string { + return fmt.Sprintf("%s/%s", ProviderIDPrefix, strings.Join(ids, "/")) +} diff --git a/pkg/cloud/scope/providerid_test.go b/pkg/cloud/scope/providerid_test.go new file mode 100644 index 0000000000..df6011f8d2 --- /dev/null +++ b/pkg/cloud/scope/providerid_test.go @@ -0,0 +1,55 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package scope + +import ( + "testing" + + . "github.com/onsi/gomega" +) + +func TestGenerateProviderID(t *testing.T) { + testCases := []struct { + ids []string + + expectedProviderID string + }{ + { + ids: []string{ + "eu-west-1a", + "instance-id", + }, + expectedProviderID: "aws:///eu-west-1a/instance-id", + }, + { + ids: []string{ + "eu-west-1a", + "test-id1", + "test-id2", + "instance-id", + }, + expectedProviderID: "aws:///eu-west-1a/test-id1/test-id2/instance-id", + }, + } + + for _, tc := range testCases { + g := NewGomegaWithT(t) + providerID := GenerateProviderID(tc.ids...) + + g.Expect(providerID).To(Equal(tc.expectedProviderID)) + } +} diff --git a/pkg/cloud/scope/rosacontrolplane.go b/pkg/cloud/scope/rosacontrolplane.go index da4c36cb13..c533304c1e 100644 --- a/pkg/cloud/scope/rosacontrolplane.go +++ b/pkg/cloud/scope/rosacontrolplane.go @@ -37,6 +37,7 @@ import ( "sigs.k8s.io/cluster-api/util/patch" ) +// ROSAControlPlaneScopeParams defines the input parameters used to create a new ROSAControlPlaneScope. type ROSAControlPlaneScopeParams struct { Client client.Client Logger *logger.Logger @@ -46,6 +47,7 @@ type ROSAControlPlaneScopeParams struct { Endpoints []ServiceEndpoint } +// NewROSAControlPlaneScope creates a new ROSAControlPlaneScope from the supplied parameters. func NewROSAControlPlaneScope(params ROSAControlPlaneScopeParams) (*ROSAControlPlaneScope, error) { if params.Cluster == nil { return nil, errors.New("failed to generate new scope from nil Cluster") @@ -67,7 +69,7 @@ func NewROSAControlPlaneScope(params ROSAControlPlaneScopeParams) (*ROSAControlP controllerName: params.ControllerName, } - session, serviceLimiters, err := sessionForClusterWithRegion(params.Client, managedScope, *params.ControlPlane.Spec.Region, params.Endpoints, params.Logger) + session, serviceLimiters, err := sessionForClusterWithRegion(params.Client, managedScope, params.ControlPlane.Spec.Region, params.Endpoints, params.Logger) if err != nil { return nil, errors.Errorf("failed to create aws session: %v", err) } @@ -106,18 +108,22 @@ type ROSAControlPlaneScope struct { Identity *sts.GetCallerIdentityOutput } +// InfraCluster returns the AWSManagedControlPlane object. func (s *ROSAControlPlaneScope) InfraCluster() cloud.ClusterObject { return s.ControlPlane } +// IdentityRef returns the AWSIdentityReference object. func (s *ROSAControlPlaneScope) IdentityRef() *infrav1.AWSIdentityReference { return s.ControlPlane.Spec.IdentityRef } +// Session returns the AWS SDK session. Used for creating clients. func (s *ROSAControlPlaneScope) Session() awsclient.ConfigProvider { return s.session } +// ServiceLimiter returns the AWS SDK session. Used for creating clients. func (s *ROSAControlPlaneScope) ServiceLimiter(service string) *throttle.ServiceLimiter { if sl, ok := s.serviceLimiters[service]; ok { return sl @@ -125,6 +131,7 @@ func (s *ROSAControlPlaneScope) ServiceLimiter(service string) *throttle.Service return nil } +// ControllerName returns the name of the controller. func (s *ROSAControlPlaneScope) ControllerName() string { return s.controllerName } @@ -143,6 +150,7 @@ func (s *ROSAControlPlaneScope) InfraClusterName() string { return s.ControlPlane.Name } +// RosaClusterName returns the ROSA cluster name. func (s *ROSAControlPlaneScope) RosaClusterName() string { return s.ControlPlane.Spec.RosaClusterName } @@ -167,6 +175,7 @@ func (s *ROSAControlPlaneScope) CredentialsSecret() *corev1.Secret { } } +// ClusterAdminPasswordSecret returns the corev1.Secret object for the cluster admin password. func (s *ROSAControlPlaneScope) ClusterAdminPasswordSecret() *corev1.Secret { return &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -183,6 +192,8 @@ func (s *ROSAControlPlaneScope) PatchObject() error { s.ControlPlane, patch.WithOwnedConditions{Conditions: []clusterv1.ConditionType{ rosacontrolplanev1.ROSAControlPlaneReadyCondition, + rosacontrolplanev1.ROSAControlPlaneValidCondition, + rosacontrolplanev1.ROSAControlPlaneUpgradingCondition, }}) } diff --git a/pkg/cloud/scope/rosamachinepool.go b/pkg/cloud/scope/rosamachinepool.go index c39372b6b0..00d480ca3e 100644 --- a/pkg/cloud/scope/rosamachinepool.go +++ b/pkg/cloud/scope/rosamachinepool.go @@ -19,13 +19,16 @@ package scope import ( "context" + awsclient "github.com/aws/aws-sdk-go/aws/client" "github.com/pkg/errors" "k8s.io/klog/v2" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" rosacontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/rosa/api/v1beta2" expinfrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/exp/api/v1beta2" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud" + "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/throttle" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/logger" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" expclusterv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1" @@ -42,6 +45,8 @@ type RosaMachinePoolScopeParams struct { RosaMachinePool *expinfrav1.ROSAMachinePool MachinePool *expclusterv1.MachinePool ControllerName string + + Endpoints []ServiceEndpoint } // NewRosaMachinePoolScope creates a new Scope from the supplied parameters. @@ -70,7 +75,7 @@ func NewRosaMachinePoolScope(params RosaMachinePoolScopeParams) (*RosaMachinePoo return nil, errors.Wrap(err, "failed to init MachinePool patch helper") } - return &RosaMachinePoolScope{ + scope := &RosaMachinePoolScope{ Logger: *params.Logger, Client: params.Client, patchHelper: ammpHelper, @@ -81,9 +86,22 @@ func NewRosaMachinePoolScope(params RosaMachinePoolScopeParams) (*RosaMachinePoo RosaMachinePool: params.RosaMachinePool, MachinePool: params.MachinePool, controllerName: params.ControllerName, - }, nil + } + + session, serviceLimiters, err := sessionForClusterWithRegion(params.Client, scope, params.ControlPlane.Spec.Region, params.Endpoints, params.Logger) + if err != nil { + return nil, errors.Errorf("failed to create aws session: %v", err) + } + + scope.session = session + scope.serviceLimiters = serviceLimiters + + return scope, nil } +var _ cloud.Session = &RosaMachinePoolScope{} +var _ cloud.SessionMetadata = &RosaMachinePoolScope{} + // RosaMachinePoolScope defines the basic context for an actuator to operate upon. type RosaMachinePoolScope struct { logger.Logger @@ -96,6 +114,9 @@ type RosaMachinePoolScope struct { RosaMachinePool *expinfrav1.ROSAMachinePool MachinePool *expclusterv1.MachinePool + session awsclient.ConfigProvider + serviceLimiters throttle.ServiceLimiters + controllerName string } @@ -135,10 +156,39 @@ func (s *RosaMachinePoolScope) ControllerName() string { return s.controllerName } +// GetSetter returns the condition setter for the RosaMachinePool. func (s *RosaMachinePoolScope) GetSetter() conditions.Setter { return s.RosaMachinePool } +// ServiceLimiter implements cloud.Session. +func (s *RosaMachinePoolScope) ServiceLimiter(service string) *throttle.ServiceLimiter { + if sl, ok := s.serviceLimiters[service]; ok { + return sl + } + return nil +} + +// Session implements cloud.Session. +func (s *RosaMachinePoolScope) Session() awsclient.ConfigProvider { + return s.session +} + +// IdentityRef implements cloud.SessionMetadata. +func (s *RosaMachinePoolScope) IdentityRef() *v1beta2.AWSIdentityReference { + return s.ControlPlane.Spec.IdentityRef +} + +// InfraClusterName implements cloud.SessionMetadata. +func (s *RosaMachinePoolScope) InfraClusterName() string { + return s.ControlPlane.Name +} + +// Namespace implements cloud.SessionMetadata. +func (s *RosaMachinePoolScope) Namespace() string { + return s.Cluster.Namespace +} + // RosaMchinePoolReadyFalse marks the ready condition false using warning if error isn't // empty. func (s *RosaMachinePoolScope) RosaMchinePoolReadyFalse(reason string, err string) error { diff --git a/pkg/cloud/scope/session.go b/pkg/cloud/scope/session.go index cda46352f5..95f5e68662 100644 --- a/pkg/cloud/scope/session.go +++ b/pkg/cloud/scope/session.go @@ -313,11 +313,7 @@ func buildProvidersForRef( } } - if sourceProvider != nil { - provider = identity.NewAWSRolePrincipalTypeProvider(roleIdentity, &sourceProvider, log) - } else { - provider = identity.NewAWSRolePrincipalTypeProvider(roleIdentity, nil, log) - } + provider = identity.NewAWSRolePrincipalTypeProvider(roleIdentity, sourceProvider, log) providers = append(providers, provider) default: return providers, errors.Errorf("No such provider known: '%s'", ref.Kind) diff --git a/pkg/cloud/services/autoscaling/autoscalinggroup.go b/pkg/cloud/services/autoscaling/autoscalinggroup.go index 6e24cf22f9..9ddd4c086d 100644 --- a/pkg/cloud/services/autoscaling/autoscalinggroup.go +++ b/pkg/cloud/services/autoscaling/autoscalinggroup.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package asg provides a service for managing AWS AutoScalingGroups. package asg import ( @@ -471,6 +472,7 @@ func (s *Service) UpdateResourceTags(resourceID *string, create, remove map[stri return nil } +// SuspendProcesses suspends the processes for an autoscaling group. func (s *Service) SuspendProcesses(name string, processes []string) error { input := autoscaling.ScalingProcessQuery{ AutoScalingGroupName: aws.String(name), @@ -482,6 +484,7 @@ func (s *Service) SuspendProcesses(name string, processes []string) error { return nil } +// ResumeProcesses resumes the processes for an autoscaling group. func (s *Service) ResumeProcesses(name string, processes []string) error { input := autoscaling.ScalingProcessQuery{ AutoScalingGroupName: aws.String(name), diff --git a/pkg/cloud/services/autoscaling/mock_autoscalingiface/doc.go b/pkg/cloud/services/autoscaling/mock_autoscalingiface/doc.go index 522e4d3ab5..f664299d6d 100644 --- a/pkg/cloud/services/autoscaling/mock_autoscalingiface/doc.go +++ b/pkg/cloud/services/autoscaling/mock_autoscalingiface/doc.go @@ -14,8 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package mock_autoscalingiface provides a mock implementation for the AutoScalingAPI interface. // Run go generate to regenerate this mock. +// //go:generate ../../../../../hack/tools/bin/mockgen -destination autoscalingapi_mock.go -package mock_autoscalingiface github.com/aws/aws-sdk-go/service/autoscaling/autoscalingiface AutoScalingAPI //go:generate /usr/bin/env bash -c "cat ../../../../../hack/boilerplate/boilerplate.generatego.txt autoscalingapi_mock.go > _autoscalingapi_mock.go && mv _autoscalingapi_mock.go autoscalingapi_mock.go" - package mock_autoscalingiface //nolint:stylecheck diff --git a/pkg/cloud/services/awsnode/cni_test.go b/pkg/cloud/services/awsnode/cni_test.go index 1619d843ac..67c78d806b 100644 --- a/pkg/cloud/services/awsnode/cni_test.go +++ b/pkg/cloud/services/awsnode/cni_test.go @@ -263,7 +263,7 @@ type cachingClient struct { updateChain []client.Object } -func (c *cachingClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { +func (c *cachingClient) Get(_ context.Context, _ client.ObjectKey, obj client.Object, _ ...client.GetOption) error { if _, ok := obj.(*v1.DaemonSet); ok { daemonset, _ := obj.(*v1.DaemonSet) *daemonset = *c.getValue.(*v1.DaemonSet) @@ -271,12 +271,12 @@ func (c *cachingClient) Get(ctx context.Context, key client.ObjectKey, obj clien return nil } -func (c *cachingClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { +func (c *cachingClient) Update(_ context.Context, obj client.Object, _ ...client.UpdateOption) error { c.updateChain = append(c.updateChain, obj) return nil } -func (c *cachingClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { +func (c *cachingClient) List(_ context.Context, _ client.ObjectList, _ ...client.ListOption) error { return nil } @@ -297,7 +297,7 @@ func (s *mockScope) VpcCni() ekscontrolplanev1.VpcCni { return s.cni } -func (s *mockScope) Info(msg string, keysAndValues ...interface{}) { +func (s *mockScope) Info(_ string, _ ...interface{}) { } diff --git a/pkg/cloud/services/awsnode/service.go b/pkg/cloud/services/awsnode/service.go index 892a703429..ddc8d52251 100644 --- a/pkg/cloud/services/awsnode/service.go +++ b/pkg/cloud/services/awsnode/service.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package awsnode provides a way to interact with AWS nodes. package awsnode import ( diff --git a/pkg/cloud/services/ec2/instances.go b/pkg/cloud/services/ec2/instances.go index 36e6661f29..dde96bd8a1 100644 --- a/pkg/cloud/services/ec2/instances.go +++ b/pkg/cloud/services/ec2/instances.go @@ -98,11 +98,11 @@ func (s *Service) InstanceIfExists(id *string) (*infrav1.Instance, error) { if len(out.Reservations) > 0 && len(out.Reservations[0].Instances) > 0 { return s.SDKToInstance(out.Reservations[0].Instances[0]) - } else { - // Failed to find instance with provider id. - record.Eventf(s.scope.InfraCluster(), "FailedFindInstances", "failed to find instance by providerId %q: %v", *id, err) - return nil, ErrInstanceNotFoundByID } + + // Failed to find instance with provider id. + record.Eventf(s.scope.InfraCluster(), "FailedFindInstances", "failed to find instance by providerId %q: %v", *id, err) + return nil, ErrInstanceNotFoundByID } // CreateInstance runs an ec2 instance. diff --git a/pkg/cloud/services/ec2/launchtemplate.go b/pkg/cloud/services/ec2/launchtemplate.go index 356433ed91..bb04605475 100644 --- a/pkg/cloud/services/ec2/launchtemplate.go +++ b/pkg/cloud/services/ec2/launchtemplate.go @@ -187,6 +187,7 @@ func (s *Service) ReconcileLaunchTemplate( return nil } +// ReconcileTags reconciles the tags for the AWSMachinePool instances. func (s *Service) ReconcileTags(scope scope.LaunchTemplateScope, resourceServicesToUpdate []scope.ResourceServiceToUpdate) error { additionalTags := scope.AdditionalTags() @@ -226,6 +227,7 @@ func (s *Service) ensureTags(scope scope.LaunchTemplateScope, resourceServicesTo return changed, nil } +// MachinePoolAnnotationJSON returns the annotation's json value as a map. func MachinePoolAnnotationJSON(lts scope.LaunchTemplateScope, annotation string) (map[string]interface{}, error) { out := map[string]interface{}{} @@ -246,6 +248,7 @@ func machinePoolAnnotation(lts scope.LaunchTemplateScope, annotation string) str return lts.GetObjectMeta().GetAnnotations()[annotation] } +// UpdateMachinePoolAnnotationJSON updates the annotation with the given content. func UpdateMachinePoolAnnotationJSON(lts scope.LaunchTemplateScope, annotation string, content map[string]interface{}) error { b, err := json.Marshal(content) if err != nil { @@ -618,6 +621,7 @@ func (s *Service) PruneLaunchTemplateVersions(id string) error { return s.deleteLaunchTemplateVersion(id, versionToPrune) } +// GetLaunchTemplateLatestVersion returns the latest version of a launch template. func (s *Service) GetLaunchTemplateLatestVersion(id string) (string, error) { input := &ec2.DescribeLaunchTemplateVersionsInput{ LaunchTemplateId: aws.String(id), @@ -854,6 +858,7 @@ func (s *Service) DiscoverLaunchTemplateAMI(scope scope.LaunchTemplateScope) (*s return aws.String(lookupAMI), nil } +// GetAdditionalSecurityGroupsIDs returns the security group IDs for the additional security groups. func (s *Service) GetAdditionalSecurityGroupsIDs(securityGroups []infrav1.AWSResourceReference) ([]string, error) { var additionalSecurityGroupsIDs []string diff --git a/pkg/cloud/services/ec2/service.go b/pkg/cloud/services/ec2/service.go index f303f1a095..b085ee86c8 100644 --- a/pkg/cloud/services/ec2/service.go +++ b/pkg/cloud/services/ec2/service.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package ec2 provides a way to interact with the AWS EC2 API. package ec2 import ( diff --git a/pkg/cloud/services/eks/cluster.go b/pkg/cloud/services/eks/cluster.go index c7d786a690..6fe0fd5a34 100644 --- a/pkg/cloud/services/eks/cluster.go +++ b/pkg/cloud/services/eks/cluster.go @@ -275,11 +275,11 @@ func makeVpcConfig(subnets infrav1.Subnets, endpointAccess ekscontrolplanev1.End return nil, awserrors.NewFailedDependency("subnets in at least 2 different az's are required") } - subnetIds := make([]*string, 0) + subnetIDs := make([]*string, 0) for i := range subnets { subnet := subnets[i] subnetID := subnet.GetResourceID() - subnetIds = append(subnetIds, &subnetID) + subnetIDs = append(subnetIDs, &subnetID) } cidrs := make([]*string, 0) @@ -295,7 +295,7 @@ func makeVpcConfig(subnets infrav1.Subnets, endpointAccess ekscontrolplanev1.End vpcConfig := &eks.VpcConfigRequest{ EndpointPublicAccess: endpointAccess.Public, EndpointPrivateAccess: endpointAccess.Private, - SubnetIds: subnetIds, + SubnetIds: subnetIDs, } if len(cidrs) > 0 { diff --git a/pkg/cloud/services/eks/cluster_test.go b/pkg/cloud/services/eks/cluster_test.go index eeb92bbac0..0441a80ab9 100644 --- a/pkg/cloud/services/eks/cluster_test.go +++ b/pkg/cloud/services/eks/cluster_test.go @@ -524,10 +524,10 @@ func TestCreateCluster(t *testing.T) { }, }, }) - subnetIds := make([]*string, 0) + subnetIDs := make([]*string, 0) for i := range tc.subnets { subnet := tc.subnets[i] - subnetIds = append(subnetIds, &subnet.ID) + subnetIDs = append(subnetIDs, &subnet.ID) } if !tc.expectError { @@ -537,7 +537,7 @@ func TestCreateCluster(t *testing.T) { Name: aws.String(clusterName), EncryptionConfig: []*eks.EncryptionConfig{}, ResourcesVpcConfig: &eks.VpcConfigRequest{ - SubnetIds: subnetIds, + SubnetIds: subnetIDs, }, RoleArn: tc.role, Tags: tc.tags, diff --git a/pkg/cloud/services/eks/eks.go b/pkg/cloud/services/eks/eks.go index 7b0c81a374..958230bccd 100644 --- a/pkg/cloud/services/eks/eks.go +++ b/pkg/cloud/services/eks/eks.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package eks provides a service to reconcile EKS control plane and nodegroups. package eks import ( diff --git a/pkg/cloud/services/eks/iam/iam.go b/pkg/cloud/services/eks/iam/iam.go index e8b13e4747..bb4db97670 100644 --- a/pkg/cloud/services/eks/iam/iam.go +++ b/pkg/cloud/services/eks/iam/iam.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package iam provides a service for managing IAM roles and policies. package iam import ( @@ -483,7 +484,7 @@ func (s *IAMService) FindAndVerifyOIDCProvider(cluster *eks.Cluster) (string, er func fetchRootCAThumbprint(issuerURL string, client *http.Client) (string, error) { // needed to appease noctx. - req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, issuerURL, nil) + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, issuerURL, http.NoBody) if err != nil { return "", err } diff --git a/pkg/cloud/services/eks/nodegroup.go b/pkg/cloud/services/eks/nodegroup.go index ec24f16fc3..ab5b20aab7 100644 --- a/pkg/cloud/services/eks/nodegroup.go +++ b/pkg/cloud/services/eks/nodegroup.go @@ -257,7 +257,7 @@ func (s *NodegroupService) createNodegroup() (*eks.Nodegroup, error) { if err != nil { if aerr, ok := err.(awserr.Error); ok { switch aerr.Code() { - // TODO + // TODO: handle other errors case eks.ErrCodeResourceNotFoundException: return nil, nil default: @@ -301,7 +301,7 @@ func (s *NodegroupService) deleteNodegroupAndWait() (reterr error) { if err != nil { if aerr, ok := err.(awserr.Error); ok { switch aerr.Code() { - // TODO + // TODO handle other errors case eks.ErrCodeResourceNotFoundException: return nil default: @@ -348,6 +348,12 @@ func (s *NodegroupService) reconcileNodegroupVersion(ng *eks.Nodegroup) error { var updateMsg string // Either update k8s version or AMI version switch { + case statusLaunchTemplateVersion != nil && *statusLaunchTemplateVersion != *ngLaunchTemplateVersion: + input.LaunchTemplate = &eks.LaunchTemplateSpecification{ + Id: s.scope.ManagedMachinePool.Status.LaunchTemplateID, + Version: statusLaunchTemplateVersion, + } + updateMsg = fmt.Sprintf("to launch template version %s", *statusLaunchTemplateVersion) case specVersion != nil && ngVersion.LessThan(specVersion): // NOTE: you can only upgrade increments of minor versions. If you want to upgrade 1.14 to 1.16 we // need to go 1.14-> 1.15 and then 1.15 -> 1.16. @@ -356,12 +362,6 @@ func (s *NodegroupService) reconcileNodegroupVersion(ng *eks.Nodegroup) error { case specAMI != nil && *specAMI != ngAMI: input.ReleaseVersion = specAMI updateMsg = fmt.Sprintf("to AMI version %s", *input.ReleaseVersion) - case statusLaunchTemplateVersion != nil && *statusLaunchTemplateVersion != *ngLaunchTemplateVersion: - input.LaunchTemplate = &eks.LaunchTemplateSpecification{ - Id: s.scope.ManagedMachinePool.Status.LaunchTemplateID, - Version: statusLaunchTemplateVersion, - } - updateMsg = fmt.Sprintf("to launch template version %s", *statusLaunchTemplateVersion) } if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { diff --git a/pkg/cloud/services/eks/service.go b/pkg/cloud/services/eks/service.go index 22684defdf..9160a398a1 100644 --- a/pkg/cloud/services/eks/service.go +++ b/pkg/cloud/services/eks/service.go @@ -52,6 +52,7 @@ type Service struct { STSClient stsiface.STSAPI } +// ServiceOpts defines the functional arguments for the service. type ServiceOpts func(s *Service) // WithIAMClient creates an access spec with a custom http client. diff --git a/pkg/cloud/services/elb/loadbalancer.go b/pkg/cloud/services/elb/loadbalancer.go index 0d03adbfcc..592ef4370c 100644 --- a/pkg/cloud/services/elb/loadbalancer.go +++ b/pkg/cloud/services/elb/loadbalancer.go @@ -250,7 +250,7 @@ func (s *Service) getAPIServerLBSpec(elbName string, lbSpec *infrav1.AWSLoadBala // The load balancer APIs require us to only attach one subnet for each AZ. subnets := s.scope.Subnets().FilterPrivate() - if s.scope.ControlPlaneLoadBalancerScheme() == infrav1.ELBSchemeInternetFacing { + if scheme == infrav1.ELBSchemeInternetFacing { subnets = s.scope.Subnets().FilterPublic() } @@ -812,7 +812,7 @@ func (s *Service) RegisterInstanceWithAPIServerLB(instance *infrav1.Instance, lb return errors.Wrapf(err, "error describing ELB's target groups %q", name) } if len(targetGroups.TargetGroups) == 0 { - return errors.New(fmt.Sprintf("no target groups found for load balancer with arn '%s'", out.ARN)) + return fmt.Errorf("no target groups found for load balancer with arn '%s'", out.ARN) } // Since TargetGroups and Listeners don't care, or are not aware, of subnets before registration, we ignore that check. // Also, registering with AZ is not supported using the an InstanceID. @@ -989,9 +989,14 @@ func (s *Service) getAPIServerClassicELBSpec(elbName string) (*infrav1.LoadBalan } securityGroupIDs = append(securityGroupIDs, s.scope.SecurityGroups()[infrav1.SecurityGroupAPIServerLB].ID) + scheme := infrav1.ELBSchemeInternetFacing + if controlPlaneLoadBalancer != nil && controlPlaneLoadBalancer.Scheme != nil { + scheme = *controlPlaneLoadBalancer.Scheme + } + res := &infrav1.LoadBalancer{ Name: elbName, - Scheme: s.scope.ControlPlaneLoadBalancerScheme(), + Scheme: scheme, ClassicELBListeners: []infrav1.ClassicELBListener{ { Protocol: infrav1.ELBProtocolTCP, @@ -1044,7 +1049,7 @@ func (s *Service) getAPIServerClassicELBSpec(elbName string) (*infrav1.LoadBalan // The load balancer APIs require us to only attach one subnet for each AZ. subnets := s.scope.Subnets().FilterPrivate() - if s.scope.ControlPlaneLoadBalancerScheme() == infrav1.ELBSchemeInternetFacing { + if scheme == infrav1.ELBSchemeInternetFacing { subnets = s.scope.Subnets().FilterPublic() } @@ -1248,27 +1253,28 @@ func (s *Service) listByTag(tag string) ([]string, error) { err := s.ResourceTaggingClient.GetResourcesPages(&input, func(r *rgapi.GetResourcesOutput, last bool) bool { for _, tagmapping := range r.ResourceTagMappingList { - if tagmapping.ResourceARN != nil { - parsedARN, err := arn.Parse(*tagmapping.ResourceARN) - if err != nil { - s.scope.Info("failed to parse ARN", "arn", *tagmapping.ResourceARN, "tag", tag) - continue - } - if strings.Contains(parsedARN.Resource, "loadbalancer/net/") { - s.scope.Info("ignoring nlb created by service, consider enabling garbage collection", "arn", *tagmapping.ResourceARN, "tag", tag) - continue - } - if strings.Contains(parsedARN.Resource, "loadbalancer/app/") { - s.scope.Info("ignoring alb created by service, consider enabling garbage collection", "arn", *tagmapping.ResourceARN, "tag", tag) - continue - } - name := strings.ReplaceAll(parsedARN.Resource, "loadbalancer/", "") - if name == "" { - s.scope.Info("failed to parse ARN", "arn", *tagmapping.ResourceARN, "tag", tag) - continue - } - names = append(names, name) + if tagmapping.ResourceARN == nil { + continue + } + parsedARN, err := arn.Parse(*tagmapping.ResourceARN) + if err != nil { + s.scope.Info("failed to parse ARN", "arn", *tagmapping.ResourceARN, "tag", tag) + continue + } + if strings.Contains(parsedARN.Resource, "loadbalancer/net/") { + s.scope.Info("ignoring nlb created by service, consider enabling garbage collection", "arn", *tagmapping.ResourceARN, "tag", tag) + continue + } + if strings.Contains(parsedARN.Resource, "loadbalancer/app/") { + s.scope.Info("ignoring alb created by service, consider enabling garbage collection", "arn", *tagmapping.ResourceARN, "tag", tag) + continue + } + name := strings.ReplaceAll(parsedARN.Resource, "loadbalancer/", "") + if name == "" { + s.scope.Info("failed to parse ARN", "arn", *tagmapping.ResourceARN, "tag", tag) + continue } + names = append(names, name) } return true }) @@ -1527,17 +1533,17 @@ func fromSDKTypeToClassicELB(v *elb.LoadBalancerDescription, attrs *elb.LoadBala } func fromSDKTypeToLB(v *elbv2.LoadBalancer, attrs []*elbv2.LoadBalancerAttribute, tags []*elbv2.Tag) *infrav1.LoadBalancer { - subnetIds := make([]*string, len(v.AvailabilityZones)) + subnetIDs := make([]*string, len(v.AvailabilityZones)) availabilityZones := make([]*string, len(v.AvailabilityZones)) for i, az := range v.AvailabilityZones { - subnetIds[i] = az.SubnetId + subnetIDs[i] = az.SubnetId availabilityZones[i] = az.ZoneName } res := &infrav1.LoadBalancer{ ARN: aws.StringValue(v.LoadBalancerArn), Name: aws.StringValue(v.LoadBalancerName), Scheme: infrav1.ELBScheme(aws.StringValue(v.Scheme)), - SubnetIDs: aws.StringValueSlice(subnetIds), + SubnetIDs: aws.StringValueSlice(subnetIDs), SecurityGroupIDs: aws.StringValueSlice(v.SecurityGroups), AvailabilityZones: aws.StringValueSlice(availabilityZones), DNSName: aws.StringValue(v.DNSName), diff --git a/pkg/cloud/services/elb/loadbalancer_test.go b/pkg/cloud/services/elb/loadbalancer_test.go index 4762edc251..593cbc3625 100644 --- a/pkg/cloud/services/elb/loadbalancer_test.go +++ b/pkg/cloud/services/elb/loadbalancer_test.go @@ -2180,7 +2180,7 @@ func TestReconcileLoadbalancers(t *testing.T) { } func TestDeleteAPIServerELB(t *testing.T) { - clusterName := "bar" //nolint:goconst // does not need to be a package-level const + clusterName := "bar" elbName := "bar-apiserver" tests := []struct { name string diff --git a/pkg/cloud/services/elb/service.go b/pkg/cloud/services/elb/service.go index b1b78ca358..c0717c6f25 100644 --- a/pkg/cloud/services/elb/service.go +++ b/pkg/cloud/services/elb/service.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package elb provides a service for managing AWS load balancers. package elb import ( diff --git a/pkg/cloud/services/gc/ec2.go b/pkg/cloud/services/gc/ec2.go index 817dbd78a1..823163dddc 100644 --- a/pkg/cloud/services/gc/ec2.go +++ b/pkg/cloud/services/gc/ec2.go @@ -72,7 +72,7 @@ func (s *Service) deleteSecurityGroup(ctx context.Context, securityGroupID strin } // getProviderOwnedSecurityGroups gets cloud provider created security groups of ELBs for this cluster, filtering by tag: kubernetes.io/cluster/:owned and VPC Id. -func (s *Service) getProviderOwnedSecurityGroups(ctx context.Context) ([]*AWSResource, error) { +func (s *Service) getProviderOwnedSecurityGroups(_ context.Context) ([]*AWSResource, error) { input := &ec2.DescribeSecurityGroupsInput{ Filters: []*ec2.Filter{ filter.EC2.ProviderOwned(s.scope.KubernetesClusterName()), diff --git a/pkg/cloud/services/gc/options.go b/pkg/cloud/services/gc/options.go index c2ebb49af7..445977bcd3 100644 --- a/pkg/cloud/services/gc/options.go +++ b/pkg/cloud/services/gc/options.go @@ -54,6 +54,7 @@ func withEC2Client(client ec2iface.EC2API) ServiceOption { } } +// WithGCStrategy is an option for specifying using the alternative GC strategy. func WithGCStrategy(alternativeGCStrategy bool) ServiceOption { if alternativeGCStrategy { return func(s *Service) { diff --git a/pkg/cloud/services/gc/service.go b/pkg/cloud/services/gc/service.go index 9eb9f789a6..27b48d653e 100644 --- a/pkg/cloud/services/gc/service.go +++ b/pkg/cloud/services/gc/service.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package gc provides a way to perform gc operations against a tenant/workload/child cluster. package gc import ( diff --git a/pkg/cloud/services/iamauth/mock_iamauth/doc.go b/pkg/cloud/services/iamauth/mock_iamauth/doc.go index 15669ccb8f..d33311cf0a 100644 --- a/pkg/cloud/services/iamauth/mock_iamauth/doc.go +++ b/pkg/cloud/services/iamauth/mock_iamauth/doc.go @@ -14,8 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package mock_iamauth provides a mock implementation for the IAMAPI interface. // Run go generate to regenerate this mock. +// //go:generate ../../../../../hack/tools/bin/mockgen -destination iamauth_mock.go -package mock_iamauth github.com/aws/aws-sdk-go/service/iam/iamiface IAMAPI //go:generate /usr/bin/env bash -c "cat ../../../../../hack/boilerplate/boilerplate.generatego.txt iamauth_mock.go > _iamauth_mock.go && mv _iamauth_mock.go iamauth_mock.go" - package mock_iamauth //nolint:stylecheck diff --git a/pkg/cloud/services/iamauth/service.go b/pkg/cloud/services/iamauth/service.go index 477e7c4928..27241b0c69 100644 --- a/pkg/cloud/services/iamauth/service.go +++ b/pkg/cloud/services/iamauth/service.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package iamauth provides a way to interact with AWS IAM. package iamauth import ( diff --git a/pkg/cloud/services/instancestate/mock_eventbridgeiface/doc.go b/pkg/cloud/services/instancestate/mock_eventbridgeiface/doc.go index 877fd9feb0..9d3af84e3b 100644 --- a/pkg/cloud/services/instancestate/mock_eventbridgeiface/doc.go +++ b/pkg/cloud/services/instancestate/mock_eventbridgeiface/doc.go @@ -18,4 +18,5 @@ limitations under the License. //go:generate ../../../../../hack/tools/bin/mockgen -destination eventbridgeiface_mock.go -package mock_eventbridgeiface github.com/aws/aws-sdk-go/service/eventbridge/eventbridgeiface EventBridgeAPI //go:generate /usr/bin/env bash -c "cat ../../../../../hack/boilerplate/boilerplate.generatego.txt eventbridgeiface_mock.go > _eventbridgeiface_mock.go && mv _eventbridgeiface_mock.go eventbridgeiface_mock.go" +// Package mock_eventbridgeiface provides a mock implementation for the EventBridgeAPI interface. package mock_eventbridgeiface //nolint:stylecheck diff --git a/pkg/cloud/services/instancestate/mock_sqsiface/doc.go b/pkg/cloud/services/instancestate/mock_sqsiface/doc.go index 356d813633..57fb6a9347 100644 --- a/pkg/cloud/services/instancestate/mock_sqsiface/doc.go +++ b/pkg/cloud/services/instancestate/mock_sqsiface/doc.go @@ -14,8 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package mock_sqsiface provides a mock implementation for the SQSAPI interface. // Run go generate to regenerate this mock. +// //go:generate ../../../../../hack/tools/bin/mockgen -destination sqsiface_mock.go -package mock_sqsiface github.com/aws/aws-sdk-go/service/sqs/sqsiface SQSAPI //go:generate /usr/bin/env bash -c "cat ../../../../../hack/boilerplate/boilerplate.generatego.txt sqsiface_mock.go > _sqsiface_mock.go && mv _sqsiface_mock.go sqsiface_mock.go" - package mock_sqsiface //nolint:stylecheck diff --git a/pkg/cloud/services/instancestate/service.go b/pkg/cloud/services/instancestate/service.go index 62ea5be2f1..b798967ffc 100644 --- a/pkg/cloud/services/instancestate/service.go +++ b/pkg/cloud/services/instancestate/service.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package instancestate provides a way to interact with the EC2 instance state. package instancestate import ( diff --git a/pkg/cloud/services/interfaces.go b/pkg/cloud/services/interfaces.go index 893c5ae278..ebee62a9a8 100644 --- a/pkg/cloud/services/interfaces.go +++ b/pkg/cloud/services/interfaces.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package services contains the interfaces for the AWS services. package services import ( diff --git a/pkg/cloud/services/kubeproxy/service.go b/pkg/cloud/services/kubeproxy/service.go index 16fbf38eed..17a4bd73af 100644 --- a/pkg/cloud/services/kubeproxy/service.go +++ b/pkg/cloud/services/kubeproxy/service.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package kubeproxy provides a way to interact with the kube-proxy service. package kubeproxy import ( diff --git a/pkg/cloud/services/mock_services/doc.go b/pkg/cloud/services/mock_services/doc.go index 04493e0002..9c5380ce19 100644 --- a/pkg/cloud/services/mock_services/doc.go +++ b/pkg/cloud/services/mock_services/doc.go @@ -14,7 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package mock_services provides a way to generate mock services for the cloud provider. // Run go generate to regenerate this mock. //nolint:revive +// //go:generate ../../../../hack/tools/bin/mockgen -destination ec2_interface_mock.go -package mock_services sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services EC2Interface //go:generate /usr/bin/env bash -c "cat ../../../../hack/boilerplate/boilerplate.generatego.txt ec2_interface_mock.go > _ec2_interface_mock.go && mv _ec2_interface_mock.go ec2_interface_mock.go" //go:generate ../../../../hack/tools/bin/mockgen -destination reconcile_interface_mock.go -package mock_services sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services MachinePoolReconcileInterface @@ -31,5 +33,4 @@ limitations under the License. //go:generate /usr/bin/env bash -c "cat ../../../../hack/boilerplate/boilerplate.generatego.txt network_interface_mock.go > _network_interface_mock.go && mv _network_interface_mock.go network_interface_mock.go" //go:generate ../../../../hack/tools/bin/mockgen -destination security_group_interface_mock.go -package mock_services sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services SecurityGroupInterface //go:generate /usr/bin/env bash -c "cat ../../../../hack/boilerplate/boilerplate.generatego.txt security_group_interface_mock.go > _security_group_interface_mock.go && mv _security_group_interface_mock.go security_group_interface_mock.go" - package mock_services //nolint:stylecheck diff --git a/pkg/cloud/services/network/natgateways.go b/pkg/cloud/services/network/natgateways.go index 8038b42290..807594f604 100644 --- a/pkg/cloud/services/network/natgateways.go +++ b/pkg/cloud/services/network/natgateways.go @@ -298,7 +298,7 @@ func (s *Service) deleteNatGateway(id string) error { } if out == nil || len(out.NatGateways) == 0 { - return false, errors.New(fmt.Sprintf("no NAT gateway returned for id %q", id)) + return false, fmt.Errorf("no NAT gateway returned for id %q", id) } ng := out.NatGateways[0] diff --git a/pkg/cloud/services/network/service.go b/pkg/cloud/services/network/service.go index 32f6d8131a..8c223c5e6d 100644 --- a/pkg/cloud/services/network/service.go +++ b/pkg/cloud/services/network/service.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package network provides a service to manage AWS network resources. package network import ( diff --git a/pkg/cloud/services/network/subnets.go b/pkg/cloud/services/network/subnets.go index c4b7a0c44f..65a70e7445 100644 --- a/pkg/cloud/services/network/subnets.go +++ b/pkg/cloud/services/network/subnets.go @@ -146,12 +146,12 @@ func (s *Service) reconcileSubnets() error { if !unmanagedVPC { record.Warnf(s.scope.InfraCluster(), "FailedTagSubnet", "Failed tagging managed Subnet %q: %v", existingSubnet.GetResourceID(), err) return errors.Wrapf(err, "failed to ensure tags on subnet %q", existingSubnet.GetResourceID()) - } else { - // We may not have a permission to tag unmanaged subnets. - // When tagging unmanaged subnet fails, record an event and proceed. - record.Warnf(s.scope.InfraCluster(), "FailedTagSubnet", "Failed tagging unmanaged Subnet %q: %v", existingSubnet.GetResourceID(), err) - break } + + // We may not have a permission to tag unmanaged subnets. + // When tagging unmanaged subnet fails, record an event and proceed. + record.Warnf(s.scope.InfraCluster(), "FailedTagSubnet", "Failed tagging unmanaged Subnet %q: %v", existingSubnet.GetResourceID(), err) + break } // TODO(vincepri): check if subnet needs to be updated. @@ -590,10 +590,10 @@ func (s *Service) getSubnetTagParams(unmanagedVPC bool, id string, public bool, Role: aws.String(role), Additional: additionalTags, } - } else { - return infrav1.BuildParams{ - ResourceID: id, - Additional: additionalTags, - } + } + + return infrav1.BuildParams{ + ResourceID: id, + Additional: additionalTags, } } diff --git a/pkg/cloud/services/network/subnets_test.go b/pkg/cloud/services/network/subnets_test.go index f7c02d4359..840583a37c 100644 --- a/pkg/cloud/services/network/subnets_test.go +++ b/pkg/cloud/services/network/subnets_test.go @@ -2677,7 +2677,7 @@ func TestDeleteSubnets(t *testing.T) { } } -// Test helpers +// Test helpers. type ScopeBuilder interface { Build() (scope.NetworkScope, error) diff --git a/pkg/cloud/services/network/vpc_test.go b/pkg/cloud/services/network/vpc_test.go index a48bec80ca..403707b8ec 100644 --- a/pkg/cloud/services/network/vpc_test.go +++ b/pkg/cloud/services/network/vpc_test.go @@ -38,7 +38,7 @@ import ( clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" ) -func describeVpcAttributeTrue(ctx context.Context, input *ec2.DescribeVpcAttributeInput, requestOptions ...request.Option) (*ec2.DescribeVpcAttributeOutput, error) { +func describeVpcAttributeTrue(_ context.Context, input *ec2.DescribeVpcAttributeInput, _ ...request.Option) (*ec2.DescribeVpcAttributeOutput, error) { result := &ec2.DescribeVpcAttributeOutput{ VpcId: input.VpcId, } @@ -51,7 +51,7 @@ func describeVpcAttributeTrue(ctx context.Context, input *ec2.DescribeVpcAttribu return result, nil } -func describeVpcAttributeFalse(ctx context.Context, input *ec2.DescribeVpcAttributeInput, requestOptions ...request.Option) (*ec2.DescribeVpcAttributeOutput, error) { +func describeVpcAttributeFalse(_ context.Context, input *ec2.DescribeVpcAttributeInput, _ ...request.Option) (*ec2.DescribeVpcAttributeOutput, error) { result := &ec2.DescribeVpcAttributeOutput{ VpcId: input.VpcId, } @@ -573,9 +573,8 @@ func TestReconcileVPC(t *testing.T) { g.Expect(err).ToNot(BeNil()) g.Expect(err.Error()).To(ContainSubstring(*tc.wantErrContaining)) return - } else { - g.Expect(err).To(BeNil()) } + g.Expect(err).To(BeNil()) g.Expect(tc.want).To(Equal(&clusterScope.AWSCluster.Spec.NetworkSpec.VPC)) }) } diff --git a/pkg/cloud/services/s3/mock_s3iface/doc.go b/pkg/cloud/services/s3/mock_s3iface/doc.go index d507db6d37..4b8b857f37 100644 --- a/pkg/cloud/services/s3/mock_s3iface/doc.go +++ b/pkg/cloud/services/s3/mock_s3iface/doc.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package mock_s3iface provides a mock implementation of the s3iface.S3API interface // Run go generate to regenerate this mock. // //go:generate ../../../../../hack/tools/bin/mockgen -destination s3api_mock.go -package mock_s3iface github.com/aws/aws-sdk-go/service/s3/s3iface S3API diff --git a/pkg/cloud/services/s3/mock_stsiface/doc.go b/pkg/cloud/services/s3/mock_stsiface/doc.go index 82065f4ad7..429a95b586 100644 --- a/pkg/cloud/services/s3/mock_stsiface/doc.go +++ b/pkg/cloud/services/s3/mock_stsiface/doc.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package mock_stsiface provides a mock implementation for the STSAPI interface. // Run go generate to regenerate this mock. // //go:generate ../../../../../hack/tools/bin/mockgen -destination stsapi_mock.go -package mock_stsiface github.com/aws/aws-sdk-go/service/sts/stsiface STSAPI diff --git a/pkg/cloud/services/s3/s3.go b/pkg/cloud/services/s3/s3.go index a6bbf26b86..b6695fd006 100644 --- a/pkg/cloud/services/s3/s3.go +++ b/pkg/cloud/services/s3/s3.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package s3 provides a way to interact with AWS S3. package s3 import ( @@ -38,6 +39,9 @@ import ( "sigs.k8s.io/cluster-api-provider-aws/v2/util/system" ) +// AWSDefaultRegion is the default AWS region. +const AWSDefaultRegion string = "us-east-1" + // Service holds a collection of interfaces. // The interfaces are broken down like this to group functions together. // One alternative is to have a large list of functions from the ec2 client. @@ -59,6 +63,7 @@ func NewService(s3Scope scope.S3Scope) *Service { } } +// ReconcileBucket reconciles the S3 bucket. func (s *Service) ReconcileBucket() error { if !s.bucketManagementEnabled() { return nil @@ -81,6 +86,7 @@ func (s *Service) ReconcileBucket() error { return nil } +// DeleteBucket deletes the S3 bucket. func (s *Service) DeleteBucket() error { if !s.bucketManagementEnabled() { return nil @@ -116,6 +122,7 @@ func (s *Service) DeleteBucket() error { return nil } +// Create creates an object in the S3 bucket. func (s *Service) Create(m *scope.MachineScope, data []byte) (string, error) { if !s.bucketManagementEnabled() { return "", errors.New("requested object creation but bucket management is not enabled") @@ -161,6 +168,7 @@ func (s *Service) Create(m *scope.MachineScope, data []byte) (string, error) { return objectURL.String(), nil } +// Delete deletes the object from the S3 bucket. func (s *Service) Delete(m *scope.MachineScope) error { if !s.bucketManagementEnabled() { return errors.New("requested object creation but bucket management is not enabled") @@ -223,11 +231,13 @@ func (s *Service) Delete(m *scope.MachineScope) error { } func (s *Service) createBucketIfNotExist(bucketName string) error { - input := &s3.CreateBucketInput{ - Bucket: aws.String(bucketName), - CreateBucketConfiguration: &s3.CreateBucketConfiguration{ + input := &s3.CreateBucketInput{Bucket: aws.String(bucketName)} + + // See https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateBucket.html#AmazonS3-CreateBucket-request-LocationConstraint. + if s.scope.Region() != AWSDefaultRegion { + input.CreateBucketConfiguration = &s3.CreateBucketConfiguration{ LocationConstraint: aws.String(s.scope.Region()), - }, + } } _, err := s.S3Client.CreateBucket(input) diff --git a/pkg/cloud/services/s3/s3_test.go b/pkg/cloud/services/s3/s3_test.go index 7ce58d19d0..baa44ff875 100644 --- a/pkg/cloud/services/s3/s3_test.go +++ b/pkg/cloud/services/s3/s3_test.go @@ -66,8 +66,10 @@ func TestReconcileBucket(t *testing.T) { expectedBucketName := "baz" - svc, s3Mock := testService(t, &infrav1.S3Bucket{ - Name: expectedBucketName, + svc, s3Mock := testService(t, &testServiceInput{ + Bucket: &infrav1.S3Bucket{ + Name: expectedBucketName, + }, }) input := &s3svc.CreateBucketInput{ @@ -168,11 +170,13 @@ func TestReconcileBucket(t *testing.T) { bucketName := "bar" - svc, s3Mock := testService(t, &infrav1.S3Bucket{ - Name: bucketName, - ControlPlaneIAMInstanceProfile: fmt.Sprintf("control-plane%s", iamv1.DefaultNameSuffix), - NodesIAMInstanceProfiles: []string{ - fmt.Sprintf("nodes%s", iamv1.DefaultNameSuffix), + svc, s3Mock := testService(t, &testServiceInput{ + Bucket: &infrav1.S3Bucket{ + Name: bucketName, + ControlPlaneIAMInstanceProfile: fmt.Sprintf("control-plane%s", iamv1.DefaultNameSuffix), + NodesIAMInstanceProfiles: []string{ + fmt.Sprintf("nodes%s", iamv1.DefaultNameSuffix), + }, }, }) @@ -218,7 +222,7 @@ func TestReconcileBucket(t *testing.T) { t.Run("is_idempotent", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{}) + svc, s3Mock := testService(t, &testServiceInput{Bucket: &infrav1.S3Bucket{}}) s3Mock.EXPECT().CreateBucket(gomock.Any()).Return(nil, nil).Times(2) s3Mock.EXPECT().PutBucketTagging(gomock.Any()).Return(nil, nil).Times(2) @@ -236,7 +240,7 @@ func TestReconcileBucket(t *testing.T) { t.Run("ignores_when_bucket_already_exists_but_its_owned_by_the_same_account", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{}) + svc, s3Mock := testService(t, &testServiceInput{Bucket: &infrav1.S3Bucket{}}) err := awserr.New(s3svc.ErrCodeBucketAlreadyOwnedByYou, "err", errors.New("err")) @@ -255,7 +259,7 @@ func TestReconcileBucket(t *testing.T) { t.Run("bucket_creation_fails", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{}) + svc, s3Mock := testService(t, &testServiceInput{Bucket: &infrav1.S3Bucket{}}) s3Mock.EXPECT().CreateBucket(gomock.Any()).Return(nil, errors.New("error")).Times(1) @@ -267,7 +271,7 @@ func TestReconcileBucket(t *testing.T) { t.Run("bucket_creation_returns_unexpected_AWS_error", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{}) + svc, s3Mock := testService(t, &testServiceInput{Bucket: &infrav1.S3Bucket{}}) s3Mock.EXPECT().CreateBucket(gomock.Any()).Return(nil, awserr.New("foo", "", nil)).Times(1) @@ -279,14 +283,14 @@ func TestReconcileBucket(t *testing.T) { t.Run("generating_bucket_policy_fails", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{}) + svc, s3Mock := testService(t, &testServiceInput{Bucket: &infrav1.S3Bucket{}}) s3Mock.EXPECT().CreateBucket(gomock.Any()).Return(nil, nil).Times(1) s3Mock.EXPECT().PutBucketTagging(gomock.Any()).Return(nil, nil).Times(1) mockCtrl := gomock.NewController(t) stsMock := mock_stsiface.NewMockSTSAPI(mockCtrl) - stsMock.EXPECT().GetCallerIdentity(gomock.Any()).Return(nil, fmt.Errorf(t.Name())).AnyTimes() + stsMock.EXPECT().GetCallerIdentity(gomock.Any()).Return(nil, errors.New(t.Name())).AnyTimes() svc.STSClient = stsMock if err := svc.ReconcileBucket(); err == nil { @@ -297,7 +301,7 @@ func TestReconcileBucket(t *testing.T) { t.Run("creating_bucket_policy_fails", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{}) + svc, s3Mock := testService(t, &testServiceInput{Bucket: &infrav1.S3Bucket{}}) s3Mock.EXPECT().CreateBucket(gomock.Any()).Return(nil, nil).Times(1) s3Mock.EXPECT().PutBucketTagging(gomock.Any()).Return(nil, nil).Times(1) @@ -307,6 +311,27 @@ func TestReconcileBucket(t *testing.T) { t.Fatalf("Expected error") } }) + + t.Run("creates_bucket_without_location", func(t *testing.T) { + t.Parallel() + + bucketName := "test" + svc, s3Mock := testService(t, &testServiceInput{ + Region: "us-east-1", + Bucket: &infrav1.S3Bucket{Name: bucketName}, + }) + input := &s3svc.CreateBucketInput{ + Bucket: aws.String(bucketName), + } + + s3Mock.EXPECT().CreateBucket(gomock.Eq(input)).Return(nil, nil).Times(1) + s3Mock.EXPECT().PutBucketTagging(gomock.Any()).Return(nil, nil).Times(1) + s3Mock.EXPECT().PutBucketPolicy(gomock.Any()).Return(nil, nil).Times(1) + + if err := svc.ReconcileBucket(); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + }) }) } @@ -328,8 +353,10 @@ func TestDeleteBucket(t *testing.T) { t.Run("deletes_bucket_with_configured_name", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{ - Name: bucketName, + svc, s3Mock := testService(t, &testServiceInput{ + Bucket: &infrav1.S3Bucket{ + Name: bucketName, + }, }) input := &s3svc.DeleteBucketInput{ @@ -348,7 +375,7 @@ func TestDeleteBucket(t *testing.T) { t.Run("unexpected_error", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{}) + svc, s3Mock := testService(t, &testServiceInput{Bucket: &infrav1.S3Bucket{}}) s3Mock.EXPECT().DeleteBucket(gomock.Any()).Return(nil, errors.New("err")).Times(1) @@ -360,7 +387,7 @@ func TestDeleteBucket(t *testing.T) { t.Run("unexpected_AWS_error", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{}) + svc, s3Mock := testService(t, &testServiceInput{Bucket: &infrav1.S3Bucket{}}) s3Mock.EXPECT().DeleteBucket(gomock.Any()).Return(nil, awserr.New("foo", "", nil)).Times(1) @@ -373,7 +400,7 @@ func TestDeleteBucket(t *testing.T) { t.Run("ignores_when_bucket_has_already_been_removed", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{}) + svc, s3Mock := testService(t, &testServiceInput{Bucket: &infrav1.S3Bucket{}}) s3Mock.EXPECT().DeleteBucket(gomock.Any()).Return(nil, awserr.New(s3svc.ErrCodeNoSuchBucket, "", nil)).Times(1) @@ -385,7 +412,7 @@ func TestDeleteBucket(t *testing.T) { t.Run("skips_bucket_removal_when_bucket_is_not_empty", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{}) + svc, s3Mock := testService(t, &testServiceInput{Bucket: &infrav1.S3Bucket{}}) s3Mock.EXPECT().DeleteBucket(gomock.Any()).Return(nil, awserr.New("BucketNotEmpty", "", nil)).Times(1) @@ -406,8 +433,10 @@ func TestCreateObject(t *testing.T) { t.Run("for_machine", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{ - Name: bucketName, + svc, s3Mock := testService(t, &testServiceInput{ + Bucket: &infrav1.S3Bucket{ + Name: bucketName, + }, }) machineScope := &scope.MachineScope{ @@ -487,7 +516,7 @@ func TestCreateObject(t *testing.T) { t.Run("is_idempotent", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{}) + svc, s3Mock := testService(t, &testServiceInput{Bucket: &infrav1.S3Bucket{}}) machineScope := &scope.MachineScope{ Machine: &clusterv1.Machine{}, @@ -516,7 +545,7 @@ func TestCreateObject(t *testing.T) { t.Run("object_creation_fails", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{}) + svc, s3Mock := testService(t, &testServiceInput{Bucket: &infrav1.S3Bucket{}}) machineScope := &scope.MachineScope{ Machine: &clusterv1.Machine{}, @@ -542,7 +571,7 @@ func TestCreateObject(t *testing.T) { t.Run("given_empty_machine_scope", func(t *testing.T) { t.Parallel() - svc, _ := testService(t, &infrav1.S3Bucket{}) + svc, _ := testService(t, &testServiceInput{Bucket: &infrav1.S3Bucket{}}) bootstrapDataURL, err := svc.Create(nil, []byte("foo")) if err == nil { @@ -558,7 +587,7 @@ func TestCreateObject(t *testing.T) { t.Run("given_empty_bootstrap_data", func(t *testing.T) { t.Parallel() - svc, _ := testService(t, &infrav1.S3Bucket{}) + svc, _ := testService(t, &testServiceInput{Bucket: &infrav1.S3Bucket{}}) machineScope := &scope.MachineScope{ Machine: &clusterv1.Machine{}, @@ -615,8 +644,10 @@ func TestDeleteObject(t *testing.T) { expectedBucketName := "foo" - svc, s3Mock := testService(t, &infrav1.S3Bucket{ - Name: expectedBucketName, + svc, s3Mock := testService(t, &testServiceInput{ + Bucket: &infrav1.S3Bucket{ + Name: expectedBucketName, + }, }) machineScope := &scope.MachineScope{ @@ -666,7 +697,7 @@ func TestDeleteObject(t *testing.T) { t.Run("succeeds_when_bucket_has_already_been_removed", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{}) + svc, s3Mock := testService(t, &testServiceInput{Bucket: &infrav1.S3Bucket{}}) machineScope := &scope.MachineScope{ Machine: &clusterv1.Machine{}, @@ -690,7 +721,7 @@ func TestDeleteObject(t *testing.T) { t.Run("object_deletion_fails", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{}) + svc, s3Mock := testService(t, &testServiceInput{Bucket: &infrav1.S3Bucket{}}) machineScope := &scope.MachineScope{ Machine: &clusterv1.Machine{}, @@ -712,7 +743,7 @@ func TestDeleteObject(t *testing.T) { t.Run("given_empty_machine_scope", func(t *testing.T) { t.Parallel() - svc, _ := testService(t, &infrav1.S3Bucket{}) + svc, _ := testService(t, nil) if err := svc.Delete(nil); err == nil { t.Fatalf("Expected error") @@ -742,7 +773,7 @@ func TestDeleteObject(t *testing.T) { t.Run("is_idempotent", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{}) + svc, s3Mock := testService(t, &testServiceInput{Bucket: &infrav1.S3Bucket{}}) machineScope := &scope.MachineScope{ Machine: &clusterv1.Machine{}, @@ -766,7 +797,14 @@ func TestDeleteObject(t *testing.T) { }) } -func testService(t *testing.T, bucket *infrav1.S3Bucket) (*s3.Service, *mock_s3iface.MockS3API) { +type testServiceInput struct { + Bucket *infrav1.S3Bucket + Region string +} + +const testAWSRegion string = "us-west-2" + +func testService(t *testing.T, si *testServiceInput) (*s3.Service, *mock_s3iface.MockS3API) { t.Helper() mockCtrl := gomock.NewController(t) @@ -780,6 +818,13 @@ func testService(t *testing.T, bucket *infrav1.S3Bucket) (*s3.Service, *mock_s3i _ = infrav1.AddToScheme(scheme) client := fake.NewClientBuilder().WithScheme(scheme).Build() + if si == nil { + si = &testServiceInput{} + } + if si.Region == "" { + si.Region = testAWSRegion + } + scope, err := scope.NewClusterScope(scope.ClusterScopeParams{ Client: client, Cluster: &clusterv1.Cluster{ @@ -790,8 +835,8 @@ func testService(t *testing.T, bucket *infrav1.S3Bucket) (*s3.Service, *mock_s3i }, AWSCluster: &infrav1.AWSCluster{ Spec: infrav1.AWSClusterSpec{ - S3Bucket: bucket, - Region: "us-west-2", + S3Bucket: si.Bucket, + Region: si.Region, AdditionalTags: infrav1.Tags{ "additional": "from-aws-cluster", }, diff --git a/pkg/cloud/services/secretsmanager/mock_secretsmanageriface/doc.go b/pkg/cloud/services/secretsmanager/mock_secretsmanageriface/doc.go index 6f9493872e..88f2878984 100644 --- a/pkg/cloud/services/secretsmanager/mock_secretsmanageriface/doc.go +++ b/pkg/cloud/services/secretsmanager/mock_secretsmanageriface/doc.go @@ -14,8 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package mock_secretsmanageriface provides a mock interface for the SecretsManager API client. // Run go generate to regenerate this mock. +// //go:generate ../../../../../hack/tools/bin/mockgen -destination secretsmanagerapi_mock.go -package mock_secretsmanageriface github.com/aws/aws-sdk-go/service/secretsmanager/secretsmanageriface SecretsManagerAPI //go:generate /usr/bin/env bash -c "cat ../../../../../hack/boilerplate/boilerplate.generatego.txt secretsmanagerapi_mock.go > _secretsmanagerapi_mock.go && mv _secretsmanagerapi_mock.go secretsmanagerapi_mock.go" - package mock_secretsmanageriface //nolint:stylecheck diff --git a/pkg/cloud/services/secretsmanager/service.go b/pkg/cloud/services/secretsmanager/service.go index 02e844919d..c9a06510f6 100644 --- a/pkg/cloud/services/secretsmanager/service.go +++ b/pkg/cloud/services/secretsmanager/service.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package secretsmanager provides a way to interact with AWS Secrets Manager. package secretsmanager import ( diff --git a/pkg/cloud/services/securitygroup/securitygroups.go b/pkg/cloud/services/securitygroup/securitygroups.go index 1a3c9440e1..b6e9bcabc4 100644 --- a/pkg/cloud/services/securitygroup/securitygroups.go +++ b/pkg/cloud/services/securitygroup/securitygroups.go @@ -207,7 +207,7 @@ func (s *Service) securityGroupIsAnOverride(securityGroupID string) bool { } func (s *Service) describeSecurityGroupOverridesByID() (map[infrav1.SecurityGroupRole]*ec2.SecurityGroup, error) { - securityGroupIds := map[infrav1.SecurityGroupRole]*string{} + securityGroupIDs := map[infrav1.SecurityGroupRole]*string{} input := &ec2.DescribeSecurityGroupsInput{} overrides := s.scope.SecurityGroupOverrides() @@ -221,7 +221,7 @@ func (s *Service) describeSecurityGroupOverridesByID() (map[infrav1.SecurityGrou for _, role := range s.roles { securityGroupID, ok := s.scope.SecurityGroupOverrides()[role] if ok { - securityGroupIds[role] = aws.String(securityGroupID) + securityGroupIDs[role] = aws.String(securityGroupID) input.GroupIds = append(input.GroupIds, aws.String(securityGroupID)) } } @@ -235,10 +235,10 @@ func (s *Service) describeSecurityGroupOverridesByID() (map[infrav1.SecurityGrou res := make(map[infrav1.SecurityGroupRole]*ec2.SecurityGroup, len(out.SecurityGroups)) for _, role := range s.roles { for _, ec2sg := range out.SecurityGroups { - if securityGroupIds[role] == nil { + if securityGroupIDs[role] == nil { continue } - if *ec2sg.GroupId == *securityGroupIds[role] { + if *ec2sg.GroupId == *securityGroupIDs[role] { s.scope.Debug("found security group override", "role", role, "security group", *ec2sg.GroupName) res[role] = ec2sg @@ -285,7 +285,7 @@ func (s *Service) DeleteSecurityGroups() error { for i := range clusterGroups { sg := clusterGroups[i] current := sg.IngressRules - if err := s.revokeAllSecurityGroupIngressRules(sg.ID); awserrors.IsIgnorableSecurityGroupError(err) != nil { + if err := s.revokeAllSecurityGroupIngressRules(sg.ID); awserrors.IsIgnorableSecurityGroupError(err) != nil { //nolint:gocritic conditions.MarkFalse(s.scope.InfraCluster(), infrav1.ClusterSecurityGroupsReadyCondition, "DeletingFailed", clusterv1.ConditionSeverityWarning, err.Error()) return err } @@ -311,7 +311,7 @@ func (s *Service) deleteSecurityGroup(sg *infrav1.SecurityGroup, typ string) err GroupId: aws.String(sg.ID), } - if _, err := s.EC2Client.DeleteSecurityGroupWithContext(context.TODO(), input); awserrors.IsIgnorableSecurityGroupError(err) != nil { + if _, err := s.EC2Client.DeleteSecurityGroupWithContext(context.TODO(), input); awserrors.IsIgnorableSecurityGroupError(err) != nil { //nolint:gocritic record.Warnf(s.scope.InfraCluster(), "FailedDeleteSecurityGroup", "Failed to delete %s SecurityGroup %q with name %q: %v", typ, sg.ID, sg.Name, err) return errors.Wrapf(err, "failed to delete security group %q with name %q", sg.ID, sg.Name) } diff --git a/pkg/cloud/services/securitygroup/securitygroups_test.go b/pkg/cloud/services/securitygroup/securitygroups_test.go index 50fe7007dc..31466058d8 100644 --- a/pkg/cloud/services/securitygroup/securitygroups_test.go +++ b/pkg/cloud/services/securitygroup/securitygroups_test.go @@ -1050,24 +1050,25 @@ func TestAdditionalControlPlaneSecurityGroup(t *testing.T) { found := false for _, r := range rules { - if r.Description == "test" { - found = true + if r.Description != "test" { + continue + } + found = true - if r.Protocol != tc.expectedAdditionalIngresRule.Protocol { - t.Fatalf("Expected protocol %s, got %s", tc.expectedAdditionalIngresRule.Protocol, r.Protocol) - } + if r.Protocol != tc.expectedAdditionalIngresRule.Protocol { + t.Fatalf("Expected protocol %s, got %s", tc.expectedAdditionalIngresRule.Protocol, r.Protocol) + } - if r.FromPort != tc.expectedAdditionalIngresRule.FromPort { - t.Fatalf("Expected from port %d, got %d", tc.expectedAdditionalIngresRule.FromPort, r.FromPort) - } + if r.FromPort != tc.expectedAdditionalIngresRule.FromPort { + t.Fatalf("Expected from port %d, got %d", tc.expectedAdditionalIngresRule.FromPort, r.FromPort) + } - if r.ToPort != tc.expectedAdditionalIngresRule.ToPort { - t.Fatalf("Expected to port %d, got %d", tc.expectedAdditionalIngresRule.ToPort, r.ToPort) - } + if r.ToPort != tc.expectedAdditionalIngresRule.ToPort { + t.Fatalf("Expected to port %d, got %d", tc.expectedAdditionalIngresRule.ToPort, r.ToPort) + } - if !sets.New[string](tc.expectedAdditionalIngresRule.SourceSecurityGroupIDs...).Equal(sets.New[string](tc.expectedAdditionalIngresRule.SourceSecurityGroupIDs...)) { - t.Fatalf("Expected source security group IDs %v, got %v", tc.expectedAdditionalIngresRule.SourceSecurityGroupIDs, r.SourceSecurityGroupIDs) - } + if !sets.New[string](tc.expectedAdditionalIngresRule.SourceSecurityGroupIDs...).Equal(sets.New[string](tc.expectedAdditionalIngresRule.SourceSecurityGroupIDs...)) { + t.Fatalf("Expected source security group IDs %v, got %v", tc.expectedAdditionalIngresRule.SourceSecurityGroupIDs, r.SourceSecurityGroupIDs) } } diff --git a/pkg/cloud/services/securitygroup/service.go b/pkg/cloud/services/securitygroup/service.go index 68c82d0752..63231ea260 100644 --- a/pkg/cloud/services/securitygroup/service.go +++ b/pkg/cloud/services/securitygroup/service.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package securitygroup provides a service to manage AWS security group resources. package securitygroup import ( diff --git a/pkg/cloud/services/ssm/cloudinit.go b/pkg/cloud/services/ssm/cloudinit.go index f507d1a1fb..4159238fba 100644 --- a/pkg/cloud/services/ssm/cloudinit.go +++ b/pkg/cloud/services/ssm/cloudinit.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package ssm provides a service to generate userdata for AWS Systems Manager. package ssm import ( diff --git a/pkg/cloud/services/ssm/mock_ssmiface/doc.go b/pkg/cloud/services/ssm/mock_ssmiface/doc.go index e71c785bf9..8188fc99d5 100644 --- a/pkg/cloud/services/ssm/mock_ssmiface/doc.go +++ b/pkg/cloud/services/ssm/mock_ssmiface/doc.go @@ -14,8 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package mock_ssmiface provides a mock interface for the SSM API client. // Run go generate to regenerate this mock. +// //go:generate ../../../../../hack/tools/bin/mockgen -destination ssmapi_mock.go -package mock_ssmiface github.com/aws/aws-sdk-go/service/ssm/ssmiface SSMAPI //go:generate /usr/bin/env bash -c "cat ../../../../../hack/boilerplate/boilerplate.generatego.txt ssmapi_mock.go > _ssmapi_mock.go && mv _ssmapi_mock.go ssmapi_mock.go" - package mock_ssmiface //nolint:stylecheck diff --git a/pkg/cloud/services/sts/mock_stsiface/doc.go b/pkg/cloud/services/sts/mock_stsiface/doc.go index 900464f08d..1c576fa536 100644 --- a/pkg/cloud/services/sts/mock_stsiface/doc.go +++ b/pkg/cloud/services/sts/mock_stsiface/doc.go @@ -14,8 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package mock_stsiface provides a mock implementation for the STSAPI interface. // Run go generate to regenerate this mock. +// //go:generate ../../../../../hack/tools/bin/mockgen -destination stsiface_mock.go -package mock_stsiface github.com/aws/aws-sdk-go/service/sts/stsiface STSAPI //go:generate /usr/bin/env bash -c "cat ../../../../../hack/boilerplate/boilerplate.generatego.txt stsiface_mock.go > _stsiface_mock.go && mv _stsiface_mock.go stsiface_mock.go" - package mock_stsiface //nolint:stylecheck diff --git a/pkg/cloud/services/userdata/userdata.go b/pkg/cloud/services/userdata/userdata.go index 6b565dfbc3..f7953b6b09 100644 --- a/pkg/cloud/services/userdata/userdata.go +++ b/pkg/cloud/services/userdata/userdata.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package userdata provides a way to generate user data for cloud instances. package userdata import ( diff --git a/pkg/cloud/services/wait/wait.go b/pkg/cloud/services/wait/wait.go index f9b9bf7a27..b725fa6b14 100644 --- a/pkg/cloud/services/wait/wait.go +++ b/pkg/cloud/services/wait/wait.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package wait provides a set of utilities for polling and waiting. package wait import ( diff --git a/pkg/cloud/tags/tags.go b/pkg/cloud/tags/tags.go index 7f97616c5b..42c8bfd843 100644 --- a/pkg/cloud/tags/tags.go +++ b/pkg/cloud/tags/tags.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package tags provides a way to tag cloud resources. package tags import ( diff --git a/pkg/cloud/throttle/throttle.go b/pkg/cloud/throttle/throttle.go index c0e2321997..77511952b7 100644 --- a/pkg/cloud/throttle/throttle.go +++ b/pkg/cloud/throttle/throttle.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package throttle provides a way to limit the number of requests to AWS services. package throttle import ( @@ -60,7 +61,7 @@ func (o *OperationLimiter) Match(r *request.Request) (bool, error) { return false, err } } - return o.regexp.Match([]byte(r.Operation.Name)), nil + return o.regexp.MatchString(r.Operation.Name), nil } // LimitRequest will limit a request. diff --git a/pkg/cloudtest/cloudtest.go b/pkg/cloudtest/cloudtest.go index 482fd54f5f..3264405784 100644 --- a/pkg/cloudtest/cloudtest.go +++ b/pkg/cloudtest/cloudtest.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package cloudtest provides utilities for testing. package cloudtest import ( @@ -42,23 +43,24 @@ func RuntimeRawExtension(t *testing.T, p interface{}) *runtime.RawExtension { // test log messages. type Log struct{} -func (l *Log) Init(info logr.RuntimeInfo) { +// Init initializes the logger. +func (l *Log) Init(_ logr.RuntimeInfo) { } // Error implements Log errors. -func (l *Log) Error(err error, msg string, keysAndValues ...interface{}) {} +func (l *Log) Error(_ error, _ string, _ ...interface{}) {} // V returns the Logger's log level. -func (l *Log) V(level int) logr.LogSink { return l } +func (l *Log) V(_ int) logr.LogSink { return l } // WithValues returns logs with specific values. -func (l *Log) WithValues(keysAndValues ...interface{}) logr.LogSink { return l } +func (l *Log) WithValues(_ ...interface{}) logr.LogSink { return l } // WithName returns the logger with a specific name. -func (l *Log) WithName(name string) logr.LogSink { return l } +func (l *Log) WithName(_ string) logr.LogSink { return l } // Info implements info messages for the logger. -func (l *Log) Info(level int, msg string, keysAndValues ...interface{}) {} +func (l *Log) Info(_ int, _ string, _ ...interface{}) {} // Enabled returns the state of the logger. -func (l *Log) Enabled(level int) bool { return false } +func (l *Log) Enabled(_ int) bool { return false } diff --git a/pkg/eks/addons/plan.go b/pkg/eks/addons/plan.go index ae4425dc1f..22d46e2ab8 100644 --- a/pkg/eks/addons/plan.go +++ b/pkg/eks/addons/plan.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package addons provides a plan to manage EKS addons. package addons import ( @@ -45,7 +46,7 @@ type plan struct { } // Create will create the plan (i.e. list of procedures) for managing EKS addons. -func (a *plan) Create(ctx context.Context) ([]planner.Procedure, error) { +func (a *plan) Create(_ context.Context) ([]planner.Procedure, error) { procedures := []planner.Procedure{} // Handle create and update @@ -54,8 +55,10 @@ func (a *plan) Create(ctx context.Context) ([]planner.Procedure, error) { installed := a.getInstalled(*desired.Name) if installed == nil { // Need to add the addon - procedures = append(procedures, &CreateAddonProcedure{plan: a, name: *desired.Name}) - procedures = append(procedures, &WaitAddonActiveProcedure{plan: a, name: *desired.Name, includeDegraded: true}) + procedures = append(procedures, + &CreateAddonProcedure{plan: a, name: *desired.Name}, + &WaitAddonActiveProcedure{plan: a, name: *desired.Name, includeDegraded: true}, + ) } else { // Check if its just the tags that need updating diffTags := desired.Tags.Difference(installed.Tags) @@ -64,8 +67,10 @@ func (a *plan) Create(ctx context.Context) ([]planner.Procedure, error) { } // Check if we also need to update the addon if !desired.IsEqual(installed, false) { - procedures = append(procedures, &UpdateAddonProcedure{plan: a, name: *installed.Name}) - procedures = append(procedures, &WaitAddonActiveProcedure{plan: a, name: *desired.Name, includeDegraded: true}) + procedures = append(procedures, + &UpdateAddonProcedure{plan: a, name: *installed.Name}, + &WaitAddonActiveProcedure{plan: a, name: *desired.Name, includeDegraded: true}, + ) } else if *installed.Status != eks.AddonStatusActive { // If the desired and installed are the same make sure its active procedures = append(procedures, &WaitAddonActiveProcedure{plan: a, name: *desired.Name, includeDegraded: true}) diff --git a/pkg/eks/addons/procedures.go b/pkg/eks/addons/procedures.go index a57435f014..82f24f56ac 100644 --- a/pkg/eks/addons/procedures.go +++ b/pkg/eks/addons/procedures.go @@ -43,7 +43,7 @@ type DeleteAddonProcedure struct { } // Do implements the logic for the procedure. -func (p *DeleteAddonProcedure) Do(ctx context.Context) error { +func (p *DeleteAddonProcedure) Do(_ context.Context) error { input := &eks.DeleteAddonInput{ AddonName: aws.String(p.name), ClusterName: aws.String(p.plan.clusterName), @@ -68,7 +68,7 @@ type UpdateAddonProcedure struct { } // Do implements the logic for the procedure. -func (p *UpdateAddonProcedure) Do(ctx context.Context) error { +func (p *UpdateAddonProcedure) Do(_ context.Context) error { desired := p.plan.getDesired(p.name) if desired == nil { @@ -103,7 +103,7 @@ type UpdateAddonTagsProcedure struct { } // Do implements the logic for the procedure. -func (p *UpdateAddonTagsProcedure) Do(ctx context.Context) error { +func (p *UpdateAddonTagsProcedure) Do(_ context.Context) error { desired := p.plan.getDesired(p.name) installed := p.plan.getInstalled(p.name) @@ -138,7 +138,7 @@ type CreateAddonProcedure struct { } // Do implements the logic for the procedure. -func (p *CreateAddonProcedure) Do(ctx context.Context) error { +func (p *CreateAddonProcedure) Do(_ context.Context) error { desired := p.plan.getDesired(p.name) if desired == nil { return fmt.Errorf("getting desired addon %s: %w", p.name, ErrAddonNotFound) @@ -181,7 +181,7 @@ type WaitAddonActiveProcedure struct { } // Do implements the logic for the procedure. -func (p *WaitAddonActiveProcedure) Do(ctx context.Context) error { +func (p *WaitAddonActiveProcedure) Do(_ context.Context) error { input := &eks.DescribeAddonInput{ AddonName: aws.String(p.name), ClusterName: aws.String(p.plan.clusterName), @@ -222,7 +222,7 @@ type WaitAddonDeleteProcedure struct { } // Do implements the logic for the procedure. -func (p *WaitAddonDeleteProcedure) Do(ctx context.Context) error { +func (p *WaitAddonDeleteProcedure) Do(_ context.Context) error { input := &eks.DescribeAddonInput{ AddonName: aws.String(p.name), ClusterName: aws.String(p.plan.clusterName), diff --git a/pkg/eks/eks.go b/pkg/eks/eks.go index ebbe442ef5..df25b1b42e 100644 --- a/pkg/eks/eks.go +++ b/pkg/eks/eks.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package eks contains the EKS API implementation. package eks import ( diff --git a/pkg/eks/identityprovider/plan.go b/pkg/eks/identityprovider/plan.go index 1aeaaf125d..fa7975ed1a 100644 --- a/pkg/eks/identityprovider/plan.go +++ b/pkg/eks/identityprovider/plan.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package identityprovider provides a plan to manage EKS OIDC identity provider association. package identityprovider import ( @@ -46,7 +47,8 @@ type plan struct { clusterName string } -func (p *plan) Create(ctx context.Context) ([]planner.Procedure, error) { +// Create will create the plan (i.e. list of procedures) for managing EKS OIDC identity provider association. +func (p *plan) Create(_ context.Context) ([]planner.Procedure, error) { procedures := []planner.Procedure{} if p.desiredIdentityProvider == nil && p.currentIdentityProvider == nil { diff --git a/pkg/eks/identityprovider/procedures.go b/pkg/eks/identityprovider/procedures.go index 20f01ebf6f..ee12f9f9ed 100644 --- a/pkg/eks/identityprovider/procedures.go +++ b/pkg/eks/identityprovider/procedures.go @@ -28,14 +28,17 @@ import ( var oidcType = aws.String("oidc") +// WaitIdentityProviderAssociatedProcedure waits for the identity provider to be associated. type WaitIdentityProviderAssociatedProcedure struct { plan *plan } +// Name returns the name of the procedure. func (w *WaitIdentityProviderAssociatedProcedure) Name() string { return "wait_identity_provider_association" } +// Do waits for the identity provider to be associated. func (w *WaitIdentityProviderAssociatedProcedure) Do(ctx context.Context) error { if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { out, err := w.plan.eksClient.DescribeIdentityProviderConfigWithContext(ctx, &eks.DescribeIdentityProviderConfigInput{ @@ -62,14 +65,17 @@ func (w *WaitIdentityProviderAssociatedProcedure) Do(ctx context.Context) error return nil } +// DisassociateIdentityProviderConfig disassociates the identity provider. type DisassociateIdentityProviderConfig struct { plan *plan } +// Name returns the name of the procedure. func (d *DisassociateIdentityProviderConfig) Name() string { return "dissociate_identity_provider" } +// Do disassociates the identity provider. func (d *DisassociateIdentityProviderConfig) Do(ctx context.Context) error { if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { _, err := d.plan.eksClient.DisassociateIdentityProviderConfigWithContext(ctx, &eks.DisassociateIdentityProviderConfigInput{ @@ -92,14 +98,17 @@ func (d *DisassociateIdentityProviderConfig) Do(ctx context.Context) error { return nil } +// AssociateIdentityProviderProcedure associates the identity provider. type AssociateIdentityProviderProcedure struct { plan *plan } +// Name returns the name of the procedure. func (a *AssociateIdentityProviderProcedure) Name() string { return "associate_identity_provider" } +// Do associates the identity provider. func (a *AssociateIdentityProviderProcedure) Do(ctx context.Context) error { oidc := a.plan.desiredIdentityProvider input := &eks.AssociateIdentityProviderConfigInput{ @@ -128,15 +137,18 @@ func (a *AssociateIdentityProviderProcedure) Do(ctx context.Context) error { return nil } +// UpdatedIdentityProviderTagsProcedure updates the tags for the identity provider. type UpdatedIdentityProviderTagsProcedure struct { plan *plan } +// Name returns the name of the procedure. func (u *UpdatedIdentityProviderTagsProcedure) Name() string { return "update_identity_provider_tags" } -func (u *UpdatedIdentityProviderTagsProcedure) Do(ctx context.Context) error { +// Do updates the tags for the identity provider. +func (u *UpdatedIdentityProviderTagsProcedure) Do(_ context.Context) error { arn := u.plan.currentIdentityProvider.IdentityProviderConfigArn _, err := u.plan.eksClient.TagResource(&eks.TagResourceInput{ ResourceArn: &arn, @@ -150,15 +162,18 @@ func (u *UpdatedIdentityProviderTagsProcedure) Do(ctx context.Context) error { return nil } +// RemoveIdentityProviderTagsProcedure removes the tags from the identity provider. type RemoveIdentityProviderTagsProcedure struct { plan *plan } +// Name returns the name of the procedure. func (r *RemoveIdentityProviderTagsProcedure) Name() string { return "remove_identity_provider_tags" } -func (r *RemoveIdentityProviderTagsProcedure) Do(ctx context.Context) error { +// Do removes the tags from the identity provider. +func (r *RemoveIdentityProviderTagsProcedure) Do(_ context.Context) error { keys := make([]*string, 0, len(r.plan.currentIdentityProvider.Tags)) for key := range r.plan.currentIdentityProvider.Tags { diff --git a/pkg/eks/identityprovider/types.go b/pkg/eks/identityprovider/types.go index e7e5868f95..940e8870e5 100644 --- a/pkg/eks/identityprovider/types.go +++ b/pkg/eks/identityprovider/types.go @@ -39,6 +39,7 @@ type OidcIdentityProviderConfig struct { UsernamePrefix string } +// IsEqual returns true if the OidcIdentityProviderConfig is equal to the supplied one. func (o *OidcIdentityProviderConfig) IsEqual(other *OidcIdentityProviderConfig) bool { if o == other { return true diff --git a/pkg/hash/base36.go b/pkg/hash/base36.go index 386b5adfc5..f03f515001 100644 --- a/pkg/hash/base36.go +++ b/pkg/hash/base36.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package hash provides a consistent hash function using blake2b. package hash import ( diff --git a/pkg/internal/bytes/bytes.go b/pkg/internal/bytes/bytes.go index 401a194d57..a9aa86df6e 100644 --- a/pkg/internal/bytes/bytes.go +++ b/pkg/internal/bytes/bytes.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package bytes provides utilities for working with byte arrays. package bytes import ( diff --git a/pkg/internal/cidr/cidr.go b/pkg/internal/cidr/cidr.go index dd56ee5e75..30f0ee4596 100644 --- a/pkg/internal/cidr/cidr.go +++ b/pkg/internal/cidr/cidr.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package cidr provides utilities for working with CIDR blocks. package cidr import ( diff --git a/pkg/internal/cmp/slice.go b/pkg/internal/cmp/slice.go index b2ff2d50db..6d36faa626 100644 --- a/pkg/internal/cmp/slice.go +++ b/pkg/internal/cmp/slice.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package cmp provides a set of comparison functions. package cmp import ( @@ -22,20 +23,25 @@ import ( "k8s.io/utils/ptr" ) +// ByPtrValue is a type to sort a slice of pointers to strings. type ByPtrValue []*string +// Len returns the length of the slice. func (s ByPtrValue) Len() int { return len(s) } +// Swap swaps the elements with indexes i and j. func (s ByPtrValue) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +// Less returns true if the element with index i should sort before the element with index j. func (s ByPtrValue) Less(i, j int) bool { return *s[i] < *s[j] } +// Equals returns true if the two slices of pointers to strings are equal. func Equals(slice1, slice2 []*string) bool { sort.Sort(ByPtrValue(slice1)) sort.Sort(ByPtrValue(slice2)) diff --git a/pkg/internal/mime/mime.go b/pkg/internal/mime/mime.go index 1324482f9f..7f7b23aa8b 100644 --- a/pkg/internal/mime/mime.go +++ b/pkg/internal/mime/mime.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package mime provides a function to generate a multipart MIME document. package mime import ( diff --git a/pkg/internal/rate/rate.go b/pkg/internal/rate/rate.go index 7528cfcad2..607f13f799 100644 --- a/pkg/internal/rate/rate.go +++ b/pkg/internal/rate/rate.go @@ -195,7 +195,7 @@ func (r *Reservation) CancelAt(now time.Time) { r.lim.tokens = tokens if r.timeToAct == r.lim.lastEvent { prevEvent := r.timeToAct.Add(r.limit.durationFromTokens(float64(-r.tokens))) - if !prevEvent.Before(now) { + if prevEvent.After(now) { r.lim.lastEvent = prevEvent } } diff --git a/pkg/internal/tristate/tristate.go b/pkg/internal/tristate/tristate.go index 6aafa52dc4..eeaae0ed86 100644 --- a/pkg/internal/tristate/tristate.go +++ b/pkg/internal/tristate/tristate.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package tristate provides a helper for working with bool pointers. package tristate // withDefault evaluates a pointer to a bool with a default value. diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index c2cd0ebde2..fa05ff5427 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package logger +// Package logger provides a convenient interface to use to log. package logger import ( @@ -69,35 +69,42 @@ func FromContext(ctx context.Context) *Logger { var _ Wrapper = &Logger{} +// Info logs a message at the info level. func (c *Logger) Info(msg string, keysAndValues ...any) { c.callStackHelper() c.logger.Info(msg, keysAndValues...) } +// Debug logs a message at the debug level. func (c *Logger) Debug(msg string, keysAndValues ...any) { c.callStackHelper() c.logger.V(logLevelDebug).Info(msg, keysAndValues...) } +// Warn logs a message at the warn level. func (c *Logger) Warn(msg string, keysAndValues ...any) { c.callStackHelper() c.logger.V(logLevelWarn).Info(msg, keysAndValues...) } +// Trace logs a message at the trace level. func (c *Logger) Trace(msg string, keysAndValues ...any) { c.callStackHelper() c.logger.V(logLevelTrace).Info(msg, keysAndValues...) } +// Error logs a message at the error level. func (c *Logger) Error(err error, msg string, keysAndValues ...any) { c.callStackHelper() c.logger.Error(err, msg, keysAndValues...) } +// GetLogger returns the underlying logr.Logger. func (c *Logger) GetLogger() logr.Logger { return c.logger } +// WithValues adds some key-value pairs of context to a logger. func (c *Logger) WithValues(keysAndValues ...any) *Logger { return &Logger{ callStackHelper: c.callStackHelper, @@ -105,6 +112,7 @@ func (c *Logger) WithValues(keysAndValues ...any) *Logger { } } +// WithName adds a new element to the logger's name. func (c *Logger) WithName(name string) *Logger { return &Logger{ callStackHelper: c.callStackHelper, diff --git a/pkg/planner/planner.go b/pkg/planner/planner.go index 9010b31edb..74ea078e2d 100644 --- a/pkg/planner/planner.go +++ b/pkg/planner/planner.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package planner provides a simple interface for creating and executing plans. package planner import "context" diff --git a/pkg/record/recorder.go b/pkg/record/recorder.go index 7591249a9b..df9a299264 100644 --- a/pkg/record/recorder.go +++ b/pkg/record/recorder.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package record provides a way to record Kubernetes events. package record import ( diff --git a/pkg/rosa/client.go b/pkg/rosa/client.go index 8d027f89ce..67c066238f 100644 --- a/pkg/rosa/client.go +++ b/pkg/rosa/client.go @@ -1,3 +1,4 @@ +// Package rosa provides a way to interact with the Red Hat OpenShift Service on AWS (ROSA) API. package rosa import ( @@ -18,6 +19,7 @@ const ( ocmAPIURLKey = "ocmApiUrl" ) +// NewOCMClient creates a new OCM client. func NewOCMClient(ctx context.Context, rosaScope *scope.ROSAControlPlaneScope) (*ocm.Client, error) { token, url, err := ocmCredentials(ctx, rosaScope) if err != nil { diff --git a/pkg/rosa/helpers.go b/pkg/rosa/helpers.go new file mode 100644 index 0000000000..39f6058069 --- /dev/null +++ b/pkg/rosa/helpers.go @@ -0,0 +1,23 @@ +package rosa + +import ( + cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" +) + +// IsNodePoolReady checkes whether the nodepool is provisoned and all replicas are available. +// If autosacling is enabled, NodePool must have replicas >= autosacling.MinReplica to be considered ready. +func IsNodePoolReady(nodePool *cmv1.NodePool) bool { + if nodePool.Status().Message() != "" { + return false + } + + if nodePool.Replicas() != 0 { + return nodePool.Replicas() == nodePool.Status().CurrentReplicas() + } + + if nodePool.Autoscaling() != nil { + return nodePool.Status().CurrentReplicas() >= nodePool.Autoscaling().MinReplica() + } + + return false +} diff --git a/pkg/rosa/idps.go b/pkg/rosa/idps.go index 72e0562d92..bfa9fce65e 100644 --- a/pkg/rosa/idps.go +++ b/pkg/rosa/idps.go @@ -97,17 +97,19 @@ func findExistingClusterAdminIDP(client *ocm.Client, clusterID string) ( } for _, idp := range idps { - if idp.Name() == clusterAdminIDPname { - itemUserList, err := client.GetHTPasswdUserList(clusterID, idp.ID()) - if err != nil { - reterr = fmt.Errorf("failed to get user list of the HTPasswd IDP of '%s: %s': %v", idp.Name(), clusterID, err) - return - } - - htpasswdIDP = idp - userList = itemUserList + if idp.Name() != clusterAdminIDPname { + continue + } + + itemUserList, err := client.GetHTPasswdUserList(clusterID, idp.ID()) + if err != nil { + reterr = fmt.Errorf("failed to get user list of the HTPasswd IDP of '%s: %s': %v", idp.Name(), clusterID, err) return } + + htpasswdIDP = idp + userList = itemUserList + return } return diff --git a/pkg/rosa/oauth.go b/pkg/rosa/oauth.go index 110f638392..299dfb01d3 100644 --- a/pkg/rosa/oauth.go +++ b/pkg/rosa/oauth.go @@ -14,6 +14,7 @@ import ( restclient "k8s.io/client-go/rest" ) +// TokenResponse contains the access token and the duration until it expires. type TokenResponse struct { AccessToken string ExpiresIn time.Duration @@ -29,7 +30,7 @@ func RequestToken(ctx context.Context, apiURL, username, password string, config } tokenReqURL := fmt.Sprintf("%s/oauth/authorize?response_type=token&client_id=%s", oauthURL, clientID) - request, err := http.NewRequestWithContext(ctx, http.MethodGet, tokenReqURL, nil) + request, err := http.NewRequestWithContext(ctx, http.MethodGet, tokenReqURL, http.NoBody) if err != nil { return nil, err } diff --git a/pkg/rosa/versions.go b/pkg/rosa/versions.go index 2949d3b5cf..d300adbf96 100644 --- a/pkg/rosa/versions.go +++ b/pkg/rosa/versions.go @@ -1,6 +1,7 @@ package rosa import ( + "fmt" "time" "github.com/blang/semver" @@ -8,6 +9,7 @@ import ( "github.com/openshift/rosa/pkg/ocm" ) +// MinSupportedVersion is the minimum supported version for ROSA. var MinSupportedVersion = semver.MustParse("4.14.0") // CheckExistingScheduledUpgrade checks and returns the current upgrade schedule if any. @@ -27,7 +29,8 @@ func CheckExistingScheduledUpgrade(client *ocm.Client, cluster *cmv1.Cluster) (* // ScheduleControlPlaneUpgrade schedules a new control plane upgrade to the specified version at the specified time. func ScheduleControlPlaneUpgrade(client *ocm.Client, cluster *cmv1.Cluster, version string, nextRun time.Time) (*cmv1.ControlPlaneUpgradePolicy, error) { // earliestNextRun is set to at least 5 min from now by the OCM API. - // we set it to 6 min here to account for latencty. + // Set our next run request to something slightly longer than 5min to make sure we account for the latency between when we send this + // request and when the server processes it. earliestNextRun := time.Now().Add(time.Minute * 6) if nextRun.Before(earliestNextRun) { nextRun = earliestNextRun @@ -45,9 +48,40 @@ func ScheduleControlPlaneUpgrade(client *ocm.Client, cluster *cmv1.Cluster, vers return client.ScheduleHypershiftControlPlaneUpgrade(cluster.ID(), upgradePolicy) } +// ScheduleNodePoolUpgrade schedules a new nodePool upgrade to the specified version at the specified time. +func ScheduleNodePoolUpgrade(client *ocm.Client, clusterID string, nodePool *cmv1.NodePool, version string, nextRun time.Time) (*cmv1.NodePoolUpgradePolicy, error) { + // earliestNextRun is set to at least 5 min from now by the OCM API. + // Set our next run request to something slightly longer than 5min to make sure we account for the latency between when we send this + // request and when the server processes it. + earliestNextRun := time.Now().Add(time.Minute * 6) + if nextRun.Before(earliestNextRun) { + nextRun = earliestNextRun + } + + upgradePolicy, err := cmv1.NewNodePoolUpgradePolicy(). + UpgradeType(cmv1.UpgradeTypeNodePool). + NodePoolID(nodePool.ID()). + ScheduleType(cmv1.ScheduleTypeManual). + Version(version). + NextRun(nextRun). + Build() + if err != nil { + return nil, err + } + + scheduledUpgrade, err := client.ScheduleNodePoolUpgrade(clusterID, nodePool.ID(), upgradePolicy) + if err != nil { + return nil, fmt.Errorf("failed to schedule nodePool upgrade to version %s: %w", version, err) + } + + return scheduledUpgrade, nil +} + // machinepools can be created with a minimal of two minor versions from the control plane. const minorVersionsAllowedDeviation = 2 +// MachinePoolSupportedVersionsRange returns the supported range of versions +// for a machine pool based on the control plane version. func MachinePoolSupportedVersionsRange(controlPlaneVersion string) (*semver.Version, *semver.Version, error) { maxVersion, err := semver.Parse(controlPlaneVersion) if err != nil { diff --git a/scripts/go_install.sh b/scripts/go_install.sh index 12ce444224..a07b8e0f11 100755 --- a/scripts/go_install.sh +++ b/scripts/go_install.sh @@ -37,7 +37,7 @@ if [ -z "${GOBIN}" ]; then exit 1 fi -rm "${GOBIN}/${2}"* || true +rm -f "${GOBIN}/${2}"* || true # install the golang module specified as the first argument go install "${1}@${3}" diff --git a/templates/cluster-template-rosa-machinepool.yaml b/templates/cluster-template-rosa-machinepool.yaml index c86266fe5d..8b6c79bbfe 100644 --- a/templates/cluster-template-rosa-machinepool.yaml +++ b/templates/cluster-template-rosa-machinepool.yaml @@ -30,8 +30,6 @@ spec: rosaClusterName: ${CLUSTER_NAME:0:15} version: "${OPENSHIFT_VERSION}" region: "${AWS_REGION}" - accountID: "${AWS_ACCOUNT_ID}" - creatorARN: "${AWS_CREATOR_ARN}" network: machineCIDR: "10.0.0.0/16" rolesRef: diff --git a/templates/cluster-template-rosa.yaml b/templates/cluster-template-rosa.yaml index 763575134d..f311e9cdb1 100644 --- a/templates/cluster-template-rosa.yaml +++ b/templates/cluster-template-rosa.yaml @@ -30,8 +30,6 @@ spec: rosaClusterName: ${CLUSTER_NAME:0:15} version: "${OPENSHIFT_VERSION}" region: "${AWS_REGION}" - accountID: "${AWS_ACCOUNT_ID}" - creatorARN: "${AWS_CREATOR_ARN}" network: machineCIDR: "10.0.0.0/16" rolesRef: diff --git a/test/e2e/shared/aws.go b/test/e2e/shared/aws.go index 95b469780e..d42837a68e 100644 --- a/test/e2e/shared/aws.go +++ b/test/e2e/shared/aws.go @@ -953,7 +953,8 @@ func (s *ServiceQuota) updateServiceQuotaRequestStatus(serviceQuotasClient *serv } } -func DumpEKSClusters(ctx context.Context, e2eCtx *E2EContext) { +// DumpEKSClusters dumps the EKS clusters in the environment. +func DumpEKSClusters(_ context.Context, e2eCtx *E2EContext) { name := "no-bootstrap-cluster" if e2eCtx.Environment.BootstrapClusterProxy != nil { name = e2eCtx.Environment.BootstrapClusterProxy.GetName() @@ -1014,7 +1015,7 @@ func dumpEKSCluster(cluster *eks.Cluster, logPath string) { } // To calculate how much resources a test consumes, these helper functions below can be used. -// ListVpcInternetGateways, ListNATGateways, ListRunningEC2, ListVPC +// ListVpcInternetGateways, ListNATGateways, ListRunningEC2, ListVPC. func ListVpcInternetGateways(e2eCtx *E2EContext) ([]*ec2.InternetGateway, error) { ec2Svc := ec2.New(e2eCtx.AWSSession) @@ -1052,7 +1053,8 @@ func ListNATGateways(e2eCtx *E2EContext) (map[string]*ec2.NatGateway, error) { return gateways, nil } -func ListRunningEC2(e2eCtx *E2EContext) ([]instance, error) { +// listRunningEC2 returns a list of running EC2 instances. +func listRunningEC2(e2eCtx *E2EContext) ([]instance, error) { //nolint:unused ec2Svc := ec2.New(e2eCtx.AWSSession) resp, err := ec2Svc.DescribeInstancesWithContext(context.TODO(), &ec2.DescribeInstancesInput{ diff --git a/test/e2e/shared/common.go b/test/e2e/shared/common.go index 8cc2d9a6b4..c11e1d82f3 100644 --- a/test/e2e/shared/common.go +++ b/test/e2e/shared/common.go @@ -92,12 +92,13 @@ func DumpSpecResourcesAndCleanup(ctx context.Context, specName string, namespace delete(e2eCtx.Environment.Namespaces, namespace) } +// AWSStackLogCollector collects logs from the AWS stack. type AWSStackLogCollector struct { E2EContext *E2EContext } // CollectInfrastructureLogs collects log from the infrastructure. -func (k AWSStackLogCollector) CollectInfrastructureLogs(ctx context.Context, managementClusterClient crclient.Client, c *clusterv1.Cluster, outputPath string) error { +func (k AWSStackLogCollector) CollectInfrastructureLogs(_ context.Context, _ crclient.Client, _ *clusterv1.Cluster, _ string) error { return nil } diff --git a/test/e2e/shared/defaults.go b/test/e2e/shared/defaults.go index c67c5538a5..13e77c84f7 100644 --- a/test/e2e/shared/defaults.go +++ b/test/e2e/shared/defaults.go @@ -73,37 +73,51 @@ const ( MultiTenancy = "MULTI_TENANCY_" ) +// ResourceQuotaFilePath is the path to the file that contains the resource usage. var ResourceQuotaFilePath = "/tmp/capa-e2e-resource-usage.lock" + var ( + // MultiTenancySimpleRole is the simple role for multi-tenancy test. MultiTenancySimpleRole = MultitenancyRole("Simple") - MultiTenancyJumpRole = MultitenancyRole("Jump") + // MultiTenancyJumpRole is the jump role for multi-tenancy test. + MultiTenancyJumpRole = MultitenancyRole("Jump") + // MultiTenancyNestedRole is the nested role for multi-tenancy test. MultiTenancyNestedRole = MultitenancyRole("Nested") - MultiTenancyRoles = []MultitenancyRole{MultiTenancySimpleRole, MultiTenancyJumpRole, MultiTenancyNestedRole} - roleLookupCache = make(map[string]string) + + // MultiTenancyRoles is the list of multi-tenancy roles. + MultiTenancyRoles = []MultitenancyRole{MultiTenancySimpleRole, MultiTenancyJumpRole, MultiTenancyNestedRole} + roleLookupCache = make(map[string]string) ) +// MultitenancyRole is the role of the test. type MultitenancyRole string +// EnvVarARN returns the environment variable name for the role ARN. func (m MultitenancyRole) EnvVarARN() string { return MultiTenancy + strings.ToUpper(string(m)) + "_ROLE_ARN" } +// EnvVarName returns the environment variable name for the role name. func (m MultitenancyRole) EnvVarName() string { return MultiTenancy + strings.ToUpper(string(m)) + "_ROLE_NAME" } +// EnvVarIdentity returns the environment variable name for the identity name. func (m MultitenancyRole) EnvVarIdentity() string { return MultiTenancy + strings.ToUpper(string(m)) + "_IDENTITY_NAME" } +// IdentityName returns the identity name. func (m MultitenancyRole) IdentityName() string { return strings.ToLower(m.RoleName()) } +// RoleName returns the role name. func (m MultitenancyRole) RoleName() string { return "CAPAMultiTenancy" + string(m) } +// SetEnvVars sets the environment variables for the role. func (m MultitenancyRole) SetEnvVars(prov client.ConfigProvider) error { arn, err := m.RoleARN(prov) if err != nil { @@ -115,6 +129,7 @@ func (m MultitenancyRole) SetEnvVars(prov client.ConfigProvider) error { return nil } +// RoleARN returns the role ARN. func (m MultitenancyRole) RoleARN(prov client.ConfigProvider) (string, error) { if roleARN, ok := roleLookupCache[m.RoleName()]; ok { return roleARN, nil diff --git a/test/e2e/shared/gpu.go b/test/e2e/shared/gpu.go index 3bbdeed267..b871b2f010 100644 --- a/test/e2e/shared/gpu.go +++ b/test/e2e/shared/gpu.go @@ -103,7 +103,7 @@ type jobsClientAdapter struct { } // Get fetches the job named by the key and updates the provided object. -func (c jobsClientAdapter) Get(ctx context.Context, key crclient.ObjectKey, obj crclient.Object, opts ...crclient.GetOption) error { +func (c jobsClientAdapter) Get(ctx context.Context, key crclient.ObjectKey, obj crclient.Object, _ ...crclient.GetOption) error { job, err := c.client.Get(ctx, key.Name, metav1.GetOptions{}) if jobObj, ok := obj.(*batchv1.Job); ok { job.DeepCopyInto(jobObj) diff --git a/test/e2e/shared/suite.go b/test/e2e/shared/suite.go index 07e83fe0da..bd52878487 100644 --- a/test/e2e/shared/suite.go +++ b/test/e2e/shared/suite.go @@ -17,6 +17,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package shared provides common utilities, setup and teardown for the e2e tests. package shared import ( @@ -118,8 +119,11 @@ func Node1BeforeSuite(e2eCtx *E2EContext) []byte { if prov.Name != "aws" { continue } - e2eCtx.E2EConfig.Providers[i].Files = append(e2eCtx.E2EConfig.Providers[i].Files, clusterctlCITemplate) - e2eCtx.E2EConfig.Providers[i].Files = append(e2eCtx.E2EConfig.Providers[i].Files, clusterctlCITemplateForUpgrade) + e2eCtx.E2EConfig.Providers[i].Files = append( + e2eCtx.E2EConfig.Providers[i].Files, + clusterctlCITemplate, + clusterctlCITemplateForUpgrade, + ) } } diff --git a/test/e2e/shared/template.go b/test/e2e/shared/template.go index caa16917e9..72c4884412 100644 --- a/test/e2e/shared/template.go +++ b/test/e2e/shared/template.go @@ -154,7 +154,7 @@ func renderCustomCloudFormation(t *cfn_bootstrap.Template) *cloudformation.Templ return cloudformationTemplate } -func appendMultiTenancyRoles(t *cfn_bootstrap.Template, cfnt *cloudformation.Template) { +func appendMultiTenancyRoles(_ *cfn_bootstrap.Template, cfnt *cloudformation.Template) { controllersPolicy := cfnt.Resources[string(cfn_bootstrap.ControllersPolicy)].(*cfn_iam.ManagedPolicy) controllersPolicy.Roles = append( controllersPolicy.Roles, diff --git a/test/e2e/suites/managed/cluster.go b/test/e2e/suites/managed/cluster.go index 1edae3adec..46829a2bcb 100644 --- a/test/e2e/suites/managed/cluster.go +++ b/test/e2e/suites/managed/cluster.go @@ -17,6 +17,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package managed implements a test for creating a managed cluster using CAPA. package managed import ( diff --git a/test/e2e/suites/managed/machine_deployment.go b/test/e2e/suites/managed/machine_deployment.go index 89a17c772a..79d26d3355 100644 --- a/test/e2e/suites/managed/machine_deployment.go +++ b/test/e2e/suites/managed/machine_deployment.go @@ -100,10 +100,6 @@ func MachineDeploymentSpec(ctx context.Context, inputGetter func() MachineDeploy Deleter: input.BootstrapClusterProxy.GetClient(), MachineDeployment: md[0], }) - // deleteMachine(ctx, deleteMachineInput{ - // Deleter: input.BootstrapClusterProxy.GetClient(), - // Machine: &workerMachines[0], - // }) waitForMachineDeploymentDeleted(ctx, waitForMachineDeploymentDeletedInput{ Getter: input.BootstrapClusterProxy.GetClient(), diff --git a/test/e2e/suites/unmanaged/helpers_test.go b/test/e2e/suites/unmanaged/helpers_test.go index 39457e481c..03c6870384 100644 --- a/test/e2e/suites/unmanaged/helpers_test.go +++ b/test/e2e/suites/unmanaged/helpers_test.go @@ -418,7 +418,7 @@ func getSubnetID(filterKey, filterValue, clusterName string) *string { return subnetOutput.Subnets[0].SubnetId } -func getVolumeIds(info statefulSetInfo, k8sclient crclient.Client) []*string { +func getVolumeIDs(info statefulSetInfo, k8sclient crclient.Client) []*string { ginkgo.By("Retrieving IDs of dynamically provisioned volumes.") statefulset := &appsv1.StatefulSet{} err := k8sclient.Get(context.TODO(), apimachinerytypes.NamespacedName{Namespace: info.namespace, Name: info.name}, statefulset) @@ -683,11 +683,11 @@ func verifyElbExists(elbName string, exists bool) { } } -func verifyVolumesExists(awsVolumeIds []*string) { +func verifyVolumesExists(awsVolumeIDs []*string) { ginkgo.By("Ensuring dynamically provisioned volumes exists") ec2Client := ec2.New(e2eCtx.AWSSession) input := &ec2.DescribeVolumesInput{ - VolumeIds: awsVolumeIds, + VolumeIds: awsVolumeIDs, } _, err := ec2Client.DescribeVolumes(input) Expect(err).NotTo(HaveOccurred()) diff --git a/test/e2e/suites/unmanaged/unmanaged_functional_test.go b/test/e2e/suites/unmanaged/unmanaged_functional_test.go index aed9e02309..a4e6a49404 100644 --- a/test/e2e/suites/unmanaged/unmanaged_functional_test.go +++ b/test/e2e/suites/unmanaged/unmanaged_functional_test.go @@ -319,8 +319,8 @@ var _ = ginkgo.Context("[unmanaged] [functional]", func() { clusterClient := e2eCtx.Environment.BootstrapClusterProxy.GetWorkloadCluster(ctx, namespace.Name, cluster1Name).GetClient() createStatefulSet(nginxStatefulsetInfo, clusterClient) - awsVolIds := getVolumeIds(nginxStatefulsetInfo, clusterClient) - verifyVolumesExists(awsVolIds) + awsVolIDs := getVolumeIDs(nginxStatefulsetInfo, clusterClient) + verifyVolumesExists(awsVolIDs) kubernetesUgradeVersion := e2eCtx.E2EConfig.GetVariable(shared.PostCSIKubernetesVer) configCluster.KubernetesVersion = kubernetesUgradeVersion @@ -348,8 +348,8 @@ var _ = ginkgo.Context("[unmanaged] [functional]", func() { ginkgo.By("Deploying StatefulSet on infra when K8s >= 1.23") createStatefulSet(nginxStatefulsetInfo2, clusterClient) - awsVolIds = getVolumeIds(nginxStatefulsetInfo2, clusterClient) - verifyVolumesExists(awsVolIds) + awsVolIDs = getVolumeIDs(nginxStatefulsetInfo2, clusterClient) + verifyVolumesExists(awsVolIDs) ginkgo.By("Deleting LB service") deleteLBService(metav1.NamespaceDefault, lbServiceName, clusterClient) @@ -358,7 +358,7 @@ var _ = ginkgo.Context("[unmanaged] [functional]", func() { deleteCluster(ctx, cluster2) ginkgo.By("Deleting retained dynamically provisioned volumes") - deleteRetainedVolumes(awsVolIds) + deleteRetainedVolumes(awsVolIDs) ginkgo.By("PASSED!") }) }) @@ -388,8 +388,8 @@ var _ = ginkgo.Context("[unmanaged] [functional]", func() { clusterClient := e2eCtx.Environment.BootstrapClusterProxy.GetWorkloadCluster(ctx, namespace.Name, cluster1Name).GetClient() createStatefulSet(nginxStatefulsetInfo, clusterClient) - awsVolIds := getVolumeIds(nginxStatefulsetInfo, clusterClient) - verifyVolumesExists(awsVolIds) + awsVolIDs := getVolumeIDs(nginxStatefulsetInfo, clusterClient) + verifyVolumesExists(awsVolIDs) kubernetesUgradeVersion := e2eCtx.E2EConfig.GetVariable(shared.PostCSIKubernetesVer) @@ -418,8 +418,8 @@ var _ = ginkgo.Context("[unmanaged] [functional]", func() { ginkgo.By("Deploying StatefulSet on infra when K8s >= 1.23") createStatefulSet(nginxStatefulsetInfo2, clusterClient) - awsVolIds = getVolumeIds(nginxStatefulsetInfo2, clusterClient) - verifyVolumesExists(awsVolIds) + awsVolIDs = getVolumeIDs(nginxStatefulsetInfo2, clusterClient) + verifyVolumesExists(awsVolIDs) ginkgo.By("Deleting LB service") deleteLBService(metav1.NamespaceDefault, lbServiceName, clusterClient) @@ -428,7 +428,7 @@ var _ = ginkgo.Context("[unmanaged] [functional]", func() { deleteCluster(ctx, cluster2) ginkgo.By("Deleting retained dynamically provisioned volumes") - deleteRetainedVolumes(awsVolIds) + deleteRetainedVolumes(awsVolIDs) ginkgo.By("PASSED!") }) }) @@ -459,8 +459,8 @@ var _ = ginkgo.Context("[unmanaged] [functional]", func() { clusterClient := e2eCtx.Environment.BootstrapClusterProxy.GetWorkloadCluster(ctx, namespace.Name, cluster1Name).GetClient() createStatefulSet(nginxStatefulsetInfo, clusterClient) - awsVolIds := getVolumeIds(nginxStatefulsetInfo, clusterClient) - verifyVolumesExists(awsVolIds) + awsVolIDs := getVolumeIDs(nginxStatefulsetInfo, clusterClient) + verifyVolumesExists(awsVolIDs) kubernetesUgradeVersion := e2eCtx.E2EConfig.GetVariable(shared.PostCSIKubernetesVer) configCluster.KubernetesVersion = kubernetesUgradeVersion @@ -488,8 +488,8 @@ var _ = ginkgo.Context("[unmanaged] [functional]", func() { ginkgo.By("Deploying StatefulSet on infra when K8s >= 1.23") createStatefulSet(nginxStatefulsetInfo2, clusterClient) - awsVolIds = getVolumeIds(nginxStatefulsetInfo2, clusterClient) - verifyVolumesExists(awsVolIds) + awsVolIDs = getVolumeIDs(nginxStatefulsetInfo2, clusterClient) + verifyVolumesExists(awsVolIDs) ginkgo.By("Deleting LB service") deleteLBService(metav1.NamespaceDefault, lbServiceName, clusterClient) @@ -498,7 +498,7 @@ var _ = ginkgo.Context("[unmanaged] [functional]", func() { deleteCluster(ctx, cluster2) ginkgo.By("Deleting retained dynamically provisioned volumes") - deleteRetainedVolumes(awsVolIds) + deleteRetainedVolumes(awsVolIDs) ginkgo.By("PASSED!") }) }) diff --git a/test/helpers/envtest.go b/test/helpers/envtest.go index 098a280d69..43f0618b0c 100644 --- a/test/helpers/envtest.go +++ b/test/helpers/envtest.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package helpers provides a set of utilities for testing controllers. package helpers import ( @@ -83,7 +84,7 @@ func init() { utilruntime.Must(clusterv1.AddToScheme(scheme.Scheme)) // Get the root of the current file to use in CRD paths. - _, filename, _, _ := goruntime.Caller(0) //nolint + _, filename, _, _ := goruntime.Caller(0) //nolint:dogsled root = path.Join(path.Dir(filename), "..", "..") } @@ -237,7 +238,7 @@ func buildModifiedWebhook(tag string, relativeFilePath string) (admissionv1.Muta if o.GetKind() == mutatingWebhookKind { // update the name in metadata if o.GetName() == defaultMutatingWebhookName { - o.SetName(strings.Join([]string{defaultMutatingWebhookName, "-", tag}, "")) + o.SetName(defaultMutatingWebhookName + "-" + tag) if err := scheme.Scheme.Convert(&o, &mutatingWebhook, nil); err != nil { klog.Fatalf("failed to convert MutatingWebhookConfiguration %s", o.GetName()) } @@ -246,7 +247,7 @@ func buildModifiedWebhook(tag string, relativeFilePath string) (admissionv1.Muta if o.GetKind() == validatingWebhookKind { // update the name in metadata if o.GetName() == defaultValidatingWebhookName { - o.SetName(strings.Join([]string{defaultValidatingWebhookName, "-", tag}, "")) + o.SetName(defaultValidatingWebhookName + "-" + tag) if err := scheme.Scheme.Convert(&o, &validatingWebhook, nil); err != nil { klog.Fatalf("failed to convert ValidatingWebhookConfiguration %s", o.GetName()) } diff --git a/test/helpers/external/cluster.go b/test/helpers/external/cluster.go index 051fb88391..524c775e0d 100644 --- a/test/helpers/external/cluster.go +++ b/test/helpers/external/cluster.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package external provides mock CRDs for use in tests. package external import ( diff --git a/test/mocks/generate_aws.go b/test/mocks/generate_aws.go index 5c5e5a7f02..f3b08973ec 100644 --- a/test/mocks/generate_aws.go +++ b/test/mocks/generate_aws.go @@ -14,16 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package mocks provides a way to generate mock objects for AWS services. +// //go:generate ../../hack/tools/bin/mockgen -destination aws_elbv2_mock.go -package mocks github.com/aws/aws-sdk-go/service/elbv2/elbv2iface ELBV2API //go:generate /usr/bin/env bash -c "cat ../../hack/boilerplate/boilerplate.generatego.txt aws_elbv2_mock.go > _aws_elbv2_mock.go && mv _aws_elbv2_mock.go aws_elbv2_mock.go" - //go:generate ../../hack/tools/bin/mockgen -destination aws_elb_mock.go -package mocks github.com/aws/aws-sdk-go/service/elb/elbiface ELBAPI //go:generate /usr/bin/env bash -c "cat ../../hack/boilerplate/boilerplate.generatego.txt aws_elb_mock.go > _aws_elb_mock.go && mv _aws_elb_mock.go aws_elb_mock.go" - //go:generate ../../hack/tools/bin/mockgen -destination aws_rgtagging_mock.go -package mocks github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi/resourcegroupstaggingapiiface ResourceGroupsTaggingAPIAPI //go:generate /usr/bin/env bash -c "cat ../../hack/boilerplate/boilerplate.generatego.txt aws_rgtagging_mock.go > _aws_rgtagging_mock.go && mv _aws_rgtagging_mock.go aws_rgtagging_mock.go" - //go:generate ../../hack/tools/bin/mockgen -destination aws_ec2api_mock.go -package mocks github.com/aws/aws-sdk-go/service/ec2/ec2iface EC2API //go:generate /usr/bin/env bash -c "cat ../../hack/boilerplate/boilerplate.generatego.txt aws_ec2api_mock.go > _aws_ec2api_mock.go && mv _aws_ec2api_mock.go aws_ec2api_mock.go" - package mocks diff --git a/util/conditions/helper.go b/util/conditions/helper.go index c4e4ad7a2a..2acb09093e 100644 --- a/util/conditions/helper.go +++ b/util/conditions/helper.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package conditions provides helper functions for working with conditions. package conditions import ( diff --git a/util/system/util.go b/util/system/util.go index 786150950d..0b6eb9507c 100644 --- a/util/system/util.go +++ b/util/system/util.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package system contains utiilities for the system namespace. package system import ( diff --git a/version/version.go b/version/version.go index 4132c9f016..b895ae2daf 100644 --- a/version/version.go +++ b/version/version.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package version provides the version of the manager. package version import (