diff --git a/.golangci.yml b/.golangci.yml index 4738157..0a62a72 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,187 +1,141 @@ -run: - timeout: 30m +# SPDX-FileCopyrightText: 2025 The Crossplane Authors +# +# SPDX-License-Identifier: CC0-1.0 +version: "2" output: - # colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number" formats: - - format: colored-line-number - print-linter-name: true - show-stats: true - -linters-settings: - errcheck: - # report about not checking of errors in type assetions: `a := b.(MyStruct)`; - # default is false: such cases aren't reported by default. - check-type-assertions: false - - # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; - # default is false: such cases aren't reported by default. - check-blank: true - - # Updated to use exclude-functions instead of ignore - exclude-functions: fmt:.*,io/ioutil:^Read.* - - govet: - # report about shadowed variables - check-shadowing: false - - revive: - # confidence for issues, default is 0.8 - confidence: 0.8 - - gofmt: - # simplify code: gofmt with `-s` option, true by default - simplify: true - - goimports: - # put imports beginning with prefix after 3rd-party packages; - # it's a comma-separated list of prefixes - local-prefixes: github.com/upbound/provider-aws - - gocyclo: - # minimal code complexity to report, 30 by default (but we recommend 10-20) - min-complexity: 10 - - dupl: - # tokens count to trigger issue, 150 by default - threshold: 100 - - goconst: - # minimal length of string constant, 3 by default - min-len: 3 - # minimal occurrences count to trigger, 3 by default - min-occurrences: 5 - - lll: - # tab width in spaces. Default to 1. - tab-width: 1 - - unparam: - # Inspect exported functions, default is false. Set to true if no external program/library imports your code. - # XXX: if you enable this setting, unparam will report a lot of false-positives in text editors: - # if it's called for subdir of a project it can't find external interfaces. All text editor integrations - # with golangci-lint call it on a directory with the changed file. - check-exported: false - - nakedret: - # make an issue if func has more lines of code than this setting and it has naked returns; default is 30 - max-func-lines: 30 - - prealloc: - # XXX: we don't recommend using this linter before doing performance profiling. - # For most programs usage of prealloc will be a premature optimization. - - # Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them. - # True by default. - simple: true - range-loops: true # Report preallocation suggestions on range loops, true by default - for-loops: false # Report preallocation suggestions on for loops, false by default - - gocritic: - # Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint` run to see all tags and checks. - # Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags". - enabled-tags: - - performance - - settings: # settings passed to gocritic - captLocal: # must be valid enabled check name - paramsOnly: true - rangeValCopy: - sizeThreshold: 32 - + text: + path: stdout linters: enable: - - govet - - gocyclo - - gocritic + - asasalint + - asciicheck + - bidichk + - bodyclose + - contextcheck + - durationcheck + - errchkjson + - errorlint + - exhaustive + - gocheckcompilerdirectives + - gochecksumtype - goconst - - goimports - - gofmt # We enable this as well as goimports for its simplify mode. - - gosimple + - gocritic + - gocyclo + - gosec + - gosmopolitan + - loggercheck + - makezero + - misspell + - musttag + - nakedret + - nilerr + - nilnesserr + - noctx - prealloc + - protogetter + - reassign + - recvcheck - revive - - staticcheck + - rowserrcheck + - spancheck + - sqlclosecheck + - testifylint - unconvert - - unused - - misspell - - nakedret - - presets: - - bugs - - unused - fast: false - - + - unparam + - zerologlint + settings: + dupl: + threshold: 100 + errcheck: + check-type-assertions: false + check-blank: false + exclude-functions: + - io/ioutil.ReadFile + - io/ioutil.ReadDir + - io/ioutil.ReadAll + goconst: + min-len: 3 + min-occurrences: 5 + gocritic: + enabled-tags: + - performance + settings: + captLocal: + paramsOnly: true + rangeValCopy: + sizeThreshold: 32 + gocyclo: + min-complexity: 15 + lll: + tab-width: 1 + nakedret: + max-func-lines: 30 + prealloc: + simple: true + range-loops: true + for-loops: false + revive: + confidence: 0.8 + unparam: + check-exported: false + exclusions: + generated: lax + rules: + - linters: + - dupl + - errcheck + - gocyclo + - gosec + - scopelint + - unparam + path: _test(ing)?\.go + - linters: + - gocritic + path: _test\.go + text: (unnamedResult|exitAfterDefer) + - linters: + - gocritic + text: '(hugeParam|rangeValCopy):' + - linters: + - staticcheck + text: 'SA3000:' + - linters: + - gosec + text: 'G101:' + - linters: + - gosec + text: 'G104:' + - linters: + - staticcheck + text: 'QF1008:' + - linters: + - staticcheck + text: 'QF1001:' + paths: + - zz_\..+\.go$ + - third_party$ + - builtin$ + - examples$ issues: - # Added the exclude-files configuration here to replace run.skip-files - exclude-files: - - "zz_generated\\..+\\.go$" - - # Excluding configuration per-path and per-linter - exclude-rules: - # Exclude some linters from running on tests files. - - path: _test(ing)?\.go - linters: - - gocyclo - - errcheck - - dupl - - gosec - - scopelint - - unparam - - # Ease some gocritic warnings on test files. - - path: _test\.go - text: "(unnamedResult|exitAfterDefer)" - linters: - - gocritic - - # These are performance optimisations rather than style issues per se. - # They warn when function arguments or range values copy a lot of memory - # rather than using a pointer. - - text: "(hugeParam|rangeValCopy):" - linters: - - gocritic - - # This "TestMain should call os.Exit to set exit code" warning is not clever - # enough to notice that we call a helper method that calls os.Exit. - - text: "SA3000:" - linters: - - staticcheck - - - text: "k8s.io/api/core/v1" - linters: - - goimports - - # This is a "potential hardcoded credentials" warning. It's triggered by - # any variable with 'secret' in the same, and thus hits a lot of false - # positives in Kubernetes land where a Secret is an object type. - - text: "G101:" - linters: - - gosec - - gas - - # This is an 'errors unhandled' warning that duplicates errcheck. - - text: "G104:" - linters: - - gosec - - gas - - # Independently from option `exclude` we use default exclude patterns, - # it can be disabled by this option. To list all - # excluded by default patterns execute `golangci-lint run --help`. - # Default value for this option is true. - exclude-use-default: false - - # Show only new issues: if there are unstaged changes or untracked files, - # only those changes are analyzed, else only changes in HEAD~ are analyzed. - # It's a super-useful option for integration of golangci-lint into existing - # large codebase. It's not practical to fix all existing issues at the moment - # of integration: much better don't allow issues in new code. - # Default is false. - new: false - - # Maximum issues count per one linter. Set to 0 to disable. Default is 50. - max-per-linter: 0 - - # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. + max-issues-per-linter: 0 max-same-issues: 0 + new: false +formatters: + enable: + - gofmt + - goimports + settings: + gofmt: + simplify: true + goimports: + local-prefixes: + - github.com/upbound/provider-terraform + exclusions: + generated: lax + paths: + - zz_\..+\.go$ + - third_party$ + - builtin$ + - examples$ diff --git a/Makefile b/Makefile index 58bba81..55c7359 100644 --- a/Makefile +++ b/Makefile @@ -10,15 +10,13 @@ PLATFORMS ?= linux_amd64 linux_arm64 -include build/makelib/output.mk # Setup Go -GO_REQUIRED_VERSION ?= 1.23 +GO_REQUIRED_VERSION ?= 1.24 NPROCS ?= 1 -# GOLANGCILINT_VERSION is inherited from build submodule by default. -# Uncomment below if you need to override the version. -# GOLANGCILINT_VERSION ?= 1.50.0 +GOLANGCILINT_VERSION = 2.1.2 GO_TEST_PARALLEL := $(shell echo $$(( $(NPROCS) / 2 ))) GO_STATIC_PACKAGES = $(GO_PROJECT)/cmd/provider GO_LDFLAGS += -X $(GO_PROJECT)/pkg/version.Version=$(VERSION) -GO_SUBDIRS += cmd internal apis +GO_SUBDIRS += cmd internal apis generate GO111MODULE = on -include build/makelib/golang.mk @@ -26,13 +24,10 @@ GO111MODULE = on # ==================================================================================== # Setup Kubernetes tools -# Uncomment below to override the versions from the build module -#KIND_VERSION = v0.27.0 +KIND_VERSION = v0.29.0 UP_VERSION = v0.34.2 UP_CHANNEL = stable UPTEST_VERSION = v1.1.2 -UPTEST_LOCAL_VERSION = v0.13.0 -UPTEST_LOCAL_CHANNEL = stable KUSTOMIZE_VERSION = v5.3.0 YQ_VERSION = v4.40.5 CROSSPLANE_VERSION = 1.17.1 @@ -43,16 +38,6 @@ export UP_CHANNEL := $(UP_CHANNEL) -include build/makelib/k8s_tools.mk -# uptest download and install -UPTEST_LOCAL := $(TOOLS_HOST_DIR)/uptest-$(UPTEST_LOCAL_VERSION) - -$(UPTEST_LOCAL): - @$(INFO) installing uptest $(UPTEST_LOCAL) - @mkdir -p $(TOOLS_HOST_DIR) - @curl -fsSLo $(UPTEST_LOCAL) https://s3.us-west-2.amazonaws.com/crossplane.uptest.releases/$(UPTEST_LOCAL_CHANNEL)/$(UPTEST_LOCAL_VERSION)/bin/$(SAFEHOST_PLATFORM)/uptest || $(FAIL) - @chmod +x $(UPTEST_LOCAL) - @$(OK) installing uptest $(UPTEST_LOCAL) - # Setup Images REGISTRY_ORGS ?= xpkg.upbound.io/upbound IMAGES = provider-terraform diff --git a/apis/terraform.go b/apis/cluster/terraform.go similarity index 87% rename from apis/terraform.go rename to apis/cluster/terraform.go index 3e5452f..95cced9 100644 --- a/apis/terraform.go +++ b/apis/cluster/terraform.go @@ -14,13 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package apis contains Kubernetes API for the terraform provider. -package apis +// Package cluster contains Kubernetes API for the terraform provider. +package cluster import ( "k8s.io/apimachinery/pkg/runtime" - "github.com/upbound/provider-terraform/apis/v1beta1" + "github.com/upbound/provider-terraform/apis/cluster/v1beta1" ) func init() { diff --git a/apis/v1beta1/doc.go b/apis/cluster/v1beta1/doc.go similarity index 100% rename from apis/v1beta1/doc.go rename to apis/cluster/v1beta1/doc.go diff --git a/apis/v1beta1/register.go b/apis/cluster/v1beta1/register.go similarity index 100% rename from apis/v1beta1/register.go rename to apis/cluster/v1beta1/register.go diff --git a/apis/v1beta1/types.go b/apis/cluster/v1beta1/types.go similarity index 98% rename from apis/v1beta1/types.go rename to apis/cluster/v1beta1/types.go index 3a3a631..06baf94 100644 --- a/apis/v1beta1/types.go +++ b/apis/cluster/v1beta1/types.go @@ -19,7 +19,7 @@ package v1beta1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" ) // A ProviderConfigSpec defines the desired state of a ProviderConfig. diff --git a/apis/v1beta1/workspace_types.go b/apis/cluster/v1beta1/workspace_types.go similarity index 99% rename from apis/v1beta1/workspace_types.go rename to apis/cluster/v1beta1/workspace_types.go index a3220c9..2d2a2fb 100644 --- a/apis/v1beta1/workspace_types.go +++ b/apis/cluster/v1beta1/workspace_types.go @@ -17,7 +17,7 @@ limitations under the License. package v1beta1 import ( - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" extensionsV1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" diff --git a/apis/cluster/v1beta1/zz_generated.deepcopy.go b/apis/cluster/v1beta1/zz_generated.deepcopy.go new file mode 100644 index 0000000..6ca207d --- /dev/null +++ b/apis/cluster/v1beta1/zz_generated.deepcopy.go @@ -0,0 +1,471 @@ +//go:build !ignore_autogenerated + +/* +Copyright 2020 The Crossplane 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. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EnvVar) DeepCopyInto(out *EnvVar) { + *out = *in + if in.ConfigMapKeyReference != nil { + in, out := &in.ConfigMapKeyReference, &out.ConfigMapKeyReference + *out = new(KeyReference) + **out = **in + } + if in.SecretKeyReference != nil { + in, out := &in.SecretKeyReference, &out.SecretKeyReference + *out = new(KeyReference) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvVar. +func (in *EnvVar) DeepCopy() *EnvVar { + if in == nil { + return nil + } + out := new(EnvVar) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KeyReference) DeepCopyInto(out *KeyReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeyReference. +func (in *KeyReference) DeepCopy() *KeyReference { + if in == nil { + return nil + } + out := new(KeyReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProviderConfig) DeepCopyInto(out *ProviderConfig) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderConfig. +func (in *ProviderConfig) DeepCopy() *ProviderConfig { + if in == nil { + return nil + } + out := new(ProviderConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ProviderConfig) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProviderConfigList) DeepCopyInto(out *ProviderConfigList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ProviderConfig, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderConfigList. +func (in *ProviderConfigList) DeepCopy() *ProviderConfigList { + if in == nil { + return nil + } + out := new(ProviderConfigList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ProviderConfigList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProviderConfigSpec) DeepCopyInto(out *ProviderConfigSpec) { + *out = *in + if in.Credentials != nil { + in, out := &in.Credentials, &out.Credentials + *out = make([]ProviderCredentials, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Configuration != nil { + in, out := &in.Configuration, &out.Configuration + *out = new(string) + **out = **in + } + if in.BackendFile != nil { + in, out := &in.BackendFile, &out.BackendFile + *out = new(string) + **out = **in + } + if in.PluginCache != nil { + in, out := &in.PluginCache, &out.PluginCache + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderConfigSpec. +func (in *ProviderConfigSpec) DeepCopy() *ProviderConfigSpec { + if in == nil { + return nil + } + out := new(ProviderConfigSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProviderConfigStatus) DeepCopyInto(out *ProviderConfigStatus) { + *out = *in + in.ProviderConfigStatus.DeepCopyInto(&out.ProviderConfigStatus) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderConfigStatus. +func (in *ProviderConfigStatus) DeepCopy() *ProviderConfigStatus { + if in == nil { + return nil + } + out := new(ProviderConfigStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProviderConfigUsage) DeepCopyInto(out *ProviderConfigUsage) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.ProviderConfigUsage.DeepCopyInto(&out.ProviderConfigUsage) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderConfigUsage. +func (in *ProviderConfigUsage) DeepCopy() *ProviderConfigUsage { + if in == nil { + return nil + } + out := new(ProviderConfigUsage) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ProviderConfigUsage) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProviderConfigUsageList) DeepCopyInto(out *ProviderConfigUsageList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ProviderConfigUsage, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderConfigUsageList. +func (in *ProviderConfigUsageList) DeepCopy() *ProviderConfigUsageList { + if in == nil { + return nil + } + out := new(ProviderConfigUsageList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ProviderConfigUsageList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProviderCredentials) DeepCopyInto(out *ProviderCredentials) { + *out = *in + in.CommonCredentialSelectors.DeepCopyInto(&out.CommonCredentialSelectors) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderCredentials. +func (in *ProviderCredentials) DeepCopy() *ProviderCredentials { + if in == nil { + return nil + } + out := new(ProviderCredentials) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Var) DeepCopyInto(out *Var) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Var. +func (in *Var) DeepCopy() *Var { + if in == nil { + return nil + } + out := new(Var) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VarFile) DeepCopyInto(out *VarFile) { + *out = *in + if in.Format != nil { + in, out := &in.Format, &out.Format + *out = new(FileFormat) + **out = **in + } + if in.ConfigMapKeyReference != nil { + in, out := &in.ConfigMapKeyReference, &out.ConfigMapKeyReference + *out = new(KeyReference) + **out = **in + } + if in.SecretKeyReference != nil { + in, out := &in.SecretKeyReference, &out.SecretKeyReference + *out = new(KeyReference) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VarFile. +func (in *VarFile) DeepCopy() *VarFile { + if in == nil { + return nil + } + out := new(VarFile) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Workspace) DeepCopyInto(out *Workspace) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Workspace. +func (in *Workspace) DeepCopy() *Workspace { + if in == nil { + return nil + } + out := new(Workspace) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Workspace) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkspaceList) DeepCopyInto(out *WorkspaceList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Workspace, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceList. +func (in *WorkspaceList) DeepCopy() *WorkspaceList { + if in == nil { + return nil + } + out := new(WorkspaceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *WorkspaceList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkspaceObservation) DeepCopyInto(out *WorkspaceObservation) { + *out = *in + if in.Outputs != nil { + in, out := &in.Outputs, &out.Outputs + *out = make(map[string]v1.JSON, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceObservation. +func (in *WorkspaceObservation) DeepCopy() *WorkspaceObservation { + if in == nil { + return nil + } + out := new(WorkspaceObservation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkspaceParameters) DeepCopyInto(out *WorkspaceParameters) { + *out = *in + if in.Env != nil { + in, out := &in.Env, &out.Env + *out = make([]EnvVar, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Vars != nil { + in, out := &in.Vars, &out.Vars + *out = make([]Var, len(*in)) + copy(*out, *in) + } + if in.VarMap != nil { + in, out := &in.VarMap, &out.VarMap + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.VarFiles != nil { + in, out := &in.VarFiles, &out.VarFiles + *out = make([]VarFile, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.InitArgs != nil { + in, out := &in.InitArgs, &out.InitArgs + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.PlanArgs != nil { + in, out := &in.PlanArgs, &out.PlanArgs + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ApplyArgs != nil { + in, out := &in.ApplyArgs, &out.ApplyArgs + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.DestroyArgs != nil { + in, out := &in.DestroyArgs, &out.DestroyArgs + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceParameters. +func (in *WorkspaceParameters) DeepCopy() *WorkspaceParameters { + if in == nil { + return nil + } + out := new(WorkspaceParameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkspaceSpec) DeepCopyInto(out *WorkspaceSpec) { + *out = *in + in.ResourceSpec.DeepCopyInto(&out.ResourceSpec) + in.ForProvider.DeepCopyInto(&out.ForProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceSpec. +func (in *WorkspaceSpec) DeepCopy() *WorkspaceSpec { + if in == nil { + return nil + } + out := new(WorkspaceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkspaceStatus) DeepCopyInto(out *WorkspaceStatus) { + *out = *in + in.ResourceStatus.DeepCopyInto(&out.ResourceStatus) + in.AtProvider.DeepCopyInto(&out.AtProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceStatus. +func (in *WorkspaceStatus) DeepCopy() *WorkspaceStatus { + if in == nil { + return nil + } + out := new(WorkspaceStatus) + in.DeepCopyInto(out) + return out +} diff --git a/apis/v1beta1/zz_generated.managed.go b/apis/cluster/v1beta1/zz_generated.managed.go similarity index 83% rename from apis/v1beta1/zz_generated.managed.go rename to apis/cluster/v1beta1/zz_generated.managed.go index 43cafe9..ce489b3 100644 --- a/apis/v1beta1/zz_generated.managed.go +++ b/apis/cluster/v1beta1/zz_generated.managed.go @@ -17,7 +17,7 @@ limitations under the License. package v1beta1 -import xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" +import xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" // GetCondition of this Workspace. func (mg *Workspace) GetCondition(ct xpv1.ConditionType) xpv1.Condition { @@ -39,11 +39,6 @@ func (mg *Workspace) GetProviderConfigReference() *xpv1.Reference { return mg.Spec.ProviderConfigReference } -// GetPublishConnectionDetailsTo of this Workspace. -func (mg *Workspace) GetPublishConnectionDetailsTo() *xpv1.PublishConnectionDetailsTo { - return mg.Spec.PublishConnectionDetailsTo -} - // GetWriteConnectionSecretToReference of this Workspace. func (mg *Workspace) GetWriteConnectionSecretToReference() *xpv1.SecretReference { return mg.Spec.WriteConnectionSecretToReference @@ -69,11 +64,6 @@ func (mg *Workspace) SetProviderConfigReference(r *xpv1.Reference) { mg.Spec.ProviderConfigReference = r } -// SetPublishConnectionDetailsTo of this Workspace. -func (mg *Workspace) SetPublishConnectionDetailsTo(r *xpv1.PublishConnectionDetailsTo) { - mg.Spec.PublishConnectionDetailsTo = r -} - // SetWriteConnectionSecretToReference of this Workspace. func (mg *Workspace) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) { mg.Spec.WriteConnectionSecretToReference = r diff --git a/apis/v1beta1/zz_generated.managedlist.go b/apis/cluster/v1beta1/zz_generated.managedlist.go similarity index 91% rename from apis/v1beta1/zz_generated.managedlist.go rename to apis/cluster/v1beta1/zz_generated.managedlist.go index c3001f4..b67b638 100644 --- a/apis/v1beta1/zz_generated.managedlist.go +++ b/apis/cluster/v1beta1/zz_generated.managedlist.go @@ -17,7 +17,7 @@ limitations under the License. package v1beta1 -import resource "github.com/crossplane/crossplane-runtime/pkg/resource" +import resource "github.com/crossplane/crossplane-runtime/v2/pkg/resource" // GetItems of this WorkspaceList. func (l *WorkspaceList) GetItems() []resource.Managed { diff --git a/apis/v1beta1/zz_generated.pc.go b/apis/cluster/v1beta1/zz_generated.pc.go similarity index 93% rename from apis/v1beta1/zz_generated.pc.go rename to apis/cluster/v1beta1/zz_generated.pc.go index 83a7b07..2db1e80 100644 --- a/apis/v1beta1/zz_generated.pc.go +++ b/apis/cluster/v1beta1/zz_generated.pc.go @@ -17,7 +17,7 @@ limitations under the License. package v1beta1 -import xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" +import xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" // GetCondition of this ProviderConfig. func (p *ProviderConfig) GetCondition(ct xpv1.ConditionType) xpv1.Condition { diff --git a/apis/v1beta1/zz_generated.pcu.go b/apis/cluster/v1beta1/zz_generated.pcu.go similarity index 94% rename from apis/v1beta1/zz_generated.pcu.go rename to apis/cluster/v1beta1/zz_generated.pcu.go index a45c12b..ceff13a 100644 --- a/apis/v1beta1/zz_generated.pcu.go +++ b/apis/cluster/v1beta1/zz_generated.pcu.go @@ -17,7 +17,7 @@ limitations under the License. package v1beta1 -import xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" +import xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" // GetProviderConfigReference of this ProviderConfigUsage. func (p *ProviderConfigUsage) GetProviderConfigReference() xpv1.Reference { diff --git a/apis/v1beta1/zz_generated.pculist.go b/apis/cluster/v1beta1/zz_generated.pculist.go similarity index 92% rename from apis/v1beta1/zz_generated.pculist.go rename to apis/cluster/v1beta1/zz_generated.pculist.go index d36470b..ce87534 100644 --- a/apis/v1beta1/zz_generated.pculist.go +++ b/apis/cluster/v1beta1/zz_generated.pculist.go @@ -17,7 +17,7 @@ limitations under the License. package v1beta1 -import resource "github.com/crossplane/crossplane-runtime/pkg/resource" +import resource "github.com/crossplane/crossplane-runtime/v2/pkg/resource" // GetItems of this ProviderConfigUsageList. func (p *ProviderConfigUsageList) GetItems() []resource.ProviderConfigUsage { diff --git a/apis/namespaced/terraform.go b/apis/namespaced/terraform.go new file mode 100644 index 0000000..5e29f2e --- /dev/null +++ b/apis/namespaced/terraform.go @@ -0,0 +1,39 @@ +/* +Copyright 2020 The Crossplane 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 namespaced contains Kubernetes API for the terraform provider. +package namespaced + +import ( + "k8s.io/apimachinery/pkg/runtime" + + "github.com/upbound/provider-terraform/apis/namespaced/v1beta1" +) + +func init() { + // Register the types with the Scheme so the components can map objects to GroupVersionKinds and back + AddToSchemes = append(AddToSchemes, + v1beta1.SchemeBuilder.AddToScheme, + ) +} + +// AddToSchemes may be used to add all resources defined in the project to a Scheme +var AddToSchemes runtime.SchemeBuilder + +// AddToScheme adds all Resources to the Scheme +func AddToScheme(s *runtime.Scheme) error { + return AddToSchemes.AddToScheme(s) +} diff --git a/apis/namespaced/v1beta1/doc.go b/apis/namespaced/v1beta1/doc.go new file mode 100644 index 0000000..ea0db57 --- /dev/null +++ b/apis/namespaced/v1beta1/doc.go @@ -0,0 +1,21 @@ +/* +Copyright 2020 The Crossplane 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 v1beta1 contains the core resources of the Terraform provider. +// +kubebuilder:object:generate=true +// +groupName=tf.m.upbound.io +// +versionName=v1beta1 +package v1beta1 diff --git a/apis/namespaced/v1beta1/register.go b/apis/namespaced/v1beta1/register.go new file mode 100644 index 0000000..114e247 --- /dev/null +++ b/apis/namespaced/v1beta1/register.go @@ -0,0 +1,79 @@ +/* +Copyright 2020 The Crossplane 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 v1beta1 + +import ( + "reflect" + + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +// Package type metadata. +const ( + Group = "tf.m.upbound.io" + Version = "v1beta1" +) + +var ( + // SchemeGroupVersion is group version used to register these objects + SchemeGroupVersion = schema.GroupVersion{Group: Group, Version: Version} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion} +) + +// ProviderConfig type metadata. +var ( + ProviderConfigKind = reflect.TypeOf(ProviderConfig{}).Name() + ProviderConfigGroupKind = schema.GroupKind{Group: Group, Kind: ProviderConfigKind}.String() + ProviderConfigKindAPIVersion = ProviderConfigKind + "." + SchemeGroupVersion.String() + ProviderConfigGroupVersionKind = SchemeGroupVersion.WithKind(ProviderConfigKind) +) + +// ProviderConfigUsage type metadata. +var ( + ProviderConfigUsageKind = reflect.TypeOf(ProviderConfigUsage{}).Name() + ProviderConfigUsageGroupKind = schema.GroupKind{Group: Group, Kind: ProviderConfigUsageKind}.String() + ProviderConfigUsageKindAPIVersion = ProviderConfigUsageKind + "." + SchemeGroupVersion.String() + ProviderConfigUsageGroupVersionKind = SchemeGroupVersion.WithKind(ProviderConfigUsageKind) + + ProviderConfigUsageListKind = reflect.TypeOf(ProviderConfigUsageList{}).Name() + ProviderConfigUsageListGroupKind = schema.GroupKind{Group: Group, Kind: ProviderConfigUsageListKind}.String() + ProviderConfigUsageListKindAPIVersion = ProviderConfigUsageListKind + "." + SchemeGroupVersion.String() + ProviderConfigUsageListGroupVersionKind = SchemeGroupVersion.WithKind(ProviderConfigUsageListKind) + + ClusterProviderConfigKind = reflect.TypeOf(ClusterProviderConfig{}).Name() + ClusterProviderConfigGroupKind = schema.GroupKind{Group: Group, Kind: ClusterProviderConfigKind}.String() + ClusterProviderConfigKindAPIVersion = ClusterProviderConfigKind + "." + SchemeGroupVersion.String() + ClusterProviderConfigGroupVersionKind = SchemeGroupVersion.WithKind(ClusterProviderConfigKind) +) + +// Workspace type metadata. +var ( + WorkspaceKind = reflect.TypeOf(Workspace{}).Name() + WorkspaceGroupKind = schema.GroupKind{Group: Group, Kind: WorkspaceKind}.String() + WorkspaceKindAPIVersion = WorkspaceKind + "." + SchemeGroupVersion.String() + WorkspaceGroupVersionKind = SchemeGroupVersion.WithKind(WorkspaceKind) +) + +func init() { + SchemeBuilder.Register(&ProviderConfig{}, &ProviderConfigList{}) + SchemeBuilder.Register(&ProviderConfigUsage{}, &ProviderConfigUsageList{}) + SchemeBuilder.Register(&Workspace{}, &WorkspaceList{}) + SchemeBuilder.Register(&ClusterProviderConfig{}, &ClusterProviderConfigList{}) +} diff --git a/apis/namespaced/v1beta1/types.go b/apis/namespaced/v1beta1/types.go new file mode 100644 index 0000000..4471c7f --- /dev/null +++ b/apis/namespaced/v1beta1/types.go @@ -0,0 +1,140 @@ +/* +Copyright 2020 The Crossplane 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 v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpv2 "github.com/crossplane/crossplane-runtime/v2/apis/common/v2" +) + +// A ProviderConfigSpec defines the desired state of a ProviderConfig. +type ProviderConfigSpec struct { + // Credentials required to authenticate to this provider. + // +optional + Credentials []ProviderCredentials `json:"credentials"` + + // Configuration that should be injected into all workspaces that use + // this provider config, expressed as inline HCL. This can be used to + // automatically inject Terraform provider configuration blocks. + // +optional + Configuration *string `json:"configuration,omitempty"` + + // Terraform backend file configuration content, + // it has the contents of the backend block as top-level attributes, + // without the need to wrap it in another terraform or backend block. + // More details at https://developer.hashicorp.com/terraform/language/settings/backends/configuration#file. + // +optional + BackendFile *string `json:"backendFile,omitempty"` + + // PluginCache enables terraform provider plugin caching mechanism + // https://developer.hashicorp.com/terraform/cli/config/config-file#provider-plugin-cache + // +optional + // +kubebuilder:default=true + PluginCache *bool `json:"pluginCache,omitempty"` +} + +// ProviderCredentials required to authenticate. +type ProviderCredentials struct { + // Filename (relative to main.tf) to which these provider credentials + // should be written. + Filename string `json:"filename"` + + // Source of the provider credentials. + // +kubebuilder:validation:Enum=None;Secret;Environment;Filesystem + Source xpv1.CredentialsSource `json:"source"` + + xpv1.CommonCredentialSelectors `json:",inline"` +} + +// A ProviderConfigStatus reflects the observed state of a ProviderConfig. +type ProviderConfigStatus struct { + xpv1.ProviderConfigStatus `json:",inline"` +} + +// +kubebuilder:object:root=true + +// A ProviderConfig configures a Terraform provider. +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:printcolumn:name="SECRET-NAME",type="string",JSONPath=".spec.credentials.secretRef.name",priority=1 +// +kubebuilder:resource:scope=Namespaced,categories={crossplane,provider,terraform} +type ProviderConfig struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ProviderConfigSpec `json:"spec"` + Status ProviderConfigStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// ProviderConfigList contains a list of ProviderConfig. +type ProviderConfigList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ProviderConfig `json:"items"` +} + +// +kubebuilder:object:root=true + +// A ProviderConfigUsage indicates that a resource is using a ProviderConfig. +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:printcolumn:name="CONFIG-NAME",type="string",JSONPath=".providerConfigRef.name" +// +kubebuilder:printcolumn:name="RESOURCE-KIND",type="string",JSONPath=".resourceRef.kind" +// +kubebuilder:printcolumn:name="RESOURCE-NAME",type="string",JSONPath=".resourceRef.name" +// +kubebuilder:resource:scope=Namespaced,categories={crossplane,provider,terraform} +type ProviderConfigUsage struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + xpv2.TypedProviderConfigUsage `json:",inline"` +} + +// +kubebuilder:object:root=true + +// ProviderConfigUsageList contains a list of ProviderConfigUsage +type ProviderConfigUsageList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ProviderConfigUsage `json:"items"` +} + +// +kubebuilder:object:root=true + +// A ClusterProviderConfig configures a Terraform provider. +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:printcolumn:name="SECRET-NAME",type="string",JSONPath=".spec.credentials.secretRef.name",priority=1 +// +kubebuilder:resource:scope=Cluster,categories={crossplane,provider,terraform} +type ClusterProviderConfig struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ProviderConfigSpec `json:"spec"` + Status ProviderConfigStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// ClusterProviderConfigList contains a list of ClusterProviderConfig. +type ClusterProviderConfigList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ClusterProviderConfig `json:"items"` +} diff --git a/apis/namespaced/v1beta1/workspace_types.go b/apis/namespaced/v1beta1/workspace_types.go new file mode 100644 index 0000000..318cd80 --- /dev/null +++ b/apis/namespaced/v1beta1/workspace_types.go @@ -0,0 +1,203 @@ +/* +Copyright 2020 The Crossplane 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 v1beta1 + +import ( + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpv2 "github.com/crossplane/crossplane-runtime/v2/apis/common/v2" + extensionsV1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// A Var represents a Terraform configuration variable. +type Var struct { + Key string `json:"key"` + Value string `json:"value"` +} + +// A VarFileSource specifies the source of a Terraform vars file. +// +kubebuilder:validation:Enum=ConfigMapKey;SecretKey +type VarFileSource string + +// Vars file sources. +const ( + VarFileSourceConfigMapKey VarFileSource = "ConfigMapKey" + VarFileSourceSecretKey VarFileSource = "SecretKey" +) + +// A FileFormat specifies the format of a Terraform file. +// +kubebuilder:validation:Enum=HCL;JSON +type FileFormat string + +// Vars file formats. +var ( + FileFormatHCL FileFormat = "HCL" + FileFormatJSON FileFormat = "JSON" +) + +// A VarFile is a file containing many Terraform variables. +type VarFile struct { + // Source of this vars file. + Source VarFileSource `json:"source"` + + // Format of this vars file. + // +kubebuilder:default=HCL + // +optional + Format *FileFormat `json:"format,omitempty"` + + // A ConfigMap key containing the vars file. + // +optional + ConfigMapKeyReference *KeyReference `json:"configMapKeyRef,omitempty"` + + // A Secret key containing the vars file. + // +optional + SecretKeyReference *KeyReference `json:"secretKeyRef,omitempty"` +} + +// An EnvVar specifies an environment variable to be set for the workspace. +type EnvVar struct { + Name string `json:"name"` + Value string `json:"value,omitempty"` + + // A ConfigMap key containing the desired env var value. + ConfigMapKeyReference *KeyReference `json:"configMapKeyRef,omitempty"` + + // A Secret key containing the desired env var value. + SecretKeyReference *KeyReference `json:"secretKeyRef,omitempty"` +} + +// A KeyReference references a key within a Secret or a ConfigMap. +type KeyReference struct { + // Name of the referenced resource. + Name string `json:"name"` + + // Key within the referenced resource. + Key string `json:"key"` +} + +// A ModuleSource represents the source of a Terraform module. +// +kubebuilder:validation:Enum=Remote;Inline;Flux +type ModuleSource string + +// Module sources. +const ( + ModuleSourceRemote ModuleSource = "Remote" + ModuleSourceInline ModuleSource = "Inline" + ModuleSourceFlux ModuleSource = "Flux" +) + +// WorkspaceParameters are the configurable fields of a Workspace. +type WorkspaceParameters struct { + // The root module of this workspace; i.e. the module containing its main.tf + // file. When the workspace's source is 'Remote' (the default) this can be + // any address supported by terraform init -from-module, for example a git + // repository or an S3 bucket. When the workspace's source is 'Inline' the + // content of a simple main.tf or main.tf.json file may be written inline. + // When the workspace's source is 'Flux', use the FluxSourceKind::namespace/name format. + // Example: + // Module: "GitRepository::my-namespace/my-repo" + Module string `json:"module"` + + // Specifies the format of the inline Terraform content + // if Source is 'Inline' + InlineFormat FileFormat `json:"inlineFormat,omitempty"` + + // Source of the root module of this workspace. + Source ModuleSource `json:"source"` + + // Entrypoint for `terraform init` within the module + // +kubebuilder:default="" + // +optional + Entrypoint string `json:"entrypoint"` + + // Environment variables. + // +optional + Env []EnvVar `json:"env,omitempty"` + + // Configuration variables. + // +optional + Vars []Var `json:"vars,omitempty"` + + // Terraform Variable Map. Should be a valid JSON representation of the input vars + // +optional + VarMap *runtime.RawExtension `json:"varmap,omitempty"` + + // Files of configuration variables. Explicitly declared vars take + // precedence. + // +optional + VarFiles []VarFile `json:"varFiles,omitempty"` + + // Arguments to be included in the terraform init CLI command + InitArgs []string `json:"initArgs,omitempty"` + + // Arguments to be included in the terraform plan CLI command + PlanArgs []string `json:"planArgs,omitempty"` + + // Arguments to be included in the terraform apply CLI command + ApplyArgs []string `json:"applyArgs,omitempty"` + + // Arguments to be included in the terraform destroy CLI command + DestroyArgs []string `json:"destroyArgs,omitempty"` + + // Boolean value to indicate CLI logging of terraform execution is enabled or not + // +optional + EnableTerraformCLILogging bool `json:"enableTerraformCLILogging,omitempty"` +} + +// WorkspaceObservation are the observable fields of a Workspace. +type WorkspaceObservation struct { + Checksum string `json:"checksum,omitempty"` + Outputs map[string]extensionsV1.JSON `json:"outputs,omitempty"` +} + +// A WorkspaceSpec defines the desired state of a Workspace. +type WorkspaceSpec struct { + xpv2.ManagedResourceSpec `json:",inline"` + ForProvider WorkspaceParameters `json:"forProvider"` +} + +// A WorkspaceStatus represents the observed state of a Workspace. +type WorkspaceStatus struct { + xpv1.ResourceStatus `json:",inline"` + AtProvider WorkspaceObservation `json:"atProvider,omitempty"` +} + +// +kubebuilder:object:root=true + +// A Workspace of Terraform Configuration. +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status" +// +kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:resource:scope=Namespaced,categories={crossplane,managed,terraform} +type Workspace struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec WorkspaceSpec `json:"spec"` + Status WorkspaceStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// WorkspaceList contains a list of Workspace +type WorkspaceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Workspace `json:"items"` +} diff --git a/apis/v1beta1/zz_generated.deepcopy.go b/apis/namespaced/v1beta1/zz_generated.deepcopy.go similarity index 89% rename from apis/v1beta1/zz_generated.deepcopy.go rename to apis/namespaced/v1beta1/zz_generated.deepcopy.go index f9f3e56..8049195 100644 --- a/apis/v1beta1/zz_generated.deepcopy.go +++ b/apis/namespaced/v1beta1/zz_generated.deepcopy.go @@ -25,6 +25,65 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterProviderConfig) DeepCopyInto(out *ClusterProviderConfig) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterProviderConfig. +func (in *ClusterProviderConfig) DeepCopy() *ClusterProviderConfig { + if in == nil { + return nil + } + out := new(ClusterProviderConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterProviderConfig) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterProviderConfigList) DeepCopyInto(out *ClusterProviderConfigList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ClusterProviderConfig, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterProviderConfigList. +func (in *ClusterProviderConfigList) DeepCopy() *ClusterProviderConfigList { + if in == nil { + return nil + } + out := new(ClusterProviderConfigList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterProviderConfigList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EnvVar) DeepCopyInto(out *EnvVar) { *out = *in @@ -182,7 +241,7 @@ func (in *ProviderConfigUsage) DeepCopyInto(out *ProviderConfigUsage) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.ProviderConfigUsage.DeepCopyInto(&out.ProviderConfigUsage) + in.TypedProviderConfigUsage.DeepCopyInto(&out.TypedProviderConfigUsage) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderConfigUsage. @@ -251,97 +310,6 @@ func (in *ProviderCredentials) DeepCopy() *ProviderCredentials { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *StoreConfig) DeepCopyInto(out *StoreConfig) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StoreConfig. -func (in *StoreConfig) DeepCopy() *StoreConfig { - if in == nil { - return nil - } - out := new(StoreConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *StoreConfig) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *StoreConfigList) DeepCopyInto(out *StoreConfigList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]StoreConfig, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StoreConfigList. -func (in *StoreConfigList) DeepCopy() *StoreConfigList { - if in == nil { - return nil - } - out := new(StoreConfigList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *StoreConfigList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *StoreConfigSpec) DeepCopyInto(out *StoreConfigSpec) { - *out = *in - in.SecretStoreConfig.DeepCopyInto(&out.SecretStoreConfig) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StoreConfigSpec. -func (in *StoreConfigSpec) DeepCopy() *StoreConfigSpec { - if in == nil { - return nil - } - out := new(StoreConfigSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *StoreConfigStatus) DeepCopyInto(out *StoreConfigStatus) { - *out = *in - in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StoreConfigStatus. -func (in *StoreConfigStatus) DeepCopy() *StoreConfigStatus { - if in == nil { - return nil - } - out := new(StoreConfigStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Var) DeepCopyInto(out *Var) { *out = *in @@ -530,7 +498,7 @@ func (in *WorkspaceParameters) DeepCopy() *WorkspaceParameters { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WorkspaceSpec) DeepCopyInto(out *WorkspaceSpec) { *out = *in - in.ResourceSpec.DeepCopyInto(&out.ResourceSpec) + in.ManagedResourceSpec.DeepCopyInto(&out.ManagedResourceSpec) in.ForProvider.DeepCopyInto(&out.ForProvider) } diff --git a/apis/namespaced/v1beta1/zz_generated.managed.go b/apis/namespaced/v1beta1/zz_generated.managed.go new file mode 100644 index 0000000..3c3227b --- /dev/null +++ b/apis/namespaced/v1beta1/zz_generated.managed.go @@ -0,0 +1,60 @@ +/* +Copyright 2020 The Crossplane 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. +*/ +// Code generated by angryjet. DO NOT EDIT. + +package v1beta1 + +import xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + +// GetCondition of this Workspace. +func (mg *Workspace) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return mg.Status.GetCondition(ct) +} + +// GetManagementPolicies of this Workspace. +func (mg *Workspace) GetManagementPolicies() xpv1.ManagementPolicies { + return mg.Spec.ManagementPolicies +} + +// GetProviderConfigReference of this Workspace. +func (mg *Workspace) GetProviderConfigReference() *xpv1.ProviderConfigReference { + return mg.Spec.ProviderConfigReference +} + +// GetWriteConnectionSecretToReference of this Workspace. +func (mg *Workspace) GetWriteConnectionSecretToReference() *xpv1.LocalSecretReference { + return mg.Spec.WriteConnectionSecretToReference +} + +// SetConditions of this Workspace. +func (mg *Workspace) SetConditions(c ...xpv1.Condition) { + mg.Status.SetConditions(c...) +} + +// SetManagementPolicies of this Workspace. +func (mg *Workspace) SetManagementPolicies(r xpv1.ManagementPolicies) { + mg.Spec.ManagementPolicies = r +} + +// SetProviderConfigReference of this Workspace. +func (mg *Workspace) SetProviderConfigReference(r *xpv1.ProviderConfigReference) { + mg.Spec.ProviderConfigReference = r +} + +// SetWriteConnectionSecretToReference of this Workspace. +func (mg *Workspace) SetWriteConnectionSecretToReference(r *xpv1.LocalSecretReference) { + mg.Spec.WriteConnectionSecretToReference = r +} diff --git a/apis/namespaced/v1beta1/zz_generated.managedlist.go b/apis/namespaced/v1beta1/zz_generated.managedlist.go new file mode 100644 index 0000000..b67b638 --- /dev/null +++ b/apis/namespaced/v1beta1/zz_generated.managedlist.go @@ -0,0 +1,29 @@ +/* +Copyright 2020 The Crossplane 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. +*/ +// Code generated by angryjet. DO NOT EDIT. + +package v1beta1 + +import resource "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + +// GetItems of this WorkspaceList. +func (l *WorkspaceList) GetItems() []resource.Managed { + items := make([]resource.Managed, len(l.Items)) + for i := range l.Items { + items[i] = &l.Items[i] + } + return items +} diff --git a/apis/namespaced/v1beta1/zz_generated.pc.go b/apis/namespaced/v1beta1/zz_generated.pc.go new file mode 100644 index 0000000..08aca74 --- /dev/null +++ b/apis/namespaced/v1beta1/zz_generated.pc.go @@ -0,0 +1,60 @@ +/* +Copyright 2020 The Crossplane 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. +*/ +// Code generated by angryjet. DO NOT EDIT. + +package v1beta1 + +import xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + +// GetCondition of this ClusterProviderConfig. +func (p *ClusterProviderConfig) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return p.Status.GetCondition(ct) +} + +// GetUsers of this ClusterProviderConfig. +func (p *ClusterProviderConfig) GetUsers() int64 { + return p.Status.Users +} + +// SetConditions of this ClusterProviderConfig. +func (p *ClusterProviderConfig) SetConditions(c ...xpv1.Condition) { + p.Status.SetConditions(c...) +} + +// SetUsers of this ClusterProviderConfig. +func (p *ClusterProviderConfig) SetUsers(i int64) { + p.Status.Users = i +} + +// GetCondition of this ProviderConfig. +func (p *ProviderConfig) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return p.Status.GetCondition(ct) +} + +// GetUsers of this ProviderConfig. +func (p *ProviderConfig) GetUsers() int64 { + return p.Status.Users +} + +// SetConditions of this ProviderConfig. +func (p *ProviderConfig) SetConditions(c ...xpv1.Condition) { + p.Status.SetConditions(c...) +} + +// SetUsers of this ProviderConfig. +func (p *ProviderConfig) SetUsers(i int64) { + p.Status.Users = i +} diff --git a/apis/namespaced/v1beta1/zz_generated.pcu.go b/apis/namespaced/v1beta1/zz_generated.pcu.go new file mode 100644 index 0000000..4518f65 --- /dev/null +++ b/apis/namespaced/v1beta1/zz_generated.pcu.go @@ -0,0 +1,40 @@ +/* +Copyright 2020 The Crossplane 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. +*/ +// Code generated by angryjet. DO NOT EDIT. + +package v1beta1 + +import xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + +// GetProviderConfigReference of this ProviderConfigUsage. +func (p *ProviderConfigUsage) GetProviderConfigReference() xpv1.ProviderConfigReference { + return p.ProviderConfigReference +} + +// GetResourceReference of this ProviderConfigUsage. +func (p *ProviderConfigUsage) GetResourceReference() xpv1.TypedReference { + return p.ResourceReference +} + +// SetProviderConfigReference of this ProviderConfigUsage. +func (p *ProviderConfigUsage) SetProviderConfigReference(r xpv1.ProviderConfigReference) { + p.ProviderConfigReference = r +} + +// SetResourceReference of this ProviderConfigUsage. +func (p *ProviderConfigUsage) SetResourceReference(r xpv1.TypedReference) { + p.ResourceReference = r +} diff --git a/apis/namespaced/v1beta1/zz_generated.pculist.go b/apis/namespaced/v1beta1/zz_generated.pculist.go new file mode 100644 index 0000000..ce87534 --- /dev/null +++ b/apis/namespaced/v1beta1/zz_generated.pculist.go @@ -0,0 +1,29 @@ +/* +Copyright 2020 The Crossplane 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. +*/ +// Code generated by angryjet. DO NOT EDIT. + +package v1beta1 + +import resource "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + +// GetItems of this ProviderConfigUsageList. +func (p *ProviderConfigUsageList) GetItems() []resource.ProviderConfigUsage { + items := make([]resource.ProviderConfigUsage, len(p.Items)) + for i := range p.Items { + items[i] = &p.Items[i] + } + return items +} diff --git a/apis/v1beta1/storeconfig_types.go b/apis/v1beta1/storeconfig_types.go deleted file mode 100644 index 03658a8..0000000 --- a/apis/v1beta1/storeconfig_types.go +++ /dev/null @@ -1,100 +0,0 @@ -/* -Copyright 2020 The Crossplane 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 v1beta1 - -import ( - "reflect" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" -) - -// A StoreConfigSpec defines the desired state of a ProviderConfig. -type StoreConfigSpec struct { - xpv1.SecretStoreConfig `json:",inline"` -} - -// A StoreConfigStatus represents the status of a StoreConfig. -type StoreConfigStatus struct { - xpv1.ConditionedStatus `json:",inline"` -} - -// GetCondition of this StoreConfigStatus. -func (s *StoreConfigStatus) GetCondition(ct xpv1.ConditionType) xpv1.Condition { - return s.ConditionedStatus.GetCondition(ct) -} - -// SetConditions of this StoreConfigStatus. -func (s *StoreConfigStatus) SetConditions(c ...xpv1.Condition) { - s.ConditionedStatus.SetConditions(c...) -} - -// +kubebuilder:object:root=true - -// A StoreConfig configures how GCP controller should store connection details. -// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" -// +kubebuilder:printcolumn:name="TYPE",type="string",JSONPath=".spec.type" -// +kubebuilder:printcolumn:name="DEFAULT-SCOPE",type="string",JSONPath=".spec.defaultScope" -// +kubebuilder:resource:scope=Cluster,categories={crossplane,store,gcp} -// +kubebuilder:subresource:status -type StoreConfig struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec StoreConfigSpec `json:"spec"` - Status StoreConfigStatus `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true - -// StoreConfigList contains a list of StoreConfig -type StoreConfigList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []StoreConfig `json:"items"` -} - -// Note(turkenh): To be generated with AngryJet - -// GetStoreConfig returns SecretStoreConfig -func (in *StoreConfig) GetStoreConfig() xpv1.SecretStoreConfig { - return in.Spec.SecretStoreConfig -} - -// GetCondition of this StoreConfig. -func (in *StoreConfig) GetCondition(ct xpv1.ConditionType) xpv1.Condition { - return in.Status.GetCondition(ct) -} - -// SetConditions of this StoreConfig. -func (in *StoreConfig) SetConditions(c ...xpv1.Condition) { - in.Status.SetConditions(c...) -} - -// StoreConfig type metadata. -var ( - StoreConfigKind = reflect.TypeOf(StoreConfig{}).Name() - StoreConfigGroupKind = schema.GroupKind{Group: Group, Kind: StoreConfigKind}.String() - StoreConfigKindAPIVersion = StoreConfigKind + "." + SchemeGroupVersion.String() - StoreConfigGroupVersionKind = SchemeGroupVersion.WithKind(StoreConfigKind) -) - -func init() { - SchemeBuilder.Register(&StoreConfig{}, &StoreConfigList{}) -} diff --git a/build b/build index 1ed1933..d315554 160000 --- a/build +++ b/build @@ -1 +1 @@ -Subproject commit 1ed19332b947c449795fd016f3c21ee0a64930fd +Subproject commit d3155548bfab68fc8bea64c5526642b7b565ae33 diff --git a/cluster/test/setup.sh b/cluster/test/setup.sh index d7bce5f..667be63 100755 --- a/cluster/test/setup.sh +++ b/cluster/test/setup.sh @@ -29,12 +29,14 @@ if [[ -n "${UPTEST_CLOUD_CREDENTIALS:-}" ]]; then if [[ -n "${AWS:-}" ]]; then echo "Creating cloud credentials secret for AWS..." ${KUBECTL} -n upbound-system create secret generic aws-creds --from-literal=credentials="${AWS}" --dry-run=client -o yaml | ${KUBECTL} apply -f - - ${KUBECTL} apply -f "${scriptdir}/../../examples/providerconfig-aws.yaml" + ${KUBECTL} apply -f "${scriptdir}/../../examples/cluster/providerconfig-aws.yaml" + ${KUBECTL} apply -f "${scriptdir}/../../examples/namespaced/clusterproviderconfig-aws.yaml" fi if [[ -n "${GCP:-}" ]]; then echo "Creating cloud credentials secret for GCP..." ${KUBECTL} -n upbound-system create secret generic gcp-creds --from-literal=credentials="${GCP}" --dry-run=client -o yaml | ${KUBECTL} apply -f - - ${KUBECTL} apply -f "${scriptdir}/../../examples/providerconfig.yaml" + ${KUBECTL} apply -f "${scriptdir}/../../examples/cluster/providerconfig.yaml" + ${KUBECTL} apply -f "${scriptdir}/../../examples/namespaced/clusterproviderconfig.yaml" fi fi \ No newline at end of file diff --git a/cmd/provider/main.go b/cmd/provider/main.go index 8157742..48737fc 100644 --- a/cmd/provider/main.go +++ b/cmd/provider/main.go @@ -23,36 +23,42 @@ import ( "path/filepath" "time" - "github.com/crossplane/crossplane-runtime/pkg/certificates" - "github.com/crossplane/crossplane-runtime/pkg/controller" - "github.com/crossplane/crossplane-runtime/pkg/feature" - "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" - "github.com/crossplane/crossplane-runtime/pkg/statemetrics" + "github.com/alecthomas/kingpin/v2" + changelogsv1alpha1 "github.com/crossplane/crossplane-runtime/v2/apis/changelogs/proto/v1alpha1" + "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/errors" + "github.com/crossplane/crossplane-runtime/v2/pkg/feature" + "github.com/crossplane/crossplane-runtime/v2/pkg/gate" + "github.com/crossplane/crossplane-runtime/v2/pkg/logging" + "github.com/crossplane/crossplane-runtime/v2/pkg/ratelimiter" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/customresourcesgate" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/statemetrics" zapuber "go.uber.org/zap" "go.uber.org/zap/zapcore" - kingpin "gopkg.in/alecthomas/kingpin.v2" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/manager" "k8s.io/client-go/tools/leaderelection/resourcelock" - kerrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/metrics" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - "github.com/crossplane/crossplane-runtime/pkg/logging" - "github.com/crossplane/crossplane-runtime/pkg/ratelimiter" - "github.com/crossplane/crossplane-runtime/pkg/resource" - sourcev1 "github.com/fluxcd/source-controller/api/v1" sourcev1beta2 "github.com/fluxcd/source-controller/api/v1beta2" - "github.com/upbound/provider-terraform/apis" - "github.com/upbound/provider-terraform/apis/v1beta1" + authv1 "k8s.io/api/authorization/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + + apiscluster "github.com/upbound/provider-terraform/apis/cluster" + apisnamespaced "github.com/upbound/provider-terraform/apis/namespaced" "github.com/upbound/provider-terraform/internal/bootcheck" - workspace "github.com/upbound/provider-terraform/internal/controller" - "github.com/upbound/provider-terraform/internal/controller/features" + clusterworkspace "github.com/upbound/provider-terraform/internal/controller/cluster" + namespacedworkspace "github.com/upbound/provider-terraform/internal/controller/namespaced" + "github.com/upbound/provider-terraform/internal/features" ) func init() { @@ -64,20 +70,19 @@ func init() { func main() { var ( - app = kingpin.New(filepath.Base(os.Args[0]), "Terraform support for Crossplane.").DefaultEnvars() - debug = app.Flag("debug", "Run with debug logging.").Short('d').Bool() - syncInterval = app.Flag("sync", "Sync interval controls how often all resources will be double checked for drift.").Short('s').Default("1h").Duration() - pollInterval = app.Flag("poll", "Poll interval controls how often an individual resource should be checked for drift.").Default("10m").Duration() - pollStateMetricInterval = app.Flag("poll-state-metric", "State metric recording interval").Default("5s").Duration() - pollJitter = app.Flag("poll-jitter", "If non-zero, varies the poll interval by a random amount up to plus-or-minus this value.").Default("1m").Duration() - timeout = app.Flag("timeout", "Controls how long Terraform processes may run before they are killed.").Default("20m").Duration() - leaderElection = app.Flag("leader-election", "Use leader election for the controller manager.").Short('l').Default("false").Envar("LEADER_ELECTION").Bool() - maxReconcileRate = app.Flag("max-reconcile-rate", "The maximum number of concurrent reconciliation operations.").Default("1").Int() - namespace = app.Flag("namespace", "Namespace used to set as default scope in default secret store config.").Default("crossplane-system").Envar("POD_NAMESPACE").String() - enableExternalSecretStores = app.Flag("enable-external-secret-stores", "Enable support for ExternalSecretStores.").Default("false").Envar("ENABLE_EXTERNAL_SECRET_STORES").Bool() - enableManagementPolicies = app.Flag("enable-management-policies", "Enable support for Management Policies.").Default("true").Envar("ENABLE_MANAGEMENT_POLICIES").Bool() - essTLSCertsPath = app.Flag("ess-tls-cert-dir", "Path of ESS TLS certificates.").Envar("ESS_TLS_CERTS_DIR").String() - logEncoding = app.Flag("log-encoding", "Container logging output ending. Possible values: console, json").Default("console").Enum("console", "json") + app = kingpin.New(filepath.Base(os.Args[0]), "Terraform support for Crossplane.").DefaultEnvars() + debug = app.Flag("debug", "Run with debug logging.").Short('d').Bool() + syncInterval = app.Flag("sync", "Sync interval controls how often all resources will be double checked for drift.").Short('s').Default("1h").Duration() + pollInterval = app.Flag("poll", "Poll interval controls how often an individual resource should be checked for drift.").Default("10m").Duration() + pollStateMetricInterval = app.Flag("poll-state-metric", "State metric recording interval").Default("5s").Duration() + pollJitter = app.Flag("poll-jitter", "If non-zero, varies the poll interval by a random amount up to plus-or-minus this value.").Default("1m").Duration() + timeout = app.Flag("timeout", "Controls how long Terraform processes may run before they are killed.").Default("20m").Duration() + leaderElection = app.Flag("leader-election", "Use leader election for the controller manager.").Short('l').Default("false").Envar("LEADER_ELECTION").Bool() + maxReconcileRate = app.Flag("max-reconcile-rate", "The maximum number of concurrent reconciliation operations.").Default("1").Int() + enableManagementPolicies = app.Flag("enable-management-policies", "Enable support for Management Policies.").Default("true").Envar("ENABLE_MANAGEMENT_POLICIES").Bool() + enableChangeLogs = app.Flag("enable-changelogs", "Enable support for capturing change logs during reconciliation.").Default("false").Envar("ENABLE_CHANGE_LOGS").Bool() + changelogsSocketPath = app.Flag("changelogs-socket-path", "Path for changelogs socket (if enabled)").Default("/var/run/changelogs/changelogs.sock").Envar("CHANGELOGS_SOCKET_PATH").String() + logEncoding = app.Flag("log-encoding", "Container logging output ending. Possible values: console, json").Default("console").Enum("console", "json") ) kingpin.MustParse(app.Parse(os.Args[1:])) @@ -125,9 +130,11 @@ func main() { }) kingpin.FatalIfError(err, "Cannot create controller manager") - kingpin.FatalIfError(apis.AddToScheme(mgr.GetScheme()), "Cannot add terraform APIs to scheme") + kingpin.FatalIfError(apiscluster.AddToScheme(mgr.GetScheme()), "Cannot add terraform APIs to scheme") + kingpin.FatalIfError(apisnamespaced.AddToScheme(mgr.GetScheme()), "Cannot add terraform APIs to scheme") kingpin.FatalIfError(sourcev1.AddToScheme(mgr.GetScheme()), "Cannot add flux gitrepository APIs to scheme") kingpin.FatalIfError(sourcev1beta2.AddToScheme(mgr.GetScheme()), "Cannot add flux ocirepository APIs to scheme") + kingpin.FatalIfError(apiextensionsv1.AddToScheme(mgr.GetScheme()), "Cannot register k8s apiextensions APIs to scheme") metricRecorder := managed.NewMRMetricRecorder() stateMetrics := statemetrics.NewMRStateMetrics() @@ -135,7 +142,20 @@ func main() { metrics.Registry.MustRegister(metricRecorder) metrics.Registry.MustRegister(stateMetrics) - o := controller.Options{ + ctx := context.Background() + clusterOpts := controller.Options{ + Logger: log, + MaxConcurrentReconciles: *maxReconcileRate, + PollInterval: *pollInterval, + GlobalRateLimiter: ratelimiter.NewGlobal(*maxReconcileRate), + Features: &feature.Flags{}, + MetricOptions: &controller.MetricOptions{ + PollStateMetricInterval: *pollStateMetricInterval, + MRMetrics: metricRecorder, + MRStateMetrics: stateMetrics, + }, + } + namespacedOpts := controller.Options{ Logger: log, MaxConcurrentReconciles: *maxReconcileRate, PollInterval: *pollInterval, @@ -149,38 +169,40 @@ func main() { } if *enableManagementPolicies { - o.Features.Enable(features.EnableBetaManagementPolicies) + clusterOpts.Features.Enable(features.EnableBetaManagementPolicies) + namespacedOpts.Features.Enable(features.EnableBetaManagementPolicies) log.Info("Beta feature enabled", "flag", features.EnableBetaManagementPolicies) } - if *enableExternalSecretStores { - o.Features.Enable(features.EnableAlphaExternalSecretStores) - log.Info("Alpha feature enabled", "flag", features.EnableAlphaExternalSecretStores) - o.ESSOptions = &controller.ESSOptions{} - if *essTLSCertsPath != "" { - log.Info("ESS TLS certificates path is set. Loading mTLS configuration.") - tCfg, err := certificates.LoadMTLSConfig(filepath.Join(*essTLSCertsPath, "ca.crt"), filepath.Join(*essTLSCertsPath, "tls.crt"), filepath.Join(*essTLSCertsPath, "tls.key"), false) - kingpin.FatalIfError(err, "Cannot load ESS TLS config.") + if *enableChangeLogs { + clusterOpts.Features.Enable(feature.EnableAlphaChangeLogs) + namespacedOpts.Features.Enable(feature.EnableAlphaChangeLogs) + log.Info("Alpha feature enabled", "flag", feature.EnableAlphaChangeLogs) - o.ESSOptions.TLSConfig = tCfg - } + conn, err := grpc.NewClient("unix://"+*changelogsSocketPath, grpc.WithTransportCredentials(insecure.NewCredentials())) + kingpin.FatalIfError(err, "failed to create change logs client connection at %s", *changelogsSocketPath) - // Ensure default store config exists. - kingpin.FatalIfError(resource.Ignore(kerrors.IsAlreadyExists, mgr.GetClient().Create(context.Background(), &v1beta1.StoreConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "default", - }, - Spec: v1beta1.StoreConfigSpec{ - // NOTE(turkenh): We only set required spec and expect optional - // ones to properly be initialized with CRD level default values. - SecretStoreConfig: xpv1.SecretStoreConfig{ - DefaultScope: *namespace, - }, - }, - })), "cannot create default store config") + clo := controller.ChangeLogOptions{ + ChangeLogger: managed.NewGRPCChangeLogger(changelogsv1alpha1.NewChangeLogServiceClient(conn)), + } + clusterOpts.ChangeLogOptions = &clo + namespacedOpts.ChangeLogOptions = &clo } - kingpin.FatalIfError(workspace.Setup(mgr, o, *timeout, *pollJitter), "Cannot setup Workspace controllers") + canSafeStart, err := canWatchCRD(ctx, mgr) + kingpin.FatalIfError(err, "SafeStart precheck failed") + if canSafeStart { + crdGate := new(gate.Gate[schema.GroupVersionKind]) + clusterOpts.Gate = crdGate + namespacedOpts.Gate = crdGate + kingpin.FatalIfError(customresourcesgate.Setup(mgr, namespacedOpts), "Cannot setup CRD gate") + kingpin.FatalIfError(clusterworkspace.SetupGated(mgr, clusterOpts, *timeout, *pollJitter), "Cannot setup cluster-scoped Workspace controllers") + kingpin.FatalIfError(namespacedworkspace.SetupGated(mgr, namespacedOpts, *timeout, *pollJitter), "Cannot setup namespaced Workspace controllers") + } else { + log.Info("Provider has missing RBAC permissions for watching CRDs, controller SafeStart capability will be disabled") + kingpin.FatalIfError(clusterworkspace.Setup(mgr, clusterOpts, *timeout, *pollJitter), "Cannot setup cluster-scoped Workspace controllers") + kingpin.FatalIfError(namespacedworkspace.Setup(mgr, namespacedOpts, *timeout, *pollJitter), "Cannot setup namespaced Workspace controllers") + } kingpin.FatalIfError(mgr.Start(ctrl.SetupSignalHandler()), "Cannot start controller manager") } @@ -199,3 +221,28 @@ func UseJSON() zap.Opts { o.Encoder = zapcore.NewJSONEncoder(encoderConfig) } } + +func canWatchCRD(ctx context.Context, mgr manager.Manager) (bool, error) { + if err := authv1.AddToScheme(mgr.GetScheme()); err != nil { + return false, err + } + verbs := []string{"get", "list", "watch"} + for _, verb := range verbs { + sar := &authv1.SelfSubjectAccessReview{ + Spec: authv1.SelfSubjectAccessReviewSpec{ + ResourceAttributes: &authv1.ResourceAttributes{ + Group: "apiextensions.k8s.io", + Resource: "customresourcedefinitions", + Verb: verb, + }, + }, + } + if err := mgr.GetClient().Create(ctx, sar); err != nil { + return false, errors.Wrapf(err, "unable to perform RBAC check for verb %s on CustomResourceDefinitions", verbs) + } + if !sar.Status.Allowed { + return false, nil + } + } + return true, nil +} diff --git a/examples/.terraformrc b/examples/cluster/.terraformrc similarity index 100% rename from examples/.terraformrc rename to examples/cluster/.terraformrc diff --git a/examples/aws-eks-irsa-setup.yaml b/examples/cluster/aws-eks-irsa-setup.yaml similarity index 100% rename from examples/aws-eks-irsa-setup.yaml rename to examples/cluster/aws-eks-irsa-setup.yaml diff --git a/examples/creationblocker/README.md b/examples/cluster/creationblocker/README.md similarity index 100% rename from examples/creationblocker/README.md rename to examples/cluster/creationblocker/README.md diff --git a/examples/creationblocker/composition.yaml b/examples/cluster/creationblocker/composition.yaml similarity index 100% rename from examples/creationblocker/composition.yaml rename to examples/cluster/creationblocker/composition.yaml diff --git a/examples/creationblocker/definition.yaml b/examples/cluster/creationblocker/definition.yaml similarity index 100% rename from examples/creationblocker/definition.yaml rename to examples/cluster/creationblocker/definition.yaml diff --git a/examples/creationblocker/xsubnetblocking.yaml b/examples/cluster/creationblocker/xsubnetblocking.yaml similarity index 100% rename from examples/creationblocker/xsubnetblocking.yaml rename to examples/cluster/creationblocker/xsubnetblocking.yaml diff --git a/examples/creationblocker/xsubnetnotblocking.yaml b/examples/cluster/creationblocker/xsubnetnotblocking.yaml similarity index 100% rename from examples/creationblocker/xsubnetnotblocking.yaml rename to examples/cluster/creationblocker/xsubnetnotblocking.yaml diff --git a/examples/environment/envconfigmap.yaml b/examples/cluster/environment/envconfigmap.yaml similarity index 100% rename from examples/environment/envconfigmap.yaml rename to examples/cluster/environment/envconfigmap.yaml diff --git a/examples/environment/envsecret.yaml b/examples/cluster/environment/envsecret.yaml similarity index 100% rename from examples/environment/envsecret.yaml rename to examples/cluster/environment/envsecret.yaml diff --git a/examples/environment/workspace-inline-env-aws.yaml b/examples/cluster/environment/workspace-inline-env-aws.yaml similarity index 100% rename from examples/environment/workspace-inline-env-aws.yaml rename to examples/cluster/environment/workspace-inline-env-aws.yaml diff --git a/examples/importer/README.md b/examples/cluster/importer/README.md similarity index 100% rename from examples/importer/README.md rename to examples/cluster/importer/README.md diff --git a/examples/importer/composition.yaml b/examples/cluster/importer/composition.yaml similarity index 100% rename from examples/importer/composition.yaml rename to examples/cluster/importer/composition.yaml diff --git a/examples/importer/definition.yaml b/examples/cluster/importer/definition.yaml similarity index 100% rename from examples/importer/definition.yaml rename to examples/cluster/importer/definition.yaml diff --git a/examples/importer/xsubnet.yaml b/examples/cluster/importer/xsubnet.yaml similarity index 100% rename from examples/importer/xsubnet.yaml rename to examples/cluster/importer/xsubnet.yaml diff --git a/examples/install.yaml b/examples/cluster/install.yaml similarity index 100% rename from examples/install.yaml rename to examples/cluster/install.yaml diff --git a/examples/observe-only-composition/README.md b/examples/cluster/observe-only-composition/README.md similarity index 100% rename from examples/observe-only-composition/README.md rename to examples/cluster/observe-only-composition/README.md diff --git a/examples/observe-only-composition/composition.yaml b/examples/cluster/observe-only-composition/composition.yaml similarity index 100% rename from examples/observe-only-composition/composition.yaml rename to examples/cluster/observe-only-composition/composition.yaml diff --git a/examples/observe-only-composition/definition.yaml b/examples/cluster/observe-only-composition/definition.yaml similarity index 100% rename from examples/observe-only-composition/definition.yaml rename to examples/cluster/observe-only-composition/definition.yaml diff --git a/examples/observe-only-composition/xsubnet.yaml b/examples/cluster/observe-only-composition/xsubnet.yaml similarity index 100% rename from examples/observe-only-composition/xsubnet.yaml rename to examples/cluster/observe-only-composition/xsubnet.yaml diff --git a/examples/providerconfig-aws.yaml b/examples/cluster/providerconfig-aws.yaml similarity index 100% rename from examples/providerconfig-aws.yaml rename to examples/cluster/providerconfig-aws.yaml diff --git a/examples/providerconfig-azure.yaml b/examples/cluster/providerconfig-azure.yaml similarity index 100% rename from examples/providerconfig-azure.yaml rename to examples/cluster/providerconfig-azure.yaml diff --git a/examples/providerconfig-backend-file.yaml b/examples/cluster/providerconfig-backend-file.yaml similarity index 100% rename from examples/providerconfig-backend-file.yaml rename to examples/cluster/providerconfig-backend-file.yaml diff --git a/examples/providerconfig-terraformrc.yaml b/examples/cluster/providerconfig-terraformrc.yaml similarity index 100% rename from examples/providerconfig-terraformrc.yaml rename to examples/cluster/providerconfig-terraformrc.yaml diff --git a/examples/providerconfig.yaml b/examples/cluster/providerconfig.yaml similarity index 100% rename from examples/providerconfig.yaml rename to examples/cluster/providerconfig.yaml diff --git a/examples/transition/00-mr-tf-workspace/workspace-inline.yaml b/examples/cluster/transition/00-mr-tf-workspace/workspace-inline.yaml similarity index 100% rename from examples/transition/00-mr-tf-workspace/workspace-inline.yaml rename to examples/cluster/transition/00-mr-tf-workspace/workspace-inline.yaml diff --git a/examples/transition/00-mr-tf-workspace/workspace-remote.yaml b/examples/cluster/transition/00-mr-tf-workspace/workspace-remote.yaml similarity index 100% rename from examples/transition/00-mr-tf-workspace/workspace-remote.yaml rename to examples/cluster/transition/00-mr-tf-workspace/workspace-remote.yaml diff --git a/examples/transition/01-composition-tf-only/composition.yaml b/examples/cluster/transition/01-composition-tf-only/composition.yaml similarity index 100% rename from examples/transition/01-composition-tf-only/composition.yaml rename to examples/cluster/transition/01-composition-tf-only/composition.yaml diff --git a/examples/transition/01-composition-tf-only/subnet-tf.yaml b/examples/cluster/transition/01-composition-tf-only/subnet-tf.yaml similarity index 100% rename from examples/transition/01-composition-tf-only/subnet-tf.yaml rename to examples/cluster/transition/01-composition-tf-only/subnet-tf.yaml diff --git a/examples/transition/02-composition-tf-and-native/composition.yaml b/examples/cluster/transition/02-composition-tf-and-native/composition.yaml similarity index 100% rename from examples/transition/02-composition-tf-and-native/composition.yaml rename to examples/cluster/transition/02-composition-tf-and-native/composition.yaml diff --git a/examples/transition/02-composition-tf-and-native/subnet-mixed.yaml b/examples/cluster/transition/02-composition-tf-and-native/subnet-mixed.yaml similarity index 100% rename from examples/transition/02-composition-tf-and-native/subnet-mixed.yaml rename to examples/cluster/transition/02-composition-tf-and-native/subnet-mixed.yaml diff --git a/examples/transition/03-composition-native-only/composition.yaml b/examples/cluster/transition/03-composition-native-only/composition.yaml similarity index 100% rename from examples/transition/03-composition-native-only/composition.yaml rename to examples/cluster/transition/03-composition-native-only/composition.yaml diff --git a/examples/transition/03-composition-native-only/subnet-native.yaml b/examples/cluster/transition/03-composition-native-only/subnet-native.yaml similarity index 100% rename from examples/transition/03-composition-native-only/subnet-native.yaml rename to examples/cluster/transition/03-composition-native-only/subnet-native.yaml diff --git a/examples/transition/README.md b/examples/cluster/transition/README.md similarity index 100% rename from examples/transition/README.md rename to examples/cluster/transition/README.md diff --git a/examples/transition/definition.yaml b/examples/cluster/transition/definition.yaml similarity index 100% rename from examples/transition/definition.yaml rename to examples/cluster/transition/definition.yaml diff --git a/examples/workspace-enable-logging.yaml b/examples/cluster/workspace-enable-logging.yaml similarity index 100% rename from examples/workspace-enable-logging.yaml rename to examples/cluster/workspace-enable-logging.yaml diff --git a/examples/workspace-inline-aws.yaml b/examples/cluster/workspace-inline-aws.yaml similarity index 100% rename from examples/workspace-inline-aws.yaml rename to examples/cluster/workspace-inline-aws.yaml diff --git a/examples/workspace-inline.yaml b/examples/cluster/workspace-inline.yaml similarity index 100% rename from examples/workspace-inline.yaml rename to examples/cluster/workspace-inline.yaml diff --git a/examples/workspace-random-generator.yaml b/examples/cluster/workspace-random-generator.yaml similarity index 100% rename from examples/workspace-random-generator.yaml rename to examples/cluster/workspace-random-generator.yaml diff --git a/examples/workspace-remote.yaml b/examples/cluster/workspace-remote.yaml similarity index 100% rename from examples/workspace-remote.yaml rename to examples/cluster/workspace-remote.yaml diff --git a/examples/namespaced/clusterproviderconfig-aws.yaml b/examples/namespaced/clusterproviderconfig-aws.yaml new file mode 100644 index 0000000..c69182c --- /dev/null +++ b/examples/namespaced/clusterproviderconfig-aws.yaml @@ -0,0 +1,32 @@ +--- +apiVersion: tf.m.upbound.io/v1beta1 +kind: ClusterProviderConfig +metadata: + name: aws-eu-west-1-cluster + namespace: upbound-system +spec: + credentials: + - filename: aws-creds.ini + source: Secret + secretRef: + namespace: upbound-system + name: aws-creds + key: credentials + configuration: | + terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "5.6.1" + } + } + backend "kubernetes" { + secret_suffix = "providerconfig-aws-eu-west-1-cluster" + namespace = "upbound-system" + in_cluster_config = true + } + } + provider "aws" { + shared_credentials_files = ["${path.module}/aws-creds.ini"] + region = "us-east-2" + } diff --git a/examples/namespaced/clusterproviderconfig.yaml b/examples/namespaced/clusterproviderconfig.yaml new file mode 100644 index 0000000..3944595 --- /dev/null +++ b/examples/namespaced/clusterproviderconfig.yaml @@ -0,0 +1,34 @@ +apiVersion: tf.m.upbound.io/v1beta1 +kind: ClusterProviderConfig +metadata: + name: default + namespace: upbound-system +spec: + # Note that unlike most provider configs this one supports an array of + # credentials. This is because each Terraform workspace uses a single + # Crossplane provider config, but could use multiple Terraform providers each + # with their own credentials. + credentials: + - filename: gcp-credentials.json + source: Secret + secretRef: + namespace: upbound-system + name: gcp-creds + key: credentials + # This optional configuration block can be used to inject HCL into any + # workspace that uses this provider config, for example to setup Terraform + # providers. + configuration: | + provider "google" { + credentials = "gcp-credentials.json" + project = "official-provider-testing" + } + + // Modules _must_ use remote state. The provider does not persist state. + terraform { + backend "kubernetes" { + secret_suffix = "clusterproviderconfig-default" + namespace = "upbound-system" + in_cluster_config = true + } + } diff --git a/examples/namespaced/providerconfig.yaml b/examples/namespaced/providerconfig.yaml new file mode 100644 index 0000000..75763d1 --- /dev/null +++ b/examples/namespaced/providerconfig.yaml @@ -0,0 +1,34 @@ +apiVersion: tf.m.upbound.io/v1beta1 +kind: ProviderConfig +metadata: + name: default + namespace: upbound-system +spec: + # Note that unlike most provider configs this one supports an array of + # credentials. This is because each Terraform workspace uses a single + # Crossplane provider config, but could use multiple Terraform providers each + # with their own credentials. + credentials: + - filename: gcp-credentials.json + source: Secret + secretRef: + namespace: upbound-system + name: gcp-creds + key: credentials + # This optional configuration block can be used to inject HCL into any + # workspace that uses this provider config, for example to setup Terraform + # providers. + configuration: | + provider "google" { + credentials = "gcp-credentials.json" + project = "official-provider-testing" + } + + // Modules _must_ use remote state. The provider does not persist state. + terraform { + backend "kubernetes" { + secret_suffix = "providerconfig-default" + namespace = "upbound-system" + in_cluster_config = true + } + } diff --git a/examples/namespaced/workspace-inline-aws.yaml b/examples/namespaced/workspace-inline-aws.yaml new file mode 100644 index 0000000..980631b --- /dev/null +++ b/examples/namespaced/workspace-inline-aws.yaml @@ -0,0 +1,38 @@ +apiVersion: tf.m.upbound.io/v1beta1 +kind: Workspace +metadata: + name: sample-inline + namespace: upbound-system +spec: + providerConfigRef: + name: aws-eu-west-1-cluster + kind: ClusterProviderConfig + forProvider: + source: Inline + module: | + resource "aws_vpc" "main" { + cidr_block = "10.0.0.0/16" + tags = { + Name = var.vpcName + } + } + resource "aws_subnet" "main" { + vpc_id = aws_vpc.main.id + cidr_block = "10.0.1.0/24" + } + output "vpc_id" { + value = aws_vpc.main.id + } + output "subnet_data" { + value = { + "id" = aws_subnet.main.id + "arn" = aws_subnet.main.arn + } + } + variable "vpcName" { + description = "VPC name" + type = string + } + vars: + - key: vpcName + value: sample-tf-inline diff --git a/apis/generate.go b/generate/generate.go similarity index 91% rename from apis/generate.go rename to generate/generate.go index 8dc4f86..e3b876b 100644 --- a/apis/generate.go +++ b/generate/generate.go @@ -24,12 +24,12 @@ limitations under the License. //go:generate rm -rf ../package/crds // Generate deepcopy methodsets and CRD manifests -//go:generate go run -tags generate sigs.k8s.io/controller-tools/cmd/controller-gen object:headerFile=../hack/boilerplate.go.txt paths=./... crd:crdVersions=v1 output:artifacts:config=../package/crds +//go:generate go run -tags generate sigs.k8s.io/controller-tools/cmd/controller-gen object:headerFile=../hack/boilerplate.go.txt paths=../apis/... crd:crdVersions=v1 output:artifacts:config=../package/crds // Generate crossplane-runtime methodsets (resource.Claim, etc) -//go:generate go run -tags generate github.com/crossplane/crossplane-tools/cmd/angryjet generate-methodsets --header-file=../hack/boilerplate.go.txt ./... +//go:generate go run -tags generate github.com/crossplane/crossplane-tools/cmd/angryjet generate-methodsets --header-file=../hack/boilerplate.go.txt ../apis/... -package apis +package generate import ( _ "sigs.k8s.io/controller-tools/cmd/controller-gen" //nolint:typecheck diff --git a/go.mod b/go.mod index 85e98e0..fabd8ca 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,12 @@ module github.com/upbound/provider-terraform -go 1.23.8 +go 1.24.6 require ( github.com/MakeNowJust/heredoc v1.0.0 - github.com/crossplane/crossplane-runtime v1.16.0 - github.com/crossplane/crossplane-tools v0.0.0-20230925130601-628280f8bf79 + github.com/alecthomas/kingpin/v2 v2.4.0 + github.com/crossplane/crossplane-runtime/v2 v2.0.0 + github.com/crossplane/crossplane-tools v0.0.0-20250731192036-00d407d8b7ec github.com/fluxcd/source-controller/api v1.2.5 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 @@ -13,105 +14,114 @@ require ( github.com/pkg/errors v0.9.1 github.com/spf13/afero v1.14.0 go.uber.org/zap v1.27.0 - gopkg.in/alecthomas/kingpin.v2 v2.2.6 - k8s.io/api v0.29.4 - k8s.io/apiextensions-apiserver v0.29.4 - k8s.io/apimachinery v0.29.4 - k8s.io/client-go v0.29.4 - sigs.k8s.io/controller-runtime v0.17.3 - sigs.k8s.io/controller-tools v0.14.0 + google.golang.org/grpc v1.68.1 + k8s.io/api v0.33.0 + k8s.io/apiextensions-apiserver v0.33.0 + k8s.io/apimachinery v0.33.0 + k8s.io/client-go v0.33.0 + sigs.k8s.io/controller-runtime v0.19.0 + sigs.k8s.io/controller-tools v0.18.0 ) require ( - cloud.google.com/go v0.110.10 // indirect - cloud.google.com/go/compute/metadata v0.3.0 // indirect + cloud.google.com/go v0.112.0 // indirect + cloud.google.com/go/compute/metadata v0.5.0 // indirect cloud.google.com/go/iam v1.1.5 // indirect - cloud.google.com/go/storage v1.35.1 // indirect - dario.cat/mergo v1.0.0 // indirect - github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect - github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect + cloud.google.com/go/storage v1.36.0 // indirect + dario.cat/mergo v1.0.1 // indirect + github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect github.com/aws/aws-sdk-go v1.44.122 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/dave/jennifer v1.4.1 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/dave/jennifer v1.7.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/evanphx/json-patch v5.6.0+incompatible // indirect - github.com/evanphx/json-patch/v5 v5.8.0 // indirect - github.com/fatih/color v1.16.0 // indirect + github.com/evanphx/json-patch v5.9.11+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fluxcd/pkg/apis/acl v0.1.0 // indirect github.com/fluxcd/pkg/apis/meta v1.3.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.3 // indirect - github.com/gobuffalo/flect v1.0.2 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/gobuffalo/flect v1.0.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/google/gnostic-models v0.6.8 // indirect - github.com/google/gofuzz v1.2.0 // indirect + github.com/google/gnostic-models v0.6.9 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/go-version v1.6.0 // indirect - github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.4 // indirect + github.com/klauspost/compress v1.18.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/prometheus/client_golang v1.18.0 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.45.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect - github.com/spf13/cobra v1.8.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/spf13/cobra v1.9.1 // indirect + github.com/spf13/pflag v1.0.6 // indirect github.com/ulikunitz/xz v0.5.10 // indirect + github.com/x448/float16 v0.8.4 // indirect + github.com/xhit/go-str2duration/v2 v2.1.0 // indirect go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect + go.opentelemetry.io/otel v1.33.0 // indirect + go.opentelemetry.io/otel/metric v1.33.0 // indirect + go.opentelemetry.io/otel/trace v1.33.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.36.0 // indirect - golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect - golang.org/x/mod v0.17.0 // indirect - golang.org/x/net v0.38.0 // indirect + golang.org/x/crypto v0.37.0 // indirect + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect + golang.org/x/mod v0.24.0 // indirect + golang.org/x/net v0.39.0 // indirect golang.org/x/oauth2 v0.27.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/term v0.30.0 // indirect - golang.org/x/text v0.23.0 // indirect - golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + golang.org/x/sync v0.13.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/term v0.31.0 // indirect + golang.org/x/text v0.24.0 // indirect + golang.org/x/time v0.9.0 // indirect + golang.org/x/tools v0.32.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/api v0.152.0 // indirect - google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect - google.golang.org/grpc v1.61.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/api v0.155.0 // indirect + google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/protobuf v1.36.5 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/component-base v0.29.4 // indirect - k8s.io/klog/v2 v2.110.1 // indirect - k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect - k8s.io/utils v0.0.0-20231127182322-b307cd553661 // indirect - sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + k8s.io/code-generator v0.33.0 // indirect + k8s.io/component-base v0.33.0 // indirect + k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect + k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 3f4fdbd..386402d 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,8 @@ cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRY cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= -cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y= -cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic= +cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM= +cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= @@ -178,8 +178,8 @@ cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZ cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= -cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= +cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= @@ -533,8 +533,8 @@ cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeL cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= -cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w= -cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= +cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8= +cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= @@ -600,8 +600,8 @@ cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoIS cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= @@ -615,10 +615,10 @@ github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= +github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= @@ -639,8 +639,9 @@ github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -659,17 +660,18 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/crossplane/crossplane-runtime v1.16.0 h1:lz+l0wEB3qowdTmN7t0PZkfuNSvfOoEhQrEYFbYqMow= -github.com/crossplane/crossplane-runtime v1.16.0/go.mod h1:Pz2tdGVMF6KDGzHZOkvKro0nKc8EzK0sb/nSA7pH4Dc= -github.com/crossplane/crossplane-tools v0.0.0-20230925130601-628280f8bf79 h1:HigXs5tEQxWz0fcj8hzbU2UAZgEM7wPe0XRFOsrtF8Y= -github.com/crossplane/crossplane-tools v0.0.0-20230925130601-628280f8bf79/go.mod h1:+e4OaFlOcmr0JvINHl/yvEYBrZawzTgj6pQumOH1SS0= -github.com/dave/jennifer v1.4.1 h1:XyqG6cn5RQsTj3qlWQTKlRGAyrTcsk1kUmWdZBzRjDw= -github.com/dave/jennifer v1.4.1/go.mod h1:7jEdnm+qBcxl8PC0zyp7vxcpSRnzXSt9r39tpTVGlwA= +github.com/crossplane/crossplane-runtime/v2 v2.0.0 h1:PK2pTKfshdDZ5IfoiMRiCi0PBnIjqbS0KGXEJgRdrb4= +github.com/crossplane/crossplane-runtime/v2 v2.0.0/go.mod h1:pkd5UzmE8esaZAApevMutR832GjJ1Qgc5Ngr78ByxrI= +github.com/crossplane/crossplane-tools v0.0.0-20250731192036-00d407d8b7ec h1:+51Et4UW8XrvGne8RAqn9qEIfhoqPXYqIp/kQvpMaAo= +github.com/crossplane/crossplane-tools v0.0.0-20250731192036-00d407d8b7ec/go.mod h1:8etxwmP4cZwJDwen4+PQlnc1tggltAhEfyyigmdHulQ= +github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo= +github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= @@ -689,13 +691,15 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= -github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= -github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.8.0 h1:lRj6N9Nci7MvzrXuX6HFzU8XjmhPiXPlsKEy1u0KQro= -github.com/evanphx/json-patch/v5 v5.8.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= +github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= +github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fluxcd/pkg/apis/acl v0.1.0 h1:EoAl377hDQYL3WqanWCdifauXqXbMyFuK82NnX6pH4Q= github.com/fluxcd/pkg/apis/acl v0.1.0/go.mod h1:zfEZzz169Oap034EsDhmCAGgnWlcWmIObZjYMusoXS8= github.com/fluxcd/pkg/apis/meta v1.3.0 h1:KxeEc6olmSZvQ5pBONPE4IKxyoWQbqTJF1X6K5nIXpU= @@ -706,6 +710,8 @@ github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/ github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= @@ -717,23 +723,27 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= -github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA= -github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4= +github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -780,8 +790,8 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= -github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= +github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -825,8 +835,8 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 h1:WzfWbQz/Ze8v6l++GGbGNFZnUShVpP/0xffCPLL+ax8= -github.com/google/pprof v0.0.0-20240117000934-35fc243c5815/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= @@ -872,8 +882,6 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= -github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= @@ -894,8 +902,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -907,6 +915,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= @@ -921,8 +931,6 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -940,10 +948,10 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY= -github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw= -github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= -github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= +github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= +github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= @@ -956,24 +964,24 @@ github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= -github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= -github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= @@ -983,13 +991,15 @@ github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -1000,10 +1010,16 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1022,6 +1038,20 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= +go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= +go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= +go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= +go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= +go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM= +go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM= +go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= +go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= @@ -1044,8 +1074,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1061,8 +1091,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= -golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o= -golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -1107,8 +1137,9 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1171,8 +1202,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= 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= @@ -1224,8 +1255,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1308,8 +1339,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= 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= @@ -1324,8 +1355,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= +golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= 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.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1346,16 +1377,16 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1420,8 +1451,9 @@ golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= +golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1498,8 +1530,8 @@ google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/ google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= -google.golang.org/api v0.152.0 h1:t0r1vPnfMc260S2Ci+en7kfCZaLOPs5KI0sVV/6jZrY= -google.golang.org/api v0.152.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY= +google.golang.org/api v0.155.0 h1:vBmGhCYs0djJttDNynWo44zosHlPvHmA0XiN2zP2DtA= +google.golang.org/api v0.155.0/go.mod h1:GI5qK5f40kCpHfPn6+YzGAByIKWv8ujFnmoWm7Igduk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1639,12 +1671,12 @@ google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOl google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= -google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= -google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= -google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo= -google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= +google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= +google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1686,8 +1718,8 @@ google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5v google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= -google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= -google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= +google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1706,16 +1738,17 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= @@ -1736,22 +1769,26 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -k8s.io/api v0.29.4 h1:WEnF/XdxuCxdG3ayHNRR8yH3cI1B/llkWBma6bq4R3w= -k8s.io/api v0.29.4/go.mod h1:DetSv0t4FBTcEpfA84NJV3g9a7+rSzlUHk5ADAYHUv0= -k8s.io/apiextensions-apiserver v0.29.4 h1:M7hbuHU/ckbibR7yPbe6DyNWgTFKNmZDbdZKD8q1Smk= -k8s.io/apiextensions-apiserver v0.29.4/go.mod h1:TTDC9fB+0kHY2rogf5hgBR03KBKCwED+GHUsXGpR7SM= -k8s.io/apimachinery v0.29.4 h1:RaFdJiDmuKs/8cm1M6Dh1Kvyh59YQFDcFuFTSmXes6Q= -k8s.io/apimachinery v0.29.4/go.mod h1:i3FJVwhvSp/6n8Fl4K97PJEP8C+MM+aoDq4+ZJBf70Y= -k8s.io/client-go v0.29.4 h1:79ytIedxVfyXV8rpH3jCBW0u+un0fxHDwX5F9K8dPR8= -k8s.io/client-go v0.29.4/go.mod h1:kC1thZQ4zQWYwldsfI088BbK6RkxK+aF5ebV8y9Q4tk= -k8s.io/component-base v0.29.4 h1:xeKzuuHI/1tjleu5jycDAcYbhAxeGHCQBZUY2eRIkOo= -k8s.io/component-base v0.29.4/go.mod h1:pYjt+oEZP9gtmwSikwAJgfSBikqKX2gOqRat0QjmQt0= -k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= -k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= -k8s.io/utils v0.0.0-20231127182322-b307cd553661 h1:FepOBzJ0GXm8t0su67ln2wAZjbQ6RxQGZDnzuLcrUTI= -k8s.io/utils v0.0.0-20231127182322-b307cd553661/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/api v0.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU= +k8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM= +k8s.io/apiextensions-apiserver v0.33.0 h1:d2qpYL7Mngbsc1taA4IjJPRJ9ilnsXIrndH+r9IimOs= +k8s.io/apiextensions-apiserver v0.33.0/go.mod h1:VeJ8u9dEEN+tbETo+lFkwaaZPg6uFKLGj5vyNEwwSzc= +k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ= +k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/client-go v0.33.0 h1:UASR0sAYVUzs2kYuKn/ZakZlcs2bEHaizrrHUZg0G98= +k8s.io/client-go v0.33.0/go.mod h1:kGkd+l/gNGg8GYWAPr0xF1rRKvVWvzh9vmZAMXtaKOg= +k8s.io/code-generator v0.33.0 h1:B212FVl6EFqNmlgdOZYWNi77yBv+ed3QgQsMR8YQCw4= +k8s.io/code-generator v0.33.0/go.mod h1:KnJRokGxjvbBQkSJkbVuBbu6z4B0rC7ynkpY5Aw6m9o= +k8s.io/component-base v0.33.0 h1:Ot4PyJI+0JAD9covDhwLp9UNkUja209OzsJ4FzScBNk= +k8s.io/component-base v0.33.0/go.mod h1:aXYZLbw3kihdkOPMDhWbjGCO6sg+luw554KP51t8qCU= +k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7 h1:2OX19X59HxDprNCVrWi6jb7LW1PoqTlYqEq5H2oetog= +k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= +k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro= +k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= @@ -1790,13 +1827,16 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/controller-runtime v0.17.3 h1:65QmN7r3FWgTxDMz9fvGnO1kbf2nu+acg9p2R9oYYYk= -sigs.k8s.io/controller-runtime v0.17.3/go.mod h1:N0jpP5Lo7lMTF9aL56Z/B2oWBJjey6StQM0jRbKQXtY= -sigs.k8s.io/controller-tools v0.14.0 h1:rnNoCC5wSXlrNoBKKzL70LNJKIQKEzT6lloG6/LF73A= -sigs.k8s.io/controller-tools v0.14.0/go.mod h1:TV7uOtNNnnR72SpzhStvPkoS/U5ir0nMudrkrC4M9Sc= -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/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q= +sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= +sigs.k8s.io/controller-tools v0.18.0 h1:rGxGZCZTV2wJreeRgqVoWab/mfcumTMmSwKzoM9xrsE= +sigs.k8s.io/controller-tools v0.18.0/go.mod h1:gLKoiGBriyNh+x1rWtUQnakUYEujErjXs9pf+x/8n1U= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= +sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/internal/clients/client.go b/internal/clients/client.go new file mode 100644 index 0000000..6052593 --- /dev/null +++ b/internal/clients/client.go @@ -0,0 +1,148 @@ +/* +Copyright 2025 The Crossplane 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 clients + +import ( + "context" + "encoding/json" + + "github.com/crossplane/crossplane-runtime/v2/pkg/errors" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + clusterv1beta1 "github.com/upbound/provider-terraform/apis/cluster/v1beta1" + namespacedv1beta1 "github.com/upbound/provider-terraform/apis/namespaced/v1beta1" +) + +const ( + errProviderConfigNotSet = "provider config is not set" + errGetProviderConfig = "cannot get provider config" + errFailedToTrackUsage = "cannot track provider config usage" +) + +func ResolveProviderConfig(ctx context.Context, crClient client.Client, lt LegacyTracker, mt ModernTracker, mg resource.Managed) (*namespacedv1beta1.ClusterProviderConfig, error) { + switch managed := mg.(type) { + case resource.LegacyManaged: + return resolveProviderConfigLegacy(ctx, crClient, managed, lt) + case resource.ModernManaged: + return resolveProviderConfigModern(ctx, crClient, managed, mt) + default: + return nil, errors.New("resource is not a managed") + } +} + +func resolveProviderConfigLegacy(ctx context.Context, client client.Client, mg resource.LegacyManaged, lt LegacyTracker) (*namespacedv1beta1.ClusterProviderConfig, error) { + configRef := mg.GetProviderConfigReference() + if configRef == nil { + return nil, errors.New(errProviderConfigNotSet) + } + pc := &clusterv1beta1.ProviderConfig{} + if err := client.Get(ctx, types.NamespacedName{Name: configRef.Name}, pc); err != nil { + return nil, errors.Wrap(err, errGetProviderConfig) + } + + if err := lt.Track(ctx, mg); err != nil { + return nil, errors.Wrap(err, errFailedToTrackUsage) + } + + return legacyToModernProviderConfigSpec(pc) +} + +func resolveProviderConfigModern(ctx context.Context, crClient client.Client, mg resource.ModernManaged, mt ModernTracker) (*namespacedv1beta1.ClusterProviderConfig, error) { + configRef := mg.GetProviderConfigReference() + if configRef == nil { + return nil, errors.New(errProviderConfigNotSet) + } + + pcRuntimeObj, err := crClient.Scheme().New(namespacedv1beta1.SchemeGroupVersion.WithKind(configRef.Kind)) + if err != nil { + return nil, errors.Wrapf(err, "referenced provider config kind %q is invalid for %s/%s", configRef.Kind, mg.GetNamespace(), mg.GetName()) + } + pcObj, ok := pcRuntimeObj.(resource.ProviderConfig) + if !ok { + return nil, errors.Errorf("referenced provider config kind %q is not a provider config type %s/%s", configRef.Kind, mg.GetNamespace(), mg.GetName()) + } + + // Namespace will be ignored if the PC is a cluster-scoped type + if err := crClient.Get(ctx, types.NamespacedName{Name: configRef.Name, Namespace: mg.GetNamespace()}, pcObj); err != nil { + return nil, errors.Wrap(err, errGetProviderConfig) + } + + var effectivePC *namespacedv1beta1.ClusterProviderConfig + switch pc := pcObj.(type) { + case *namespacedv1beta1.ProviderConfig: + enrichLocalSecretRefs(pc, mg) + effectivePC = &namespacedv1beta1.ClusterProviderConfig{ + TypeMeta: metav1.TypeMeta{ + APIVersion: namespacedv1beta1.SchemeGroupVersion.String(), + Kind: namespacedv1beta1.ClusterProviderConfigKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: pc.GetName(), + Labels: pc.GetLabels(), + Annotations: pc.GetAnnotations(), + }, + Spec: pc.Spec, + } + case *namespacedv1beta1.ClusterProviderConfig: + // noop + effectivePC = pc + default: + return nil, errors.New("unknown") + } + + if err := mt.Track(ctx, mg); err != nil { + return nil, errors.Wrap(err, errFailedToTrackUsage) + } + return effectivePC, nil +} + +func legacyToModernProviderConfigSpec(pc *clusterv1beta1.ProviderConfig) (*namespacedv1beta1.ClusterProviderConfig, error) { + // TODO(erhan): this is hacky and potentially lossy, generate or manually implement + if pc == nil { + return nil, nil + } + data, err := json.Marshal(pc) + if err != nil { + return nil, err + } + + var mSpec namespacedv1beta1.ClusterProviderConfig + err = json.Unmarshal(data, &mSpec) + mSpec.TypeMeta.Kind = namespacedv1beta1.ClusterProviderConfigKind + mSpec.TypeMeta.APIVersion = namespacedv1beta1.SchemeGroupVersion.String() + mSpec.ObjectMeta = metav1.ObjectMeta{ + Name: pc.GetName(), + Labels: pc.GetLabels(), + Annotations: pc.GetAnnotations(), + } + return &mSpec, err +} + +func enrichLocalSecretRefs(pc *namespacedv1beta1.ProviderConfig, mg resource.Managed) { + if pc != nil { + if pc.Spec.Credentials != nil { + for _, v := range pc.Spec.Credentials { + if v.SecretRef != nil { + v.SecretRef.Namespace = mg.GetNamespace() + } + } + } + } +} diff --git a/internal/clients/interfaces.go b/internal/clients/interfaces.go new file mode 100644 index 0000000..830f8d0 --- /dev/null +++ b/internal/clients/interfaces.go @@ -0,0 +1,57 @@ +/* +Copyright 2025 The Crossplane 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 clients + +import ( + "context" + + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" +) + +// TODO: Remove these temporary interfaces once crossplane-runtime supports them +// natively. These interfaces enable unit testing by providing mockable Track +// functions for both legacy and modern managed resources. They are temporary +// workarounds pending the merge of https://github.com/crossplane/crossplane-runtime/pull/862, +// after which we should migrate to the upstream implementations. + +// A LegacyTracker tracks legacy managed resources. +type LegacyTracker interface { + // Track the supplied legacy managed resource. + Track(ctx context.Context, mg resource.LegacyManaged) error +} + +// A LegacyTrackerFn is a function that tracks managed resources. +type LegacyTrackerFn func(ctx context.Context, mg resource.LegacyManaged) error + +// Track the supplied legacy managed resource. +func (fn LegacyTrackerFn) Track(ctx context.Context, mg resource.LegacyManaged) error { + return fn(ctx, mg) +} + +// A ModernTracker tracks modern managed resources. +type ModernTracker interface { + // Track the supplied modern managed resource. + Track(ctx context.Context, mg resource.ModernManaged) error +} + +// A ModernTrackerFn is a function that tracks managed resources. +type ModernTrackerFn func(ctx context.Context, mg resource.ModernManaged) error + +// Track the supplied managed resource. +func (fn ModernTrackerFn) Track(ctx context.Context, mg resource.ModernManaged) error { + return fn(ctx, mg) +} diff --git a/internal/controller/config/config.go b/internal/controller/cluster/config/config.go similarity index 60% rename from internal/controller/config/config.go rename to internal/controller/cluster/config/config.go index 88e35e5..f822122 100644 --- a/internal/controller/config/config.go +++ b/internal/controller/cluster/config/config.go @@ -21,22 +21,23 @@ import ( ctrl "sigs.k8s.io/controller-runtime" - "github.com/crossplane/crossplane-runtime/pkg/controller" - "github.com/crossplane/crossplane-runtime/pkg/event" - "github.com/crossplane/crossplane-runtime/pkg/ratelimiter" - "github.com/crossplane/crossplane-runtime/pkg/reconciler/providerconfig" - "github.com/crossplane/crossplane-runtime/pkg/resource" + "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/ratelimiter" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/providerconfig" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" - "github.com/upbound/provider-terraform/apis/v1beta1" + "github.com/upbound/provider-terraform/apis/cluster/v1beta1" ) // Setup adds a controller that reconciles ProviderConfigs by accounting for // their current usage. -func Setup(mgr ctrl.Manager, o controller.Options, timeout time.Duration) error { +func Setup(mgr ctrl.Manager, o controller.Options, timeout time.Duration, pollJitter time.Duration) error { name := providerconfig.ControllerName(v1beta1.ProviderConfigGroupKind) of := resource.ProviderConfigKinds{ Config: v1beta1.ProviderConfigGroupVersionKind, + Usage: v1beta1.ProviderConfigUsageGroupVersionKind, UsageList: v1beta1.ProviderConfigUsageListGroupVersionKind, } @@ -51,3 +52,14 @@ func Setup(mgr ctrl.Manager, o controller.Options, timeout time.Duration) error Watches(&v1beta1.ProviderConfigUsage{}, &resource.EnqueueRequestForProviderConfig{}). Complete(ratelimiter.NewReconciler(name, r, o.GlobalRateLimiter)) } + +// SetupGated adds a controller that reconciles ProviderConfigs by accounting for +// their current usage. +func SetupGated(mgr ctrl.Manager, o controller.Options, timeout time.Duration, pollJitter time.Duration) error { + o.Gate.Register(func() { + if err := Setup(mgr, o, timeout, pollJitter); err != nil { + mgr.GetLogger().Error(err, "unable to setup reconciler", "gvk", v1beta1.ProviderConfigGroupVersionKind.String()) + } + }, v1beta1.ProviderConfigGroupVersionKind, v1beta1.ProviderConfigUsageGroupVersionKind) + return nil +} diff --git a/internal/controller/doc.go b/internal/controller/cluster/doc.go similarity index 96% rename from internal/controller/doc.go rename to internal/controller/cluster/doc.go index 3682235..1ae293e 100644 --- a/internal/controller/doc.go +++ b/internal/controller/cluster/doc.go @@ -14,4 +14,4 @@ See the License for the specific language governing permissions and limitations under the License. */ -package controller +package cluster diff --git a/internal/controller/cluster/terraform.go b/internal/controller/cluster/terraform.go new file mode 100644 index 0000000..b0f3a37 --- /dev/null +++ b/internal/controller/cluster/terraform.go @@ -0,0 +1,56 @@ +/* +Copyright 2020 The Crossplane 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 cluster + +import ( + "time" + + ctrl "sigs.k8s.io/controller-runtime" + + "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + + "github.com/upbound/provider-terraform/internal/controller/cluster/config" + "github.com/upbound/provider-terraform/internal/controller/cluster/workspace" +) + +// Setup creates all TF controllers with the supplied logger and adds them +// to the supplied manager. +func Setup(mgr ctrl.Manager, o controller.Options, timeout time.Duration, pollJitter time.Duration) error { + for _, setup := range []func(ctrl.Manager, controller.Options, time.Duration, time.Duration) error{ + config.Setup, + workspace.Setup, + } { + if err := setup(mgr, o, timeout, pollJitter); err != nil { + return err + } + } + return nil +} + +// SetupGated creates all controllers with the supplied logger and adds them to +// the supplied manager gated. +func SetupGated(mgr ctrl.Manager, o controller.Options, timeout time.Duration, pollJitter time.Duration) error { + for _, setup := range []func(ctrl.Manager, controller.Options, time.Duration, time.Duration) error{ + config.SetupGated, + workspace.SetupGated, + } { + if err := setup(mgr, o, timeout, pollJitter); err != nil { + return err + } + } + return nil +} diff --git a/internal/controller/workspace/workspace.go b/internal/controller/cluster/workspace/workspace.go similarity index 91% rename from internal/controller/workspace/workspace.go rename to internal/controller/cluster/workspace/workspace.go index ea6e359..6b46b99 100644 --- a/internal/controller/workspace/workspace.go +++ b/internal/controller/cluster/workspace/workspace.go @@ -26,8 +26,8 @@ import ( "strings" "time" - "github.com/crossplane/crossplane-runtime/pkg/logging" - "github.com/crossplane/crossplane-runtime/pkg/statemetrics" + "github.com/crossplane/crossplane-runtime/v2/pkg/logging" + "github.com/crossplane/crossplane-runtime/v2/pkg/statemetrics" getter "github.com/hashicorp/go-getter" "github.com/pkg/errors" "github.com/spf13/afero" @@ -37,17 +37,17 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - "github.com/crossplane/crossplane-runtime/pkg/connection" - "github.com/crossplane/crossplane-runtime/pkg/controller" - "github.com/crossplane/crossplane-runtime/pkg/event" - "github.com/crossplane/crossplane-runtime/pkg/meta" - "github.com/crossplane/crossplane-runtime/pkg/ratelimiter" - "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" - "github.com/crossplane/crossplane-runtime/pkg/resource" - - "github.com/upbound/provider-terraform/apis/v1beta1" - "github.com/upbound/provider-terraform/internal/controller/features" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/meta" + "github.com/crossplane/crossplane-runtime/v2/pkg/ratelimiter" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + "github.com/upbound/provider-terraform/apis/cluster/v1beta1" + tfClient "github.com/upbound/provider-terraform/internal/clients" + "github.com/upbound/provider-terraform/internal/features" "github.com/upbound/provider-terraform/internal/terraform" "github.com/upbound/provider-terraform/internal/workdir" @@ -123,19 +123,14 @@ func Setup(mgr ctrl.Manager, o controller.Options, timeout, pollJitter time.Dura fs := afero.Afero{Fs: afero.NewOsFs()} gcWorkspace := workdir.NewGarbageCollector(mgr.GetClient(), tfDir, workdir.WithFs(fs), workdir.WithLogger(o.Logger)) - go gcWorkspace.Run(context.TODO()) + go gcWorkspace.Run(context.TODO(), false) gcTmp := workdir.NewGarbageCollector(mgr.GetClient(), filepath.Join("/tmp", tfDir), workdir.WithFs(fs), workdir.WithLogger(o.Logger)) - go gcTmp.Run(context.TODO()) - - cps := []managed.ConnectionPublisher{managed.NewAPISecretPublisher(mgr.GetClient(), mgr.GetScheme())} - if o.Features.Enabled(features.EnableAlphaExternalSecretStores) { - cps = append(cps, connection.NewDetailsManager(mgr.GetClient(), v1beta1.StoreConfigGroupVersionKind, connection.WithTLSConfig(o.ESSOptions.TLSConfig))) - } + go gcTmp.Run(context.TODO(), false) c := &connector{ kube: mgr.GetClient(), - usage: resource.NewProviderConfigUsageTracker(mgr.GetClient(), &v1beta1.ProviderConfigUsage{}), + usage: resource.NewLegacyProviderConfigUsageTracker(mgr.GetClient(), &v1beta1.ProviderConfigUsage{}), logger: o.Logger, fs: fs, terraform: func(dir string, usePluginCache bool, enableTerraformCLILogging bool, logger logging.Logger, envs ...string) tfclient { @@ -150,7 +145,6 @@ func Setup(mgr ctrl.Manager, o controller.Options, timeout, pollJitter time.Dura managed.WithLogger(o.Logger.WithValues("controller", name)), managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), managed.WithTimeout(timeout), - managed.WithConnectionPublishers(cps...), managed.WithMetricRecorder(o.MetricOptions.MRMetrics), } @@ -175,9 +169,20 @@ func Setup(mgr ctrl.Manager, o controller.Options, timeout, pollJitter time.Dura Complete(ratelimiter.NewReconciler(name, r, o.GlobalRateLimiter)) } +// SetupGated adds a controller that reconciles ProviderConfigs by accounting for +// their current usage. +func SetupGated(mgr ctrl.Manager, o controller.Options, timeout time.Duration, pollJitter time.Duration) error { + o.Gate.Register(func() { + if err := Setup(mgr, o, timeout, pollJitter); err != nil { + mgr.GetLogger().Error(err, "unable to setup reconciler", "gvk", v1beta1.WorkspaceGroupVersionKind.String()) + } + }, v1beta1.WorkspaceGroupVersionKind) + return nil +} + type connector struct { kube client.Client - usage resource.Tracker + usage tfClient.LegacyTracker logger logging.Logger fs afero.Afero terraform func(dir string, usePluginCache bool, enableTerraformCLILogging bool, logger logging.Logger, envs ...string) tfclient @@ -203,13 +208,9 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E return nil, errors.Wrap(err, errMkdir) } - if err := c.usage.Track(ctx, mg); err != nil { - return nil, errors.Wrap(err, errTrackPCUsage) - } - - pc := &v1beta1.ProviderConfig{} - if err := c.kube.Get(ctx, types.NamespacedName{Name: cr.GetProviderConfigReference().Name}, pc); err != nil { - return nil, errors.Wrap(err, errGetPC) + pc, err := tfClient.ResolveProviderConfig(ctx, c.kube, c.usage, nil, mg) + if err != nil { + return nil, errors.Wrap(err, "failed to resolve provider config") } // Make git credentials available to inline and remote sources @@ -522,19 +523,23 @@ func (c *external) Update(ctx context.Context, mg resource.Managed) (managed.Ext return managed.ExternalUpdate{ConnectionDetails: op2cd(op)}, nil } -func (c *external) Delete(ctx context.Context, mg resource.Managed) error { +func (c *external) Delete(ctx context.Context, mg resource.Managed) (managed.ExternalDelete, error) { cr, ok := mg.(*v1beta1.Workspace) if !ok { - return errors.New(errNotWorkspace) + return managed.ExternalDelete{}, errors.New(errNotWorkspace) } o, err := c.options(ctx, cr.Spec.ForProvider) if err != nil { - return errors.Wrap(err, errOptions) + return managed.ExternalDelete{}, errors.Wrap(err, errOptions) } o = append(o, terraform.WithArgs(cr.Spec.ForProvider.DestroyArgs)) - return errors.Wrap(c.tf.Destroy(ctx, o...), errDestroy) + return managed.ExternalDelete{}, errors.Wrap(c.tf.Destroy(ctx, o...), errDestroy) +} + +func (c *external) Disconnect(ctx context.Context) error { + return nil } //nolint:gocyclo diff --git a/internal/controller/workspace/workspace_test.go b/internal/controller/cluster/workspace/workspace_test.go similarity index 93% rename from internal/controller/workspace/workspace_test.go rename to internal/controller/cluster/workspace/workspace_test.go index dfa3659..406a031 100644 --- a/internal/controller/workspace/workspace_test.go +++ b/internal/controller/cluster/workspace/workspace_test.go @@ -22,7 +22,7 @@ import ( "path/filepath" "testing" - "github.com/crossplane/crossplane-runtime/pkg/logging" + "github.com/crossplane/crossplane-runtime/v2/pkg/logging" "github.com/google/go-cmp/cmp" "github.com/pkg/errors" "github.com/spf13/afero" @@ -33,17 +33,20 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" - "github.com/crossplane/crossplane-runtime/pkg/resource" - "github.com/crossplane/crossplane-runtime/pkg/test" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + "github.com/crossplane/crossplane-runtime/v2/pkg/test" - "github.com/upbound/provider-terraform/apis/v1beta1" + "github.com/upbound/provider-terraform/apis/cluster/v1beta1" + tfClient "github.com/upbound/provider-terraform/internal/clients" "github.com/upbound/provider-terraform/internal/terraform" ) const ( tfChecksum = "checksum" + + errProviderConfigNotSet = "provider config is not set" ) type ErrFs struct { @@ -117,12 +120,13 @@ func (tf *MockTf) DeleteCurrentWorkspace(ctx context.Context) error { func TestConnect(t *testing.T) { errBoom := errors.New("boom") + errNoProviderConfig := errors.New(errProviderConfigNotSet) uid := types.UID("no-you-id") tfCreds := "credentials" type fields struct { kube client.Client - usage resource.Tracker + usage tfClient.LegacyTracker fs afero.Afero terraform func(dir string, usePluginCache bool, enableTerraformCLILogging bool, logger logging.Logger, envs ...string) tfclient } @@ -166,7 +170,7 @@ func TestConnect(t *testing.T) { "TrackUsageError": { reason: "We should return any error encountered while tracking ProviderConfig usage", fields: fields{ - usage: resource.TrackerFn(func(_ context.Context, _ resource.Managed) error { return errBoom }), + usage: tfClient.LegacyTrackerFn(func(_ context.Context, _ resource.LegacyManaged) error { return errBoom }), fs: afero.Afero{Fs: afero.NewMemMapFs()}, }, args: args{ @@ -174,7 +178,7 @@ func TestConnect(t *testing.T) { ObjectMeta: metav1.ObjectMeta{UID: uid}, }, }, - want: errors.Wrap(errBoom, errTrackPCUsage), + want: errors.Wrap(errNoProviderConfig, "failed to resolve provider config"), }, "GetProviderConfigError": { reason: "We should return any error encountered while getting our ProviderConfig", @@ -182,7 +186,7 @@ func TestConnect(t *testing.T) { kube: &test.MockClient{ MockGet: test.NewMockGetFn(errBoom), }, - usage: resource.TrackerFn(func(_ context.Context, _ resource.Managed) error { return nil }), + usage: tfClient.LegacyTrackerFn(func(_ context.Context, _ resource.LegacyManaged) error { return nil }), fs: afero.Afero{Fs: afero.NewMemMapFs()}, }, args: args{ @@ -195,7 +199,7 @@ func TestConnect(t *testing.T) { }, }, }, - want: errors.Wrap(errBoom, errGetPC), + want: errors.Wrap(errors.Wrap(errBoom, "cannot get provider config"), "failed to resolve provider config"), }, "GetProviderConfigCredentialsError": { reason: "We should return any error encountered while getting our ProviderConfig credentials", @@ -214,7 +218,7 @@ func TestConnect(t *testing.T) { return nil }), }, - usage: resource.TrackerFn(func(_ context.Context, _ resource.Managed) error { return nil }), + usage: tfClient.LegacyTrackerFn(func(_ context.Context, _ resource.LegacyManaged) error { return nil }), fs: afero.Afero{Fs: afero.NewMemMapFs()}, terraform: func(_ string, _ bool, _ bool, _ logging.Logger, _ ...string) tfclient { return &MockTf{ @@ -248,7 +252,7 @@ func TestConnect(t *testing.T) { return nil }), }, - usage: resource.TrackerFn(func(_ context.Context, _ resource.Managed) error { return nil }), + usage: tfClient.LegacyTrackerFn(func(_ context.Context, _ resource.LegacyManaged) error { return nil }), fs: afero.Afero{ Fs: &ErrFs{ Fs: afero.NewMemMapFs(), @@ -287,7 +291,7 @@ func TestConnect(t *testing.T) { return nil }), }, - usage: resource.TrackerFn(func(_ context.Context, _ resource.Managed) error { return nil }), + usage: tfClient.LegacyTrackerFn(func(_ context.Context, _ resource.LegacyManaged) error { return nil }), fs: afero.Afero{ Fs: &ErrFs{ Fs: afero.NewMemMapFs(), @@ -331,7 +335,7 @@ func TestConnect(t *testing.T) { return nil }), }, - usage: resource.TrackerFn(func(_ context.Context, _ resource.Managed) error { return nil }), + usage: tfClient.LegacyTrackerFn(func(_ context.Context, _ resource.LegacyManaged) error { return nil }), fs: afero.Afero{ Fs: &ErrFs{ Fs: afero.NewMemMapFs(), @@ -374,7 +378,7 @@ func TestConnect(t *testing.T) { return nil }), }, - usage: resource.TrackerFn(func(_ context.Context, _ resource.Managed) error { return nil }), + usage: tfClient.LegacyTrackerFn(func(_ context.Context, _ resource.LegacyManaged) error { return nil }), fs: afero.Afero{ Fs: &ErrFs{ Fs: afero.NewMemMapFs(), @@ -415,7 +419,7 @@ func TestConnect(t *testing.T) { return nil }), }, - usage: resource.TrackerFn(func(_ context.Context, _ resource.Managed) error { return nil }), + usage: tfClient.LegacyTrackerFn(func(_ context.Context, _ resource.LegacyManaged) error { return nil }), fs: afero.Afero{ Fs: &ErrFs{ Fs: afero.NewMemMapFs(), @@ -456,7 +460,7 @@ func TestConnect(t *testing.T) { return nil }), }, - usage: resource.TrackerFn(func(_ context.Context, _ resource.Managed) error { return nil }), + usage: tfClient.LegacyTrackerFn(func(_ context.Context, _ resource.LegacyManaged) error { return nil }), fs: afero.Afero{ Fs: &ErrFs{ Fs: afero.NewMemMapFs(), @@ -492,7 +496,7 @@ func TestConnect(t *testing.T) { kube: &test.MockClient{ MockGet: test.NewMockGetFn(nil), }, - usage: resource.TrackerFn(func(_ context.Context, _ resource.Managed) error { return nil }), + usage: tfClient.LegacyTrackerFn(func(_ context.Context, _ resource.LegacyManaged) error { return nil }), fs: afero.Afero{Fs: afero.NewMemMapFs()}, terraform: func(_ string, _ bool, _ bool, _ logging.Logger, _ ...string) tfclient { return &MockTf{ @@ -522,7 +526,7 @@ func TestConnect(t *testing.T) { kube: &test.MockClient{ MockGet: test.NewMockGetFn(nil), }, - usage: resource.TrackerFn(func(_ context.Context, _ resource.Managed) error { return nil }), + usage: tfClient.LegacyTrackerFn(func(_ context.Context, _ resource.LegacyManaged) error { return nil }), fs: afero.Afero{ Fs: &ErrFs{ Fs: afero.NewMemMapFs(), @@ -557,7 +561,7 @@ func TestConnect(t *testing.T) { kube: &test.MockClient{ MockGet: test.NewMockGetFn(nil), }, - usage: resource.TrackerFn(func(_ context.Context, _ resource.Managed) error { return nil }), + usage: tfClient.LegacyTrackerFn(func(_ context.Context, _ resource.LegacyManaged) error { return nil }), fs: afero.Afero{ Fs: &ErrFs{ Fs: afero.NewMemMapFs(), @@ -593,7 +597,7 @@ func TestConnect(t *testing.T) { kube: &test.MockClient{ MockGet: test.NewMockGetFn(nil), }, - usage: resource.TrackerFn(func(_ context.Context, _ resource.Managed) error { return nil }), + usage: tfClient.LegacyTrackerFn(func(_ context.Context, _ resource.LegacyManaged) error { return nil }), fs: afero.Afero{Fs: afero.NewMemMapFs()}, terraform: func(_ string, _ bool, _ bool, _ logging.Logger, _ ...string) tfclient { return &MockTf{MockInit: func(_ context.Context, _ ...terraform.InitOption) error { return errBoom }} @@ -617,7 +621,7 @@ func TestConnect(t *testing.T) { kube: &test.MockClient{ MockGet: test.NewMockGetFn(nil), }, - usage: resource.TrackerFn(func(_ context.Context, _ resource.Managed) error { return nil }), + usage: tfClient.LegacyTrackerFn(func(_ context.Context, _ resource.LegacyManaged) error { return nil }), fs: afero.Afero{Fs: afero.NewMemMapFs()}, terraform: func(_ string, _ bool, _ bool, _ logging.Logger, _ ...string) tfclient { return &MockTf{ @@ -643,7 +647,7 @@ func TestConnect(t *testing.T) { fields: fields{kube: &test.MockClient{ MockGet: test.NewMockGetFn(nil), }, - usage: resource.TrackerFn(func(_ context.Context, _ resource.Managed) error { return nil }), + usage: tfClient.LegacyTrackerFn(func(_ context.Context, _ resource.LegacyManaged) error { return nil }), fs: afero.Afero{Fs: afero.NewMemMapFs()}, terraform: func(_ string, _ bool, _ bool, _ logging.Logger, _ ...string) tfclient { return &MockTf{ @@ -677,7 +681,7 @@ func TestConnect(t *testing.T) { fields: fields{kube: &test.MockClient{ MockGet: test.NewMockGetFn(nil), }, - usage: resource.TrackerFn(func(_ context.Context, _ resource.Managed) error { return nil }), + usage: tfClient.LegacyTrackerFn(func(_ context.Context, _ resource.LegacyManaged) error { return nil }), fs: afero.Afero{Fs: afero.NewMemMapFs()}, terraform: func(_ string, _ bool, _ bool, _ logging.Logger, _ ...string) tfclient { return &MockTf{ @@ -713,7 +717,7 @@ func TestConnect(t *testing.T) { kube: &test.MockClient{ MockGet: test.NewMockGetFn(nil), }, - usage: resource.TrackerFn(func(_ context.Context, _ resource.Managed) error { return nil }), + usage: tfClient.LegacyTrackerFn(func(_ context.Context, _ resource.LegacyManaged) error { return nil }), fs: afero.Afero{Fs: afero.NewMemMapFs()}, terraform: func(_ string, _ bool, _ bool, _ logging.Logger, _ ...string) tfclient { return &MockTf{ @@ -752,7 +756,7 @@ func TestConnect(t *testing.T) { return nil }), }, - usage: resource.TrackerFn(func(_ context.Context, _ resource.Managed) error { return nil }), + usage: tfClient.LegacyTrackerFn(func(_ context.Context, _ resource.LegacyManaged) error { return nil }), fs: afero.Afero{Fs: afero.NewMemMapFs()}, terraform: func(_ string, _ bool, _ bool, _ logging.Logger, _ ...string) tfclient { return &MockTf{ @@ -922,7 +926,7 @@ func TestObserve(t *testing.T) { }, }, want: want{ - err: errors.Wrap(errors.Wrap(errors.New("json: error calling MarshalJSON for type *runtime.RawExtension: invalid character 'I' looking for beginning of value"), errVarMap), errOptions), + err: errors.Wrap(errors.Wrap(errors.New("json: error calling MarshalJSON for type *runtime.RawExtension: cannot convert RawExtension with unrecognized content type to unstructured"), errVarMap), errOptions), }, }, "DiffError": { @@ -1301,7 +1305,7 @@ func TestCreate(t *testing.T) { }, }, want: want{ - err: errors.Wrap(errors.Wrap(errors.New("json: error calling MarshalJSON for type *runtime.RawExtension: invalid character 'I' looking for beginning of value"), errVarMap), errOptions), + err: errors.Wrap(errors.Wrap(errors.New("json: error calling MarshalJSON for type *runtime.RawExtension: cannot convert RawExtension with unrecognized content type to unstructured"), errVarMap), errOptions), }, }, "ApplyError": { @@ -1511,7 +1515,7 @@ func TestDelete(t *testing.T) { }, }, }, - want: errors.Wrap(errors.Wrap(errors.New("json: error calling MarshalJSON for type *runtime.RawExtension: invalid character 'I' looking for beginning of value"), errVarMap), errOptions), + want: errors.Wrap(errors.Wrap(errors.New("json: error calling MarshalJSON for type *runtime.RawExtension: cannot convert RawExtension with unrecognized content type to unstructured"), errVarMap), errOptions), }, "DestroyError": { reason: "We should return any error we encounter destroying our Terraform configuration", @@ -1563,7 +1567,7 @@ func TestDelete(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { e := external{tf: tc.fields.tf, kube: tc.fields.kube, logger: logging.NewNopLogger()} - err := e.Delete(tc.args.ctx, tc.args.mg) + _, err := e.Delete(tc.args.ctx, tc.args.mg) if diff := cmp.Diff(tc.want, err, test.EquateErrors()); diff != "" { t.Errorf("\n%s\ne.Delete(...): -want error, +got error:\n%s\n", tc.reason, diff) } diff --git a/internal/controller/namespaced/config/config.go b/internal/controller/namespaced/config/config.go new file mode 100644 index 0000000..0204952 --- /dev/null +++ b/internal/controller/namespaced/config/config.go @@ -0,0 +1,65 @@ +/* +Copyright 2020 The Crossplane 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 config + +import ( + "time" + + ctrl "sigs.k8s.io/controller-runtime" + + "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/ratelimiter" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/providerconfig" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + "github.com/upbound/provider-terraform/apis/namespaced/v1beta1" +) + +// Setup adds a controller that reconciles ProviderConfigs by accounting for +// their current usage. +func Setup(mgr ctrl.Manager, o controller.Options, timeout time.Duration, pollJitter time.Duration) error { + name := providerconfig.ControllerName(v1beta1.ProviderConfigGroupKind) + + of := resource.ProviderConfigKinds{ + Config: v1beta1.ProviderConfigGroupVersionKind, + Usage: v1beta1.ProviderConfigUsageGroupVersionKind, + UsageList: v1beta1.ProviderConfigUsageListGroupVersionKind, + } + + r := providerconfig.NewReconciler(mgr, of, + providerconfig.WithLogger(o.Logger.WithValues("controller", name)), + providerconfig.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name)))) + + return ctrl.NewControllerManagedBy(mgr). + Named(name). + WithOptions(o.ForControllerRuntime()). + For(&v1beta1.ProviderConfig{}). + Watches(&v1beta1.ProviderConfigUsage{}, &resource.EnqueueRequestForProviderConfig{}). + Complete(ratelimiter.NewReconciler(name, r, o.GlobalRateLimiter)) +} + +// SetupGated adds a controller that reconciles ProviderConfigs by accounting for +// their current usage. +func SetupGated(mgr ctrl.Manager, o controller.Options, timeout time.Duration, pollJitter time.Duration) error { + o.Gate.Register(func() { + if err := Setup(mgr, o, timeout, pollJitter); err != nil { + mgr.GetLogger().Error(err, "unable to setup reconciler", "gvk", v1beta1.ProviderConfigGroupVersionKind.String()) + } + }, v1beta1.ProviderConfigGroupVersionKind, v1beta1.ProviderConfigUsageGroupVersionKind) + return nil +} diff --git a/internal/controller/namespaced/doc.go b/internal/controller/namespaced/doc.go new file mode 100644 index 0000000..abb60c0 --- /dev/null +++ b/internal/controller/namespaced/doc.go @@ -0,0 +1,17 @@ +/* +Copyright 2020 The Crossplane 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 namespaced diff --git a/internal/controller/namespaced/terraform.go b/internal/controller/namespaced/terraform.go new file mode 100644 index 0000000..f723a09 --- /dev/null +++ b/internal/controller/namespaced/terraform.go @@ -0,0 +1,56 @@ +/* +Copyright 2020 The Crossplane 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 namespaced + +import ( + "time" + + ctrl "sigs.k8s.io/controller-runtime" + + "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + + "github.com/upbound/provider-terraform/internal/controller/namespaced/config" + "github.com/upbound/provider-terraform/internal/controller/namespaced/workspace" +) + +// Setup creates all TF controllers with the supplied logger and adds them +// to the supplied manager. +func Setup(mgr ctrl.Manager, o controller.Options, timeout time.Duration, pollJitter time.Duration) error { + for _, setup := range []func(ctrl.Manager, controller.Options, time.Duration, time.Duration) error{ + config.Setup, + workspace.Setup, + } { + if err := setup(mgr, o, timeout, pollJitter); err != nil { + return err + } + } + return nil +} + +// SetupGated creates all controllers with the supplied logger and adds them to +// the supplied manager gated. +func SetupGated(mgr ctrl.Manager, o controller.Options, timeout time.Duration, pollJitter time.Duration) error { + for _, setup := range []func(ctrl.Manager, controller.Options, time.Duration, time.Duration) error{ + config.SetupGated, + workspace.SetupGated, + } { + if err := setup(mgr, o, timeout, pollJitter); err != nil { + return err + } + } + return nil +} diff --git a/internal/controller/namespaced/workspace/workspace.go b/internal/controller/namespaced/workspace/workspace.go new file mode 100644 index 0000000..cf250c1 --- /dev/null +++ b/internal/controller/namespaced/workspace/workspace.go @@ -0,0 +1,619 @@ +/* +Copyright 2020 The Crossplane 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 workspace + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + "time" + + "github.com/crossplane/crossplane-runtime/v2/pkg/logging" + "github.com/crossplane/crossplane-runtime/v2/pkg/statemetrics" + getter "github.com/hashicorp/go-getter" + "github.com/pkg/errors" + "github.com/spf13/afero" + corev1 "k8s.io/api/core/v1" + extensionsV1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/meta" + "github.com/crossplane/crossplane-runtime/v2/pkg/ratelimiter" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + "github.com/upbound/provider-terraform/apis/namespaced/v1beta1" + tfClient "github.com/upbound/provider-terraform/internal/clients" + "github.com/upbound/provider-terraform/internal/features" + "github.com/upbound/provider-terraform/internal/terraform" + "github.com/upbound/provider-terraform/internal/workdir" + + sourcev1 "github.com/fluxcd/source-controller/api/v1" + sourcev1beta2 "github.com/fluxcd/source-controller/api/v1beta2" +) + +const ( + errNotWorkspace = "managed resource is not a Workspace custom resource" + errTrackPCUsage = "cannot track ProviderConfig usage" + errGetPC = "cannot get ProviderConfig" + errGetCreds = "cannot get credentials" + + errMkdir = "cannot make Terraform configuration directory" + errRemoteModule = "cannot get remote Terraform module" + errFluxArtefactModule = "cannot get Flux Artefact Terraform module" + errSetGitCredDir = "cannot set GIT_CRED_DIR environment variable" + errWriteCreds = "cannot write Terraform credentials" + errWriteGitCreds = "cannot write .git-credentials to /tmp dir" + errWriteConfig = "cannot write Terraform configuration " + tfConfig + errWriteMain = "cannot write Terraform configuration " + errWriteBackend = "cannot write Terraform configuration " + tfBackendFile + errInit = "cannot initialize Terraform configuration" + errWorkspace = "cannot select Terraform workspace" + errResources = "cannot list Terraform resources" + errDiff = "cannot diff (i.e. plan) Terraform configuration" + errOutputs = "cannot list Terraform outputs" + errOptions = "cannot determine Terraform options" + errApply = "cannot apply Terraform configuration" + errDestroy = "cannot destroy Terraform configuration" + errVarFile = "cannot get tfvars" + errVarMap = "cannot get tfvars from var map" + errVarResolution = "cannot resolve variables" + errDeleteWorkspace = "cannot delete Terraform workspace" + errChecksum = "cannot calculate workspace checksum" + + gitCredentialsFilename = ".git-credentials" +) + +const ( + // TODO(negz): Make the Terraform binary path and work dir configurable. + tfPath = "terraform" + tfMain = "main.tf" + tfMainJSON = "main.tf.json" + tfConfig = "crossplane-provider-config.tf" + tfBackendFile = "crossplane.remote.tfbackend" +) + +func envVarFallback(envvar string, fallback string) string { + if value, ok := os.LookupEnv(envvar); ok { + return value + } + return fallback +} + +var tfDir = envVarFallback("XP_TF_DIR", "/tf") + +type tfclient interface { + Init(ctx context.Context, o ...terraform.InitOption) error + Workspace(ctx context.Context, name string) error + Outputs(ctx context.Context) ([]terraform.Output, error) + Resources(ctx context.Context) ([]string, error) + Diff(ctx context.Context, o ...terraform.Option) (bool, error) + Apply(ctx context.Context, o ...terraform.Option) error + Destroy(ctx context.Context, o ...terraform.Option) error + DeleteCurrentWorkspace(ctx context.Context) error + GenerateChecksum(ctx context.Context) (string, error) +} + +// Setup adds a controller that reconciles Workspace managed resources. +func Setup(mgr ctrl.Manager, o controller.Options, timeout, pollJitter time.Duration) error { + name := managed.ControllerName(v1beta1.WorkspaceGroupKind) + + fs := afero.Afero{Fs: afero.NewOsFs()} + gcWorkspace := workdir.NewGarbageCollector(mgr.GetClient(), tfDir, workdir.WithFs(fs), workdir.WithLogger(o.Logger)) + go gcWorkspace.Run(context.TODO(), true) + + gcTmp := workdir.NewGarbageCollector(mgr.GetClient(), filepath.Join("/tmp", tfDir), workdir.WithFs(fs), workdir.WithLogger(o.Logger)) + go gcTmp.Run(context.TODO(), true) + + c := &connector{ + kube: mgr.GetClient(), + usage: resource.NewProviderConfigUsageTracker(mgr.GetClient(), &v1beta1.ProviderConfigUsage{}), + logger: o.Logger, + fs: fs, + terraform: func(dir string, usePluginCache bool, enableTerraformCLILogging bool, logger logging.Logger, envs ...string) tfclient { + return terraform.Harness{Path: tfPath, Dir: dir, UsePluginCache: usePluginCache, EnableTerraformCLILogging: enableTerraformCLILogging, Logger: logger, Envs: envs} + }, + } + + opts := []managed.ReconcilerOption{ + managed.WithPollInterval(o.PollInterval), + managed.WithPollJitterHook(pollJitter), + managed.WithExternalConnecter(c), + managed.WithLogger(o.Logger.WithValues("controller", name)), + managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), + managed.WithTimeout(timeout), + managed.WithMetricRecorder(o.MetricOptions.MRMetrics), + } + + if o.Features.Enabled(features.EnableBetaManagementPolicies) { + opts = append(opts, managed.WithManagementPolicies()) + } + + if err := mgr.Add(statemetrics.NewMRStateRecorder( + mgr.GetClient(), o.Logger, o.MetricOptions.MRStateMetrics, &v1beta1.WorkspaceList{}, o.MetricOptions.PollStateMetricInterval)); err != nil { + return err + } + + r := managed.NewReconciler(mgr, + resource.ManagedKind(v1beta1.WorkspaceGroupVersionKind), + opts...) + + return ctrl.NewControllerManagedBy(mgr). + Named(name). + WithOptions(o.ForControllerRuntime()). + WithEventFilter(resource.DesiredStateChanged()). + For(&v1beta1.Workspace{}). + Complete(ratelimiter.NewReconciler(name, r, o.GlobalRateLimiter)) +} + +// SetupGated adds a controller that reconciles ProviderConfigs by accounting for +// their current usage. +func SetupGated(mgr ctrl.Manager, o controller.Options, timeout time.Duration, pollJitter time.Duration) error { + o.Gate.Register(func() { + if err := Setup(mgr, o, timeout, pollJitter); err != nil { + mgr.GetLogger().Error(err, "unable to setup reconciler", "gvk", v1beta1.WorkspaceGroupVersionKind.String()) + } + }, v1beta1.WorkspaceGroupVersionKind) + return nil +} + +type connector struct { + kube client.Client + usage tfClient.ModernTracker + logger logging.Logger + fs afero.Afero + terraform func(dir string, usePluginCache bool, enableTerraformCLILogging bool, logger logging.Logger, envs ...string) tfclient +} + +func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.ExternalClient, error) { //nolint:gocyclo + // NOTE(negz): This method is slightly over our complexity goal, but I + // can't immediately think of a clean way to decompose it without + // affecting readability. + + cr, ok := mg.(*v1beta1.Workspace) + if !ok { + return nil, errors.New(errNotWorkspace) + } + l := c.logger.WithValues("request", map[string]string{"name": cr.Name}) + // NOTE(negz): This directory will be garbage collected by the workdir + // garbage collector that is started in Setup. + dir := filepath.Join(tfDir, string(cr.GetUID())) + if err := c.fs.MkdirAll(dir, 0700); resource.Ignore(os.IsExist, err) != nil { + return nil, errors.Wrap(err, errMkdir) + } + if err := c.fs.MkdirAll(filepath.Join("/tmp", tfDir), 0700); resource.Ignore(os.IsExist, err) != nil { + return nil, errors.Wrap(err, errMkdir) + } + + pc, err := tfClient.ResolveProviderConfig(ctx, c.kube, nil, c.usage, mg) + if err != nil { + return nil, errors.Wrap(err, "failed to resolve provider config") + } + + // Make git credentials available to inline and remote sources + for _, cd := range pc.Spec.Credentials { + if cd.Filename != gitCredentialsFilename { + continue + } + data, err := resource.CommonCredentialExtractor(ctx, cd.Source, c.kube, cd.CommonCredentialSelectors) + if err != nil { + return nil, errors.Wrap(err, errGetCreds) + } + // NOTE(bobh66): Put the git credentials file in /tmp/tf/ so it doesn't get removed or overwritten + // by the remote module source case + gitCredDir := filepath.Clean(filepath.Join("/tmp", dir)) + if err = c.fs.MkdirAll(gitCredDir, 0700); err != nil { + return nil, errors.Wrap(err, errWriteGitCreds) + } + + // NOTE(ytsarev): Make go-getter pick up .git-credentials, see /.gitconfig in the container image + err = os.Setenv("GIT_CRED_DIR", gitCredDir) + if err != nil { + return nil, errors.Wrap(err, errSetGitCredDir) + } + p := filepath.Clean(filepath.Join(gitCredDir, filepath.Base(cd.Filename))) + if err := c.fs.WriteFile(p, data, 0600); err != nil { + return nil, errors.Wrap(err, errWriteGitCreds) + } + } + + switch cr.Spec.ForProvider.Source { + case v1beta1.ModuleSourceRemote: + gc := getter.Client{ + Src: cr.Spec.ForProvider.Module, + Dst: dir, + Pwd: dir, + + Mode: getter.ClientModeDir, + } + err := gc.Get() + if err != nil { + return nil, errors.Wrap(err, errRemoteModule) + } + + case v1beta1.ModuleSourceInline: + fn := tfMain + if cr.Spec.ForProvider.InlineFormat == v1beta1.FileFormatJSON { + fn = tfMainJSON + } + if err := c.fs.WriteFile(filepath.Join(dir, fn), []byte(cr.Spec.ForProvider.Module), 0600); err != nil { + return nil, errors.Wrap(err, errWriteMain+fn) + } + + case v1beta1.ModuleSourceFlux: + url, err := c.getFluxArtefactURL(ctx, cr.Spec.ForProvider.Module) + if err != nil { + return nil, errors.Wrap(err, errFluxArtefactModule) + } + gc := getter.Client{ + Src: url, + Dst: dir, + Pwd: dir, + + Mode: getter.ClientModeDir, + } + err = gc.Get() + if err != nil { + return nil, errors.Wrap(err, errFluxArtefactModule) + } + } + + if len(cr.Spec.ForProvider.Entrypoint) > 0 { + entrypoint := strings.ReplaceAll(cr.Spec.ForProvider.Entrypoint, "../", "") + dir = filepath.Join(dir, entrypoint) + } + + for _, cd := range pc.Spec.Credentials { + data, err := resource.CommonCredentialExtractor(ctx, cd.Source, c.kube, cd.CommonCredentialSelectors) + if err != nil { + return nil, errors.Wrap(err, errGetCreds) + } + p := filepath.Clean(filepath.Join(dir, filepath.Base(cd.Filename))) + if err := c.fs.WriteFile(p, data, 0600); err != nil { + return nil, errors.Wrap(err, errWriteCreds) + } + } + + if pc.Spec.Configuration != nil { + if err := c.fs.WriteFile(filepath.Join(dir, tfConfig), []byte(*pc.Spec.Configuration), 0600); err != nil { + return nil, errors.Wrap(err, errWriteConfig) + } + } + + if pc.Spec.BackendFile != nil { + if err := c.fs.WriteFile(filepath.Join(dir, tfBackendFile), []byte(*pc.Spec.BackendFile), 0600); err != nil { + return nil, errors.Wrap(err, errWriteBackend) + } + } + + // NOTE(ytsarev): user tf provider cache mechanism to speed up + // reconciliation, see https://developer.hashicorp.com/terraform/cli/config/config-file#provider-plugin-cache + if pc.Spec.PluginCache == nil { + pc.Spec.PluginCache = new(bool) + *pc.Spec.PluginCache = true + } + + envs := make([]string, len(cr.Spec.ForProvider.Env)) + for idx, env := range cr.Spec.ForProvider.Env { + runtimeVal := env.Value + if runtimeVal == "" { + switch { + case env.ConfigMapKeyReference != nil: + cm := &corev1.ConfigMap{} + r := env.ConfigMapKeyReference + nn := types.NamespacedName{Namespace: mg.GetNamespace(), Name: r.Name} + if err := c.kube.Get(ctx, nn, cm); err != nil { + return nil, errors.Wrap(err, errVarResolution) + } + runtimeVal, ok = cm.Data[r.Key] + if !ok { + return nil, errors.Wrap(fmt.Errorf("couldn't find key %v in ConfigMap %v/%v", r.Key, mg.GetNamespace(), r.Name), errVarResolution) + } + case env.SecretKeyReference != nil: + s := &corev1.Secret{} + r := env.SecretKeyReference + nn := types.NamespacedName{Namespace: mg.GetNamespace(), Name: r.Name} + if err := c.kube.Get(ctx, nn, s); err != nil { + return nil, errors.Wrap(err, errVarResolution) + } + secretBytes, ok := s.Data[r.Key] + if !ok { + return nil, errors.Wrap(fmt.Errorf("couldn't find key %v in Secret %v/%v", r.Key, mg.GetNamespace(), r.Name), errVarResolution) + } + runtimeVal = string(secretBytes) + } + } + envs[idx] = strings.Join([]string{env.Name, runtimeVal}, "=") + } + + tf := c.terraform(dir, *pc.Spec.PluginCache, cr.Spec.ForProvider.EnableTerraformCLILogging, l, envs...) + if cr.Status.AtProvider.Checksum != "" { + checksum, err := tf.GenerateChecksum(ctx) + if err != nil { + return nil, errors.Wrap(err, errChecksum) + } + if cr.Status.AtProvider.Checksum == checksum { + l.Debug("Checksums match - skip running terraform init") + return &external{tf: tf, kube: c.kube, logger: c.logger}, errors.Wrap(tf.Workspace(ctx, meta.GetExternalName(cr)), errWorkspace) + } + l.Debug("Checksums don't match so run terraform init:", "old", cr.Status.AtProvider.Checksum, "new", checksum) + } + + o := make([]terraform.InitOption, 0, len(cr.Spec.ForProvider.InitArgs)) + if pc.Spec.BackendFile != nil { + o = append(o, terraform.WithInitArgs([]string{"-backend-config=" + filepath.Join(dir, tfBackendFile)})) + } + o = append(o, terraform.WithInitArgs(cr.Spec.ForProvider.InitArgs)) + if err := tf.Init(ctx, o...); err != nil { + return nil, errors.Wrap(err, errInit) + } + return &external{tf: tf, kube: c.kube}, errors.Wrap(tf.Workspace(ctx, meta.GetExternalName(cr)), errWorkspace) +} + +func (c *connector) getFluxArtefactURL(ctx context.Context, fluxSourceName string) (string, error) { + regexResult := regexp.MustCompile(fmt.Sprintf(`(?i)^(%s|%s)::([^/]+)/(.+)$`, sourcev1.GitRepositoryKind, sourcev1beta2.OCIRepositoryKind)) + matches := regexResult.FindStringSubmatch(fluxSourceName) + if len(matches) != 4 { + return "", fmt.Errorf("invalid module format for flux source. Expected 'FluxSourceKind::namespace/name', got '%s'", fluxSourceName) + } + sourceKind := matches[1] + sourceNamespace := matches[2] + sourceName := matches[3] + + objectKey := client.ObjectKey{ + Namespace: sourceNamespace, + Name: sourceName, + } + + var artefact *sourcev1.Artifact + switch strings.ToLower(sourceKind) { + case strings.ToLower(sourcev1.GitRepositoryKind): + var gitRepo sourcev1.GitRepository + err := c.kube.Get(ctx, objectKey, &gitRepo) + if err != nil { + return "", err + } + artefact = gitRepo.GetArtifact() + case strings.ToLower(sourcev1beta2.OCIRepositoryKind): + var ociRepo sourcev1beta2.OCIRepository + err := c.kube.Get(ctx, objectKey, &ociRepo) + if err != nil { + return "", err + } + artefact = ociRepo.GetArtifact() + } + + if artefact == nil { + return "", fmt.Errorf("artefact for '%s/%s' %s is not ready", sourceNamespace, sourceName, sourceKind) + } + + return artefact.URL, nil +} + +type external struct { + tf tfclient + kube client.Client + logger logging.Logger +} + +func (c *external) checkDiff(ctx context.Context, cr *v1beta1.Workspace) (bool, error) { + o, err := c.options(ctx, cr.Spec.ForProvider, cr.GetNamespace()) + if err != nil { + return false, errors.Wrap(err, errOptions) + } + + o = append(o, terraform.WithArgs(cr.Spec.ForProvider.PlanArgs)) + differs, err := c.tf.Diff(ctx, o...) + if err != nil { + if !meta.WasDeleted(cr) { + return false, errors.Wrap(err, errDiff) + } + // terraform plan can fail on deleted resources, so let the reconciliation loop + // call Delete() if there are still resources in the tfstate file + differs = false + } + return differs, nil +} + +func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { + cr, ok := mg.(*v1beta1.Workspace) + if !ok { + return managed.ExternalObservation{}, errors.New(errNotWorkspace) + } + + differs, err := c.checkDiff(ctx, cr) + if err != nil { + return managed.ExternalObservation{}, err + } + r, err := c.tf.Resources(ctx) + if err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, errResources) + } + if meta.WasDeleted(cr) && len(r) == 0 { + // The CR was deleted and there are no more terraform resources so the workspace can be deleted + if err = c.tf.DeleteCurrentWorkspace(ctx); err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, errDeleteWorkspace) + } + } + // Include any non-sensitive outputs in our status + op, err := c.tf.Outputs(ctx) + if err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, errOutputs) + } + cr.Status.AtProvider = generateWorkspaceObservation(op) + + checksum, err := c.tf.GenerateChecksum(ctx) + if err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, errChecksum) + } + cr.Status.AtProvider.Checksum = checksum + + if !differs { + // TODO(negz): Allow Workspaces to optionally derive their readiness from an + // output - similar to the logic XRs use to derive readiness from a field of + // a composed resource. + cr.Status.SetConditions(xpv1.Available()) + } + + return managed.ExternalObservation{ + ResourceExists: len(r)+len(op) > 0, + ResourceUpToDate: !differs, + ResourceLateInitialized: false, + ConnectionDetails: op2cd(op), + }, nil +} + +func (c *external) Create(ctx context.Context, mg resource.Managed) (managed.ExternalCreation, error) { + // Terraform does not have distinct 'create' and 'update' operations. + u, err := c.Update(ctx, mg) + return managed.ExternalCreation(u), err +} + +func (c *external) Update(ctx context.Context, mg resource.Managed) (managed.ExternalUpdate, error) { + cr, ok := mg.(*v1beta1.Workspace) + if !ok { + return managed.ExternalUpdate{}, errors.New(errNotWorkspace) + } + + o, err := c.options(ctx, cr.Spec.ForProvider, mg.GetNamespace()) + if err != nil { + return managed.ExternalUpdate{}, errors.Wrap(err, errOptions) + } + + o = append(o, terraform.WithArgs(cr.Spec.ForProvider.ApplyArgs)) + if err := c.tf.Apply(ctx, o...); err != nil { + return managed.ExternalUpdate{}, errors.Wrap(err, errApply) + } + + op, err := c.tf.Outputs(ctx) + if err != nil { + return managed.ExternalUpdate{}, errors.Wrap(err, errOutputs) + } + cr.Status.AtProvider = generateWorkspaceObservation(op) + // TODO(negz): Allow Workspaces to optionally derive their readiness from an + // output - similar to the logic XRs use to derive readiness from a field of + // a composed resource. + // Note that since Create() calls this function the Reconciler will overwrite this Available condition with Creating + // on the first pass and it will get reset to Available() by Observe() on the next pass if there are no differences. + // Leave this call for the Update() case. + cr.Status.SetConditions(xpv1.Available()) + return managed.ExternalUpdate{ConnectionDetails: op2cd(op)}, nil +} + +func (c *external) Delete(ctx context.Context, mg resource.Managed) (managed.ExternalDelete, error) { + cr, ok := mg.(*v1beta1.Workspace) + if !ok { + return managed.ExternalDelete{}, errors.New(errNotWorkspace) + } + + o, err := c.options(ctx, cr.Spec.ForProvider, mg.GetNamespace()) + if err != nil { + return managed.ExternalDelete{}, errors.Wrap(err, errOptions) + } + + o = append(o, terraform.WithArgs(cr.Spec.ForProvider.DestroyArgs)) + return managed.ExternalDelete{}, errors.Wrap(c.tf.Destroy(ctx, o...), errDestroy) +} + +func (c *external) Disconnect(ctx context.Context) error { + return nil +} + +//nolint:gocyclo +func (c *external) options(ctx context.Context, p v1beta1.WorkspaceParameters, namespace string) ([]terraform.Option, error) { + o := make([]terraform.Option, 0, len(p.Vars)+len(p.VarFiles)+len(p.DestroyArgs)+len(p.ApplyArgs)+len(p.PlanArgs)) + + for _, v := range p.Vars { + o = append(o, terraform.WithVar(v.Key, v.Value)) + } + + for _, vf := range p.VarFiles { + fmt := terraform.HCL + if vf.Format != nil && *vf.Format == v1beta1.FileFormatJSON { + fmt = terraform.JSON + } + + switch vf.Source { + case v1beta1.VarFileSourceConfigMapKey: + cm := &corev1.ConfigMap{} + r := vf.ConfigMapKeyReference + nn := types.NamespacedName{Namespace: namespace, Name: r.Name} + if err := c.kube.Get(ctx, nn, cm); err != nil { + return nil, errors.Wrap(err, errVarFile) + } + o = append(o, terraform.WithVarFile([]byte(cm.Data[r.Key]), fmt)) + + case v1beta1.VarFileSourceSecretKey: + s := &corev1.Secret{} + r := vf.SecretKeyReference + nn := types.NamespacedName{Namespace: namespace, Name: r.Name} + if err := c.kube.Get(ctx, nn, s); err != nil { + return nil, errors.Wrap(err, errVarFile) + } + o = append(o, terraform.WithVarFile(s.Data[r.Key], fmt)) + } + } + + if p.VarMap != nil { + jsonBytes, err := json.Marshal(p.VarMap) + if err != nil { + return nil, errors.Wrap(err, errVarMap) + } + o = append(o, terraform.WithVarFile(jsonBytes, terraform.JSON)) + } + + return o, nil +} + +func op2cd(o []terraform.Output) managed.ConnectionDetails { + cd := managed.ConnectionDetails{} + for _, op := range o { + if op.Type == terraform.OutputTypeString { + cd[op.Name] = []byte(op.StringValue()) + continue + } + if j, err := op.JSONValue(); err == nil { + cd[op.Name] = j + } + } + return cd +} + +// generateWorkspaceObservation is used to produce v1beta1.WorkspaceObservation from +// workspace_type.Workspace. +func generateWorkspaceObservation(op []terraform.Output) v1beta1.WorkspaceObservation { + wo := v1beta1.WorkspaceObservation{ + Outputs: make(map[string]extensionsV1.JSON, len(op)), + } + for _, o := range op { + if !o.Sensitive { + if j, err := o.JSONValue(); err == nil { + wo.Outputs[o.Name] = extensionsV1.JSON{Raw: j} + } + } + } + return wo +} diff --git a/internal/controller/namespaced/workspace/workspace_test.go b/internal/controller/namespaced/workspace/workspace_test.go new file mode 100644 index 0000000..6b0d679 --- /dev/null +++ b/internal/controller/namespaced/workspace/workspace_test.go @@ -0,0 +1,1735 @@ +/* +Copyright 2020 The Crossplane 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 workspace + +import ( + "context" + "os" + "path/filepath" + "testing" + + xpv2 "github.com/crossplane/crossplane-runtime/v2/apis/common/v2" + "github.com/crossplane/crossplane-runtime/v2/pkg/logging" + "github.com/google/go-cmp/cmp" + "github.com/pkg/errors" + "github.com/spf13/afero" + corev1 "k8s.io/api/core/v1" + extensionsV1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + "github.com/crossplane/crossplane-runtime/v2/pkg/test" + + "github.com/upbound/provider-terraform/apis/namespaced" + "github.com/upbound/provider-terraform/apis/namespaced/v1beta1" + tfClient "github.com/upbound/provider-terraform/internal/clients" + "github.com/upbound/provider-terraform/internal/terraform" +) + +const ( + tfChecksum = "checksum" + errProviderConfigNotSet = "provider config is not set" +) + +type ErrFs struct { + afero.Fs + + errs map[string]error +} + +func (e *ErrFs) MkdirAll(path string, perm os.FileMode) error { + if err := e.errs[path]; err != nil { + return err + } + return e.Fs.MkdirAll(path, perm) +} + +// Called by afero.WriteFile +func (e *ErrFs) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) { + if err := e.errs[name]; err != nil { + return nil, err + } + return e.Fs.OpenFile(name, flag, perm) +} + +type MockTf struct { + MockInit func(ctx context.Context, o ...terraform.InitOption) error + MockWorkspace func(ctx context.Context, name string) error + MockOutputs func(ctx context.Context) ([]terraform.Output, error) + MockResources func(ctx context.Context) ([]string, error) + MockDiff func(ctx context.Context, o ...terraform.Option) (bool, error) + MockApply func(ctx context.Context, o ...terraform.Option) error + MockDestroy func(ctx context.Context, o ...terraform.Option) error + MockDeleteCurrentWorkspace func(ctx context.Context) error + MockGenerateChecksum func(ctx context.Context) (string, error) +} + +func (tf *MockTf) Init(ctx context.Context, o ...terraform.InitOption) error { + return tf.MockInit(ctx, o...) +} + +func (tf *MockTf) GenerateChecksum(ctx context.Context) (string, error) { + return tf.MockGenerateChecksum(ctx) +} + +func (tf *MockTf) Workspace(ctx context.Context, name string) error { + return tf.MockWorkspace(ctx, name) +} + +func (tf *MockTf) Outputs(ctx context.Context) ([]terraform.Output, error) { + return tf.MockOutputs(ctx) +} + +func (tf *MockTf) Resources(ctx context.Context) ([]string, error) { + return tf.MockResources(ctx) +} + +func (tf *MockTf) Diff(ctx context.Context, o ...terraform.Option) (bool, error) { + return tf.MockDiff(ctx, o...) +} + +func (tf *MockTf) Apply(ctx context.Context, o ...terraform.Option) error { + return tf.MockApply(ctx, o...) +} + +func (tf *MockTf) Destroy(ctx context.Context, o ...terraform.Option) error { + return tf.MockDestroy(ctx, o...) +} + +func (tf *MockTf) DeleteCurrentWorkspace(ctx context.Context) error { + return tf.MockDeleteCurrentWorkspace(ctx) +} + +func TestConnect(t *testing.T) { + errBoom := errors.New("boom") + errNoProviderConfig := errors.New(errProviderConfigNotSet) + uid := types.UID("no-you-id") + tfCreds := "credentials" + + type fields struct { + kube client.Client + usage tfClient.ModernTracker + fs afero.Afero + terraform func(dir string, usePluginCache bool, enableTerraformCLILogging bool, logger logging.Logger, envs ...string) tfclient + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + cases := map[string]struct { + reason string + fields fields + args args + want error + }{ + "NotWorkSpaceError": { + reason: "We should return an error if the supplied managed resource is not a Workspace", + fields: fields{}, + args: args{ + mg: nil, + }, + want: errors.New(errNotWorkspace), + }, + "MakeDirError": { + reason: "We should return any error encountered while making a directory for our configuration", + fields: fields{ + fs: afero.Afero{ + Fs: &ErrFs{ + Fs: afero.NewMemMapFs(), + errs: map[string]error{filepath.Join(tfDir, string(uid)): errBoom}, + }, + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{UID: uid}, + }, + }, + want: errors.Wrap(errBoom, errMkdir), + }, + "TrackUsageError": { + reason: "We should return any error encountered while tracking ProviderConfig usage", + fields: fields{ + usage: tfClient.ModernTrackerFn(func(_ context.Context, _ resource.ModernManaged) error { return errBoom }), + fs: afero.Afero{Fs: afero.NewMemMapFs()}, + }, + args: args{ + mg: &v1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{UID: uid}, + }, + }, + want: errors.Wrap(errNoProviderConfig, "failed to resolve provider config"), + }, + "GetProviderConfigError": { + reason: "We should return any error encountered while getting our ProviderConfig", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(errNoProviderConfig), + MockScheme: func() *runtime.Scheme { + s := runtime.NewScheme() + if err := namespaced.AddToScheme(s); err != nil { + t.Fatal(err) + } + return s + }, + }, + usage: tfClient.ModernTrackerFn(func(_ context.Context, _ resource.ModernManaged) error { return nil }), + fs: afero.Afero{Fs: afero.NewMemMapFs()}, + }, + args: args{ + mg: &v1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{UID: uid}, + Spec: v1beta1.WorkspaceSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &xpv1.ProviderConfigReference{ + Kind: "ClusterProviderConfig", + }, + }, + }, + }, + }, + want: errors.Wrap(errors.Wrap(errNoProviderConfig, "cannot get provider config"), "failed to resolve provider config"), + }, + "GetProviderConfigCredentialsError": { + reason: "We should return any error encountered while getting our ProviderConfig credentials", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + if pc, ok := obj.(*v1beta1.ClusterProviderConfig); ok { + // We're testing through CommonCredentialsExtractor + // here. We cause an error to be returned by asking + // for credentials from the environment, but not + // specifying an environment variable. + pc.Spec.Credentials = []v1beta1.ProviderCredentials{{ + Source: xpv1.CredentialsSourceEnvironment, + }} + } + return nil + }), + MockScheme: func() *runtime.Scheme { + s := runtime.NewScheme() + if err := namespaced.AddToScheme(s); err != nil { + t.Fatal(err) + } + return s + }, + }, + usage: tfClient.ModernTrackerFn(func(_ context.Context, _ resource.ModernManaged) error { return nil }), + fs: afero.Afero{Fs: afero.NewMemMapFs()}, + terraform: func(_ string, _ bool, _ bool, _ logging.Logger, _ ...string) tfclient { + return &MockTf{ + MockInit: func(ctx context.Context, o ...terraform.InitOption) error { return nil }, + } + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{UID: uid}, + Spec: v1beta1.WorkspaceSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &xpv1.ProviderConfigReference{ + Kind: "ClusterProviderConfig", + }, + }, + }, + }, + }, + want: errors.Wrap(errors.New("cannot extract from environment variable when none specified"), errGetCreds), + }, + "WriteProviderConfigCredentialsError": { + reason: "We should return any error encountered while writing our ProviderConfig credentials to a file", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + if pc, ok := obj.(*v1beta1.ClusterProviderConfig); ok { + pc.Spec.Credentials = []v1beta1.ProviderCredentials{{ + Filename: tfCreds, + Source: xpv1.CredentialsSourceNone, + }} + } + return nil + }), + MockScheme: func() *runtime.Scheme { + s := runtime.NewScheme() + if err := namespaced.AddToScheme(s); err != nil { + t.Fatal(err) + } + return s + }, + }, + usage: tfClient.ModernTrackerFn(func(_ context.Context, _ resource.ModernManaged) error { return nil }), + fs: afero.Afero{ + Fs: &ErrFs{ + Fs: afero.NewMemMapFs(), + errs: map[string]error{filepath.Join(tfDir, string(uid), tfCreds): errBoom}, + }, + }, + terraform: func(_ string, _ bool, _ bool, _ logging.Logger, _ ...string) tfclient { + return &MockTf{ + MockInit: func(ctx context.Context, o ...terraform.InitOption) error { return nil }, + MockWorkspace: func(ctx context.Context, name string) error { return errors.New(errWriteCreds) }, + } + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{UID: uid}, + Spec: v1beta1.WorkspaceSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &xpv1.ProviderConfigReference{ + Kind: "ClusterProviderConfig", + }, + }, + }, + }, + }, + want: errors.Wrap(errBoom, errWriteCreds), + }, + "WriteProviderConfigCredentialsEntrypointError": { + reason: "We should return any error encountered while writing our ProviderConfig credentials to a file with entrypoint subdir", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + if pc, ok := obj.(*v1beta1.ClusterProviderConfig); ok { + pc.Spec.Credentials = []v1beta1.ProviderCredentials{{ + Filename: tfCreds, + Source: xpv1.CredentialsSourceNone, + }} + } + return nil + }), + MockScheme: func() *runtime.Scheme { + s := runtime.NewScheme() + if err := namespaced.AddToScheme(s); err != nil { + t.Fatal(err) + } + return s + }, + }, + usage: tfClient.ModernTrackerFn(func(_ context.Context, _ resource.ModernManaged) error { return nil }), + fs: afero.Afero{ + Fs: &ErrFs{ + Fs: afero.NewMemMapFs(), + errs: map[string]error{filepath.Join(tfDir, string(uid), "subdir", tfCreds): errBoom}, + }, + }, + terraform: func(_ string, _ bool, _ bool, _ logging.Logger, _ ...string) tfclient { + return &MockTf{ + MockInit: func(ctx context.Context, o ...terraform.InitOption) error { return nil }, + MockWorkspace: func(ctx context.Context, name string) error { return errors.New(errWriteCreds) }, + } + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{UID: uid}, + Spec: v1beta1.WorkspaceSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &xpv1.ProviderConfigReference{ + Kind: "ClusterProviderConfig", + }, + }, + ForProvider: v1beta1.WorkspaceParameters{ + Module: "I'm HCL!", + Source: v1beta1.ModuleSourceInline, + Entrypoint: "subdir", + }, + }, + }, + }, + want: errors.Wrap(errBoom, errWriteCreds), + }, + "WriteProviderGitCredentialsError": { + reason: "We should return any error encountered while writing our git credentials to a file", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + if pc, ok := obj.(*v1beta1.ClusterProviderConfig); ok { + pc.Spec.Credentials = []v1beta1.ProviderCredentials{{ + Filename: ".git-credentials", + Source: xpv1.CredentialsSourceNone, + }} + } + return nil + }), + MockScheme: func() *runtime.Scheme { + s := runtime.NewScheme() + if err := namespaced.AddToScheme(s); err != nil { + t.Fatal(err) + } + return s + }, + }, + usage: tfClient.ModernTrackerFn(func(_ context.Context, _ resource.ModernManaged) error { return nil }), + fs: afero.Afero{ + Fs: &ErrFs{ + Fs: afero.NewMemMapFs(), + errs: map[string]error{filepath.Join("/tmp", tfDir, string(uid), ".git-credentials"): errBoom}, + }, + }, + terraform: func(_ string, _ bool, _ bool, _ logging.Logger, _ ...string) tfclient { + return &MockTf{ + MockInit: func(ctx context.Context, o ...terraform.InitOption) error { return nil }, + } + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{UID: uid}, + Spec: v1beta1.WorkspaceSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &xpv1.ProviderConfigReference{ + Kind: "ClusterProviderConfig", + }, + }, + ForProvider: v1beta1.WorkspaceParameters{ + Module: "github.com/crossplane/rocks", + Source: v1beta1.ModuleSourceRemote, + }, + }, + }, + }, + want: errors.Wrap(errBoom, errWriteGitCreds), + }, + "WriteProviderGitCredentialsMkdirError": { + reason: "We should return any error encountered while creating the credentials directory in /tmp", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + if pc, ok := obj.(*v1beta1.ClusterProviderConfig); ok { + pc.Spec.Credentials = []v1beta1.ProviderCredentials{{ + Filename: ".git-credentials", + Source: xpv1.CredentialsSourceNone, + }} + } + return nil + }), + MockScheme: func() *runtime.Scheme { + s := runtime.NewScheme() + if err := namespaced.AddToScheme(s); err != nil { + t.Fatal(err) + } + return s + }, + }, + usage: tfClient.ModernTrackerFn(func(_ context.Context, _ resource.ModernManaged) error { return nil }), + fs: afero.Afero{ + Fs: &ErrFs{ + Fs: afero.NewMemMapFs(), + errs: map[string]error{filepath.Join("/tmp", tfDir, string(uid)): errBoom}, + }, + }, + terraform: func(_ string, _ bool, _ bool, _ logging.Logger, _ ...string) tfclient { + return &MockTf{ + MockInit: func(ctx context.Context, o ...terraform.InitOption) error { return nil }, + } + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{UID: uid}, + Spec: v1beta1.WorkspaceSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &xpv1.ProviderConfigReference{ + Kind: "ClusterProviderConfig", + }, + }, + ForProvider: v1beta1.WorkspaceParameters{ + Module: "github.com/crossplane/rocks", + Source: v1beta1.ModuleSourceRemote, + }, + }, + }, + }, + want: errors.Wrap(errBoom, errWriteGitCreds), + }, + "WriteConfigError": { + reason: "We should return any error encountered while writing our crossplane-provider-config.tf file", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + if pc, ok := obj.(*v1beta1.ClusterProviderConfig); ok { + cfg := "I'm HCL!" + pc.Spec.Configuration = &cfg + } + return nil + }), + MockScheme: func() *runtime.Scheme { + s := runtime.NewScheme() + if err := namespaced.AddToScheme(s); err != nil { + t.Fatal(err) + } + return s + }, + }, + usage: tfClient.ModernTrackerFn(func(_ context.Context, _ resource.ModernManaged) error { return nil }), + fs: afero.Afero{ + Fs: &ErrFs{ + Fs: afero.NewMemMapFs(), + errs: map[string]error{filepath.Join(tfDir, string(uid), tfConfig): errBoom}, + }, + }, + terraform: func(_ string, _ bool, _ bool, _ logging.Logger, _ ...string) tfclient { + return &MockTf{ + MockInit: func(ctx context.Context, o ...terraform.InitOption) error { return nil }, + } + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{UID: uid}, + Spec: v1beta1.WorkspaceSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &xpv1.ProviderConfigReference{ + Kind: "ClusterProviderConfig", + }, + }, + ForProvider: v1beta1.WorkspaceParameters{ + Module: "I'm HCL!", + Source: v1beta1.ModuleSourceInline, + }, + }, + }, + }, + want: errors.Wrap(errBoom, errWriteConfig), + }, + "WriteConfigEntrypointError": { + reason: "We should return any error encountered while writing our crossplane-provider-config.tf file to entrypoint subdir location", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + if pc, ok := obj.(*v1beta1.ClusterProviderConfig); ok { + cfg := "I'm HCL!" + pc.Spec.Configuration = &cfg + } + return nil + }), + MockScheme: func() *runtime.Scheme { + s := runtime.NewScheme() + if err := namespaced.AddToScheme(s); err != nil { + t.Fatal(err) + } + return s + }, + }, + usage: tfClient.ModernTrackerFn(func(_ context.Context, _ resource.ModernManaged) error { return nil }), + fs: afero.Afero{ + Fs: &ErrFs{ + Fs: afero.NewMemMapFs(), + errs: map[string]error{filepath.Join(tfDir, string(uid), "subdir", tfConfig): errBoom}, + }, + }, + terraform: func(_ string, _ bool, _ bool, _ logging.Logger, _ ...string) tfclient { + return &MockTf{ + MockInit: func(ctx context.Context, o ...terraform.InitOption) error { return nil }, + MockWorkspace: func(ctx context.Context, name string) error { + return errors.Wrap(errBoom, errWriteGitCreds) + }, + } + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{UID: uid}, + Spec: v1beta1.WorkspaceSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &xpv1.ProviderConfigReference{ + Kind: "ClusterProviderConfig", + }, + }, + ForProvider: v1beta1.WorkspaceParameters{ + Module: "I'm HCL!", + Source: v1beta1.ModuleSourceInline, + Entrypoint: "subdir", + }, + }, + }, + }, + want: errors.Wrap(errBoom, errWriteConfig), + }, + "WriteFluxSourceError": { + reason: "We should return any error encountered if module for flux source is malformed", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil), + MockScheme: func() *runtime.Scheme { + s := runtime.NewScheme() + if err := namespaced.AddToScheme(s); err != nil { + t.Fatal(err) + } + return s + }, + }, + usage: tfClient.ModernTrackerFn(func(_ context.Context, _ resource.ModernManaged) error { return nil }), + fs: afero.Afero{Fs: afero.NewMemMapFs()}, + terraform: func(_ string, _ bool, _ bool, _ logging.Logger, _ ...string) tfclient { + return &MockTf{ + MockInit: func(ctx context.Context, o ...terraform.InitOption) error { return nil }, + } + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{UID: uid}, + Spec: v1beta1.WorkspaceSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &xpv1.ProviderConfigReference{ + Kind: "ClusterProviderConfig", + }, + }, + ForProvider: v1beta1.WorkspaceParameters{ + Module: "I'm HCL!", + Source: v1beta1.ModuleSourceFlux, + }, + }, + }, + }, + want: errors.Wrap(errors.New("invalid module format for flux source. Expected 'FluxSourceKind::namespace/name', got 'I'm HCL!'"), errFluxArtefactModule), + }, + "WriteMainError": { + reason: "We should return any error encountered while writing our main.tf file", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil), + MockScheme: func() *runtime.Scheme { + s := runtime.NewScheme() + if err := namespaced.AddToScheme(s); err != nil { + t.Fatal(err) + } + return s + }, + }, + usage: tfClient.ModernTrackerFn(func(_ context.Context, _ resource.ModernManaged) error { return nil }), + fs: afero.Afero{ + Fs: &ErrFs{ + Fs: afero.NewMemMapFs(), + errs: map[string]error{filepath.Join(tfDir, string(uid), tfMain): errBoom}, + }, + }, + terraform: func(_ string, _ bool, _ bool, _ logging.Logger, _ ...string) tfclient { + return &MockTf{ + MockInit: func(ctx context.Context, o ...terraform.InitOption) error { return nil }, + } + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{UID: uid}, + Spec: v1beta1.WorkspaceSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &xpv1.ProviderConfigReference{ + Kind: "ClusterProviderConfig", + }, + }, + ForProvider: v1beta1.WorkspaceParameters{ + Module: "I'm HCL!", + Source: v1beta1.ModuleSourceInline, + }, + }, + }, + }, + want: errors.Wrap(errBoom, errWriteMain+tfMain), + }, + "WriteMainJsonError": { + reason: "We should return any error encountered while writing our main.tf file", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil), + MockScheme: func() *runtime.Scheme { + s := runtime.NewScheme() + if err := namespaced.AddToScheme(s); err != nil { + t.Fatal(err) + } + return s + }, + }, + usage: tfClient.ModernTrackerFn(func(_ context.Context, _ resource.ModernManaged) error { return nil }), + fs: afero.Afero{ + Fs: &ErrFs{ + Fs: afero.NewMemMapFs(), + errs: map[string]error{filepath.Join(tfDir, string(uid), tfMainJSON): errBoom}, + }, + }, + terraform: func(_ string, _ bool, _ bool, _ logging.Logger, _ ...string) tfclient { + return &MockTf{ + MockInit: func(ctx context.Context, o ...terraform.InitOption) error { return nil }, + } + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{UID: uid}, + Spec: v1beta1.WorkspaceSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &xpv1.ProviderConfigReference{ + Kind: "ClusterProviderConfig", + }, + }, + ForProvider: v1beta1.WorkspaceParameters{ + Module: "I'm JSON!", + Source: v1beta1.ModuleSourceInline, + InlineFormat: v1beta1.FileFormatJSON, + }, + }, + }, + }, + want: errors.Wrap(errBoom, errWriteMain+tfMainJSON), + }, + "TerraformInitError": { + reason: "We should return any error encountered while initializing Terraform", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil), + MockScheme: func() *runtime.Scheme { + s := runtime.NewScheme() + if err := namespaced.AddToScheme(s); err != nil { + t.Fatal(err) + } + return s + }, + }, + usage: tfClient.ModernTrackerFn(func(_ context.Context, _ resource.ModernManaged) error { return nil }), + fs: afero.Afero{Fs: afero.NewMemMapFs()}, + terraform: func(_ string, _ bool, _ bool, _ logging.Logger, _ ...string) tfclient { + return &MockTf{MockInit: func(_ context.Context, _ ...terraform.InitOption) error { return errBoom }} + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{UID: uid}, + Spec: v1beta1.WorkspaceSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &xpv1.ProviderConfigReference{ + Kind: "ClusterProviderConfig", + }, + }, + }, + }, + }, + want: errors.Wrap(errBoom, errInit), + }, + "TerraformWorkspaceError": { + reason: "We should return any error encountered while selecting a Terraform workspace", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil), + MockScheme: func() *runtime.Scheme { + s := runtime.NewScheme() + if err := namespaced.AddToScheme(s); err != nil { + t.Fatal(err) + } + return s + }, + }, + usage: tfClient.ModernTrackerFn(func(_ context.Context, _ resource.ModernManaged) error { return nil }), + fs: afero.Afero{Fs: afero.NewMemMapFs()}, + terraform: func(_ string, _ bool, _ bool, _ logging.Logger, _ ...string) tfclient { + return &MockTf{ + MockInit: func(ctx context.Context, o ...terraform.InitOption) error { return nil }, + MockWorkspace: func(_ context.Context, _ string) error { return errBoom }, + } + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{UID: uid}, + Spec: v1beta1.WorkspaceSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &xpv1.ProviderConfigReference{ + Kind: "ClusterProviderConfig", + }, + }, + }, + }, + }, + want: errors.Wrap(errBoom, errWorkspace), + }, + "GenerateChecksumError": { + reason: "We should return any error when generating the workspace checksum", + fields: fields{kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil), + MockScheme: func() *runtime.Scheme { + s := runtime.NewScheme() + if err := namespaced.AddToScheme(s); err != nil { + t.Fatal(err) + } + return s + }, + }, + usage: tfClient.ModernTrackerFn(func(_ context.Context, _ resource.ModernManaged) error { return nil }), + fs: afero.Afero{Fs: afero.NewMemMapFs()}, + terraform: func(_ string, _ bool, _ bool, _ logging.Logger, _ ...string) tfclient { + return &MockTf{ + MockGenerateChecksum: func(ctx context.Context) (string, error) { return "", errBoom }, + } + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{UID: uid}, + Spec: v1beta1.WorkspaceSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &xpv1.ProviderConfigReference{ + Kind: "ClusterProviderConfig", + }, + }, + ForProvider: v1beta1.WorkspaceParameters{ + Module: "I'm HCL!", + Source: v1beta1.ModuleSourceInline, + }, + }, + Status: v1beta1.WorkspaceStatus{ + AtProvider: v1beta1.WorkspaceObservation{ + Checksum: tfChecksum, + }, + }, + }, + }, + want: errors.Wrap(errBoom, errChecksum), + }, + "ChecksumMatches": { + reason: "We should return any error when generating the workspace checksum", + fields: fields{kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil), + MockScheme: func() *runtime.Scheme { + s := runtime.NewScheme() + if err := namespaced.AddToScheme(s); err != nil { + t.Fatal(err) + } + return s + }, + }, + usage: tfClient.ModernTrackerFn(func(_ context.Context, _ resource.ModernManaged) error { return nil }), + fs: afero.Afero{Fs: afero.NewMemMapFs()}, + terraform: func(_ string, _ bool, _ bool, _ logging.Logger, _ ...string) tfclient { + return &MockTf{ + MockGenerateChecksum: func(ctx context.Context) (string, error) { return tfChecksum, nil }, + MockWorkspace: func(_ context.Context, _ string) error { return nil }, + } + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{UID: uid}, + Spec: v1beta1.WorkspaceSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &xpv1.ProviderConfigReference{ + Kind: "ClusterProviderConfig", + }, + }, + ForProvider: v1beta1.WorkspaceParameters{ + Module: "I'm HCL!", + Source: v1beta1.ModuleSourceInline, + }, + }, + Status: v1beta1.WorkspaceStatus{ + AtProvider: v1beta1.WorkspaceObservation{ + Checksum: tfChecksum, + }, + }, + }, + }, + want: nil, + }, + "Success": { + reason: "We should not return an error when we successfully 'connect' to Terraform", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil), + MockScheme: func() *runtime.Scheme { + s := runtime.NewScheme() + if err := namespaced.AddToScheme(s); err != nil { + t.Fatal(err) + } + return s + }, + }, + usage: tfClient.ModernTrackerFn(func(_ context.Context, _ resource.ModernManaged) error { return nil }), + fs: afero.Afero{Fs: afero.NewMemMapFs()}, + terraform: func(_ string, _ bool, _ bool, _ logging.Logger, _ ...string) tfclient { + return &MockTf{ + MockInit: func(ctx context.Context, o ...terraform.InitOption) error { return nil }, + MockGenerateChecksum: func(ctx context.Context) (string, error) { return tfChecksum, nil }, + MockWorkspace: func(_ context.Context, _ string) error { return nil }, + } + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{UID: uid}, + Spec: v1beta1.WorkspaceSpec{ + ForProvider: v1beta1.WorkspaceParameters{ + InitArgs: []string{"-upgrade=true"}, + }, + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &xpv1.ProviderConfigReference{ + Kind: "ClusterProviderConfig", + }, + }, + }, + }, + }, + want: nil, + }, + "SuccessUsingBackendFile": { + reason: "We should not return an error when we successfully 'connect' to Terraform using a Backend file", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + if pc, ok := obj.(*v1beta1.ClusterProviderConfig); ok { + cfg := "I'm HCL!" + backendFile := "I'm a backend!" + pc.Spec.Configuration = &cfg + pc.Spec.BackendFile = &backendFile + } + return nil + }), + MockScheme: func() *runtime.Scheme { + s := runtime.NewScheme() + if err := namespaced.AddToScheme(s); err != nil { + t.Fatal(err) + } + return s + }, + }, + usage: tfClient.ModernTrackerFn(func(_ context.Context, _ resource.ModernManaged) error { return nil }), + fs: afero.Afero{Fs: afero.NewMemMapFs()}, + terraform: func(_ string, _ bool, _ bool, _ logging.Logger, _ ...string) tfclient { + return &MockTf{ + MockInit: func(ctx context.Context, o ...terraform.InitOption) error { + args := terraform.InitArgsToString(o) + if len(args) != 2 { + return errors.New("two init args are expected") + } else if args[0] != "-backend-config=/tf/no-you-id/crossplane.remote.tfbackend" { + return errors.Errorf("backend config arg has invalid value: %s", args[0]) + } + return nil + }, + MockGenerateChecksum: func(ctx context.Context) (string, error) { return tfChecksum, nil }, + MockWorkspace: func(_ context.Context, _ string) error { return nil }, + } + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{UID: uid}, + Spec: v1beta1.WorkspaceSpec{ + ForProvider: v1beta1.WorkspaceParameters{ + InitArgs: []string{"-upgrade=true"}, + }, + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &xpv1.ProviderConfigReference{ + Kind: "ClusterProviderConfig", + }, + }, + }, + }, + }, + want: nil, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + c := connector{ + kube: tc.fields.kube, + usage: tc.fields.usage, + fs: tc.fields.fs, + terraform: tc.fields.terraform, + logger: logging.NewNopLogger(), + } + _, err := c.Connect(tc.args.ctx, tc.args.mg) + if diff := cmp.Diff(tc.want, err, test.EquateErrors()); diff != "" { + t.Errorf("\n%s\ne.Connect(...): -want error, +got error:\n%s\n", tc.reason, diff) + } + }) + } +} + +func TestObserve(t *testing.T) { + errBoom := errors.New("boom") + now := metav1.Now() + type fields struct { + tf tfclient + kube client.Client + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + o managed.ExternalObservation + wo v1beta1.WorkspaceObservation + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "NotAWorkspaceError": { + reason: "We should return an error if the supplied managed resource is not a Workspace", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotWorkspace), + }, + }, + "GetConfigMapError": { + reason: "We should return any error we encounter getting tfvars from a ConfigMap", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + if _, ok := obj.(*corev1.ConfigMap); ok { + return errBoom + } + return nil + }), + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + Spec: v1beta1.WorkspaceSpec{ + ForProvider: v1beta1.WorkspaceParameters{ + VarFiles: []v1beta1.VarFile{ + { + Source: v1beta1.VarFileSourceConfigMapKey, + ConfigMapKeyReference: &v1beta1.KeyReference{}, + }, + }, + }, + }, + }, + }, + want: want{ + err: errors.Wrap(errors.Wrap(errBoom, errVarFile), errOptions), + }, + }, + "GetSecretError": { + reason: "We should return any error we encounter getting tfvars from a Secret", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + if _, ok := obj.(*corev1.Secret); ok { + return errBoom + } + return nil + }), + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + Spec: v1beta1.WorkspaceSpec{ + ForProvider: v1beta1.WorkspaceParameters{ + VarFiles: []v1beta1.VarFile{ + { + Source: v1beta1.VarFileSourceSecretKey, + SecretKeyReference: &v1beta1.KeyReference{}, + }, + }, + }, + }, + }, + }, + want: want{ + err: errors.Wrap(errors.Wrap(errBoom, errVarFile), errOptions), + }, + }, + "GetVarMapError": { + reason: "We should return any error we encounter getting tfvars from varmap", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + if _, ok := obj.(*corev1.Secret); ok { + return errBoom + } + return nil + }), + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + Spec: v1beta1.WorkspaceSpec{ + ForProvider: v1beta1.WorkspaceParameters{ + VarMap: &runtime.RawExtension{ + Raw: []byte("I'm not JSON"), + }, + }, + }, + }, + }, + want: want{ + err: errors.Wrap(errors.Wrap(errors.New("json: error calling MarshalJSON for type *runtime.RawExtension: cannot convert RawExtension with unrecognized content type to unstructured"), errVarMap), errOptions), + }, + }, + "DiffError": { + reason: "We should return any error encountered while diffing the Terraform configuration", + fields: fields{ + tf: &MockTf{ + MockDiff: func(ctx context.Context, o ...terraform.Option) (bool, error) { return false, errBoom }, + }, + }, + args: args{ + mg: &v1beta1.Workspace{}, + }, + want: want{ + err: errors.Wrap(errBoom, errDiff), + }, + }, + "DiffErrorDeletedWithExistingResources": { + reason: "We should return ResourceUpToDate true when resource is deleted and there are existing resources but terraform plan fails", + fields: fields{ + tf: &MockTf{ + MockDiff: func(ctx context.Context, o ...terraform.Option) (bool, error) { return false, errBoom }, + MockGenerateChecksum: func(ctx context.Context) (string, error) { return tfChecksum, nil }, + MockOutputs: func(ctx context.Context) ([]terraform.Output, error) { return nil, nil }, + MockResources: func(ctx context.Context) ([]string, error) { + return []string{"cool_resource.very"}, nil + }, + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{ + DeletionTimestamp: &now, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + ConnectionDetails: managed.ConnectionDetails{}, + }, + wo: v1beta1.WorkspaceObservation{ + Checksum: tfChecksum, + Outputs: map[string]extensionsV1.JSON{}, + }, + }, + }, + "DiffErrorDeletedWithoutExistingResources": { + reason: "We should return ResourceUpToDate true when resource is deleted and there are no existing resources and terraform plan fails", + fields: fields{ + tf: &MockTf{ + MockDiff: func(ctx context.Context, o ...terraform.Option) (bool, error) { return false, errBoom }, + MockGenerateChecksum: func(ctx context.Context) (string, error) { return tfChecksum, nil }, + MockOutputs: func(ctx context.Context) ([]terraform.Output, error) { return nil, nil }, + MockResources: func(ctx context.Context) ([]string, error) { return nil, nil }, + MockDeleteCurrentWorkspace: func(ctx context.Context) error { return nil }, + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{ + DeletionTimestamp: &now, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: false, + ResourceUpToDate: true, + ConnectionDetails: managed.ConnectionDetails{}, + }, + wo: v1beta1.WorkspaceObservation{ + Checksum: tfChecksum, + Outputs: map[string]extensionsV1.JSON{}, + }, + }, + }, + "DiffErrorDeletedWithoutExistingResourcesWorkspaceDeleteError": { + reason: "We should return ResourceUpToDate true when resource is deleted and there are no existing resources and terraform plan fails", + fields: fields{ + tf: &MockTf{ + MockDiff: func(ctx context.Context, o ...terraform.Option) (bool, error) { return false, errBoom }, + MockResources: func(ctx context.Context) ([]string, error) { return nil, nil }, + MockDeleteCurrentWorkspace: func(ctx context.Context) error { return errBoom }, + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{ + DeletionTimestamp: &now, + }, + }, + }, + want: want{ + err: errors.Wrap(errBoom, errDeleteWorkspace), + }, + }, + "ResourcesError": { + reason: "We should return any error encountered while listing extant Terraform resources", + fields: fields{ + tf: &MockTf{ + MockDiff: func(ctx context.Context, o ...terraform.Option) (bool, error) { return false, nil }, + MockResources: func(ctx context.Context) ([]string, error) { return nil, errBoom }, + }, + }, + args: args{ + mg: &v1beta1.Workspace{}, + }, + want: want{ + err: errors.Wrap(errBoom, errResources), + }, + }, + "OutputsError": { + reason: "We should return any error encountered while listing Terraform outputs", + fields: fields{ + tf: &MockTf{ + MockDiff: func(ctx context.Context, o ...terraform.Option) (bool, error) { return false, nil }, + MockResources: func(ctx context.Context) ([]string, error) { return nil, nil }, + MockOutputs: func(ctx context.Context) ([]terraform.Output, error) { return nil, errBoom }, + }, + }, + args: args{ + mg: &v1beta1.Workspace{}, + }, + want: want{ + err: errors.Wrap(errBoom, errOutputs), + }, + }, + "WorkspaceDoesNotExist": { + reason: "A workspace with zero resources should be considered to be non-existent", + fields: fields{ + tf: &MockTf{ + MockDiff: func(ctx context.Context, o ...terraform.Option) (bool, error) { return false, nil }, + MockGenerateChecksum: func(ctx context.Context) (string, error) { return tfChecksum, nil }, + MockResources: func(ctx context.Context) ([]string, error) { return []string{}, nil }, + MockOutputs: func(ctx context.Context) ([]terraform.Output, error) { return nil, nil }, + }, + }, + args: args{ + mg: &v1beta1.Workspace{}, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: false, + ResourceUpToDate: true, + ConnectionDetails: managed.ConnectionDetails{}, + }, + wo: v1beta1.WorkspaceObservation{ + Checksum: tfChecksum, + Outputs: map[string]extensionsV1.JSON{}, + }, + }, + }, + "WorkspaceExists": { + reason: "A workspace with resources should return its outputs as connection details", + fields: fields{ + tf: &MockTf{ + MockDiff: func(ctx context.Context, o ...terraform.Option) (bool, error) { return false, nil }, + MockGenerateChecksum: func(ctx context.Context) (string, error) { return tfChecksum, nil }, + MockResources: func(ctx context.Context) ([]string, error) { + return []string{"cool_resource.very"}, nil + }, + MockOutputs: func(ctx context.Context) ([]terraform.Output, error) { + return []terraform.Output{ + {Name: "string", Type: terraform.OutputTypeString, Sensitive: false}, + {Name: "object", Type: terraform.OutputTypeObject, Sensitive: true}, + }, nil + }, + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + Spec: v1beta1.WorkspaceSpec{ + ForProvider: v1beta1.WorkspaceParameters{ + PlanArgs: []string{"-refresh=false"}, + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + ConnectionDetails: managed.ConnectionDetails{ + "string": []byte{}, + "object": []byte("null"), // Because we JSON decode the the value, which is any{} + }, + }, + wo: v1beta1.WorkspaceObservation{ + Checksum: tfChecksum, + Outputs: map[string]extensionsV1.JSON{ + "string": {Raw: []byte("null")}, + }, + }, + }, + }, + "WorkspaceExistsOnlyOutputs": { + reason: "A workspace with only outputs and no resources should set ResourceExists to true", + fields: fields{ + tf: &MockTf{ + MockDiff: func(ctx context.Context, o ...terraform.Option) (bool, error) { return false, nil }, + MockGenerateChecksum: func(ctx context.Context) (string, error) { return tfChecksum, nil }, + MockResources: func(ctx context.Context) ([]string, error) { + return nil, nil + }, + MockOutputs: func(ctx context.Context) ([]terraform.Output, error) { + return []terraform.Output{ + {Name: "string", Type: terraform.OutputTypeString, Sensitive: false}, + {Name: "object", Type: terraform.OutputTypeObject, Sensitive: true}, + }, nil + }, + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + Spec: v1beta1.WorkspaceSpec{ + ForProvider: v1beta1.WorkspaceParameters{ + PlanArgs: []string{"-refresh=false"}, + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + ConnectionDetails: managed.ConnectionDetails{ + "string": []byte{}, + "object": []byte("null"), // Because we JSON decode the the value, which is any{} + }, + }, + wo: v1beta1.WorkspaceObservation{ + Checksum: tfChecksum, + Outputs: map[string]extensionsV1.JSON{ + "string": {Raw: []byte("null")}, + }, + }, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{tf: tc.fields.tf, kube: tc.fields.kube, logger: logging.NewNopLogger()} + got, err := e.Observe(tc.args.ctx, tc.args.mg) + if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { + t.Errorf("\n%s\ne.Observe(...): -want error, +got error:\n%s\n", tc.reason, diff) + } + if diff := cmp.Diff(tc.want.o, got); diff != "" { + t.Errorf("\n%s\ne.Observe(...): -want, +got:\n%s\n", tc.reason, diff) + } + if tc.args.mg != nil { + if diff := cmp.Diff(tc.want.wo, tc.args.mg.(*v1beta1.Workspace).Status.AtProvider); diff != "" { + t.Errorf("\n%s\ne.Observe(...): -want, +got:\n%s\n", tc.reason, diff) + } + } + }) + } +} + +func TestCreate(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + tf tfclient + kube client.Client + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + c managed.ExternalCreation + wo v1beta1.WorkspaceObservation + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "NotAWorkspaceError": { + reason: "We should return an error if the supplied managed resource is not a Workspace", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotWorkspace), + }, + }, + "GetConfigMapError": { + reason: "We should return any error we encounter getting tfvars from a ConfigMap", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + if _, ok := obj.(*corev1.ConfigMap); ok { + return errBoom + } + return nil + }), + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + Spec: v1beta1.WorkspaceSpec{ + ForProvider: v1beta1.WorkspaceParameters{ + VarFiles: []v1beta1.VarFile{ + { + Source: v1beta1.VarFileSourceConfigMapKey, + ConfigMapKeyReference: &v1beta1.KeyReference{}, + }, + }, + }, + }, + }, + }, + want: want{ + err: errors.Wrap(errors.Wrap(errBoom, errVarFile), errOptions), + }, + }, + "GetSecretError": { + reason: "We should return any error we encounter getting tfvars from a Secret", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + if _, ok := obj.(*corev1.Secret); ok { + return errBoom + } + return nil + }), + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + Spec: v1beta1.WorkspaceSpec{ + ForProvider: v1beta1.WorkspaceParameters{ + VarFiles: []v1beta1.VarFile{ + { + Source: v1beta1.VarFileSourceSecretKey, + SecretKeyReference: &v1beta1.KeyReference{}, + }, + }, + }, + }, + }, + }, + want: want{ + err: errors.Wrap(errors.Wrap(errBoom, errVarFile), errOptions), + }, + }, + "GetVarMapError": { + reason: "We should return any error we encounter getting tfvars from varmap", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + if _, ok := obj.(*corev1.Secret); ok { + return errBoom + } + return nil + }), + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + Spec: v1beta1.WorkspaceSpec{ + ForProvider: v1beta1.WorkspaceParameters{ + VarMap: &runtime.RawExtension{ + Raw: []byte("I'm not JSON"), + }, + }, + }, + }, + }, + want: want{ + err: errors.Wrap(errors.Wrap(errors.New("json: error calling MarshalJSON for type *runtime.RawExtension: cannot convert RawExtension with unrecognized content type to unstructured"), errVarMap), errOptions), + }, + }, + "ApplyError": { + reason: "We should return any error we encounter applying our Terraform configuration", + fields: fields{ + tf: &MockTf{ + MockApply: func(_ context.Context, _ ...terraform.Option) error { return errBoom }, + }, + }, + args: args{ + mg: &v1beta1.Workspace{}, + }, + want: want{ + err: errors.Wrap(errBoom, errApply), + }, + }, + "OutputsError": { + reason: "We should return any error we encounter getting our Terraform outputs", + fields: fields{ + tf: &MockTf{ + MockApply: func(_ context.Context, _ ...terraform.Option) error { return nil }, + MockOutputs: func(ctx context.Context) ([]terraform.Output, error) { return nil, errBoom }, + }, + }, + args: args{ + mg: &v1beta1.Workspace{}, + }, + want: want{ + err: errors.Wrap(errBoom, errOutputs), + }, + }, + "Success": { + reason: "We should refresh our connection details with any updated outputs after successfully applying the Terraform configuration", + fields: fields{ + tf: &MockTf{ + MockApply: func(_ context.Context, _ ...terraform.Option) error { return nil }, + MockGenerateChecksum: func(ctx context.Context) (string, error) { return tfChecksum, nil }, + MockOutputs: func(ctx context.Context) ([]terraform.Output, error) { + return []terraform.Output{ + {Name: "string", Type: terraform.OutputTypeString, Sensitive: true}, + {Name: "object", Type: terraform.OutputTypeObject, Sensitive: false}, + }, nil + }, + }, + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil), + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + Spec: v1beta1.WorkspaceSpec{ + ForProvider: v1beta1.WorkspaceParameters{ + ApplyArgs: []string{"-refresh=false"}, + Vars: []v1beta1.Var{{Key: "super", Value: "cool"}}, + VarFiles: []v1beta1.VarFile{ + { + Source: v1beta1.VarFileSourceConfigMapKey, + ConfigMapKeyReference: &v1beta1.KeyReference{}, + }, + { + Source: v1beta1.VarFileSourceSecretKey, + SecretKeyReference: &v1beta1.KeyReference{}, + Format: &v1beta1.FileFormatJSON, + }, + }, + }, + }, + }, + }, + want: want{ + c: managed.ExternalCreation{ + ConnectionDetails: managed.ConnectionDetails{ + "string": []byte{}, + "object": []byte("null"), // Because we JSON decode the value, which is any{} + }, + }, + wo: v1beta1.WorkspaceObservation{ + Outputs: map[string]extensionsV1.JSON{ + "object": {Raw: []byte("null")}, + }, + }, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{tf: tc.fields.tf, kube: tc.fields.kube, logger: logging.NewNopLogger()} + got, err := e.Create(tc.args.ctx, tc.args.mg) + if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { + t.Errorf("\n%s\ne.Create(...): -want error, +got error:\n%s\n", tc.reason, diff) + } + if diff := cmp.Diff(tc.want.c, got); diff != "" { + t.Errorf("\n%s\ne.Create(...): -want, +got:\n%s\n", tc.reason, diff) + } + if tc.args.mg != nil { + if diff := cmp.Diff(tc.want.wo, tc.args.mg.(*v1beta1.Workspace).Status.AtProvider); diff != "" { + t.Errorf("\n%s\ne.Observe(...): -want, +got:\n%s\n", tc.reason, diff) + } + } + }) + } +} + +func TestDelete(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + tf tfclient + kube client.Client + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + cases := map[string]struct { + reason string + fields fields + args args + want error + }{ + "NotAWorkspaceError": { + reason: "We should return an error if the supplied managed resource is not a Workspace", + args: args{ + mg: nil, + }, + want: errors.New(errNotWorkspace), + }, + "GetConfigMapError": { + reason: "We should return any error we encounter getting tfvars from a ConfigMap", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + if _, ok := obj.(*corev1.ConfigMap); ok { + return errBoom + } + return nil + }), + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + Spec: v1beta1.WorkspaceSpec{ + ForProvider: v1beta1.WorkspaceParameters{ + VarFiles: []v1beta1.VarFile{ + { + Source: v1beta1.VarFileSourceConfigMapKey, + ConfigMapKeyReference: &v1beta1.KeyReference{}, + }, + }, + }, + }, + }, + }, + want: errors.Wrap(errors.Wrap(errBoom, errVarFile), errOptions), + }, + "GetSecretError": { + reason: "We should return any error we encounter getting tfvars from a Secret", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + if _, ok := obj.(*corev1.Secret); ok { + return errBoom + } + return nil + }), + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + Spec: v1beta1.WorkspaceSpec{ + ForProvider: v1beta1.WorkspaceParameters{ + VarFiles: []v1beta1.VarFile{ + { + Source: v1beta1.VarFileSourceSecretKey, + SecretKeyReference: &v1beta1.KeyReference{}, + }, + }, + }, + }, + }, + }, + want: errors.Wrap(errors.Wrap(errBoom, errVarFile), errOptions), + }, + "GetVarMapError": { + reason: "We should return any error we encounter getting tfvars from varmap", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + if _, ok := obj.(*corev1.Secret); ok { + return errBoom + } + return nil + }), + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + Spec: v1beta1.WorkspaceSpec{ + ForProvider: v1beta1.WorkspaceParameters{ + VarMap: &runtime.RawExtension{ + Raw: []byte("I'm not JSON"), + }, + }, + }, + }, + }, + want: errors.Wrap(errors.Wrap(errors.New("json: error calling MarshalJSON for type *runtime.RawExtension: cannot convert RawExtension with unrecognized content type to unstructured"), errVarMap), errOptions), + }, + "DestroyError": { + reason: "We should return any error we encounter destroying our Terraform configuration", + fields: fields{ + tf: &MockTf{ + MockDestroy: func(_ context.Context, _ ...terraform.Option) error { return errBoom }, + }, + }, + args: args{ + mg: &v1beta1.Workspace{}, + }, + want: errors.Wrap(errBoom, errDestroy), + }, + "Success": { + reason: "We should not return an error if we successfully destroy the Terraform configuration", + fields: fields{ + tf: &MockTf{ + MockDestroy: func(_ context.Context, _ ...terraform.Option) error { return nil }, + }, + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil), + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + Spec: v1beta1.WorkspaceSpec{ + ForProvider: v1beta1.WorkspaceParameters{ + DestroyArgs: []string{"-refresh=false"}, + Vars: []v1beta1.Var{{Key: "super", Value: "cool"}}, + VarFiles: []v1beta1.VarFile{ + { + Source: v1beta1.VarFileSourceConfigMapKey, + ConfigMapKeyReference: &v1beta1.KeyReference{}, + }, + { + Source: v1beta1.VarFileSourceSecretKey, + SecretKeyReference: &v1beta1.KeyReference{}, + Format: &v1beta1.FileFormatJSON, + }, + }, + }, + }, + }, + }, + want: nil, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{tf: tc.fields.tf, kube: tc.fields.kube, logger: logging.NewNopLogger()} + _, err := e.Delete(tc.args.ctx, tc.args.mg) + if diff := cmp.Diff(tc.want, err, test.EquateErrors()); diff != "" { + t.Errorf("\n%s\ne.Delete(...): -want error, +got error:\n%s\n", tc.reason, diff) + } + }) + } +} diff --git a/internal/controller/terraform.go b/internal/controller/terraform.go deleted file mode 100644 index fd065e8..0000000 --- a/internal/controller/terraform.go +++ /dev/null @@ -1,40 +0,0 @@ -/* -Copyright 2020 The Crossplane 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 controller - -import ( - "time" - - ctrl "sigs.k8s.io/controller-runtime" - - "github.com/crossplane/crossplane-runtime/pkg/controller" - - "github.com/upbound/provider-terraform/internal/controller/config" - "github.com/upbound/provider-terraform/internal/controller/workspace" -) - -// Setup creates all terraform controllers with the supplied options and adds -// them to the supplied manager. -func Setup(mgr ctrl.Manager, o controller.Options, timeout, pollJitter time.Duration) error { - if err := config.Setup(mgr, o, timeout); err != nil { - return err - } - if err := workspace.Setup(mgr, o, timeout, pollJitter); err != nil { - return err - } - return nil -} diff --git a/internal/controller/features/features.go b/internal/features/features.go similarity index 94% rename from internal/controller/features/features.go rename to internal/features/features.go index fad6960..d5348a2 100644 --- a/internal/controller/features/features.go +++ b/internal/features/features.go @@ -16,7 +16,7 @@ package features -import "github.com/crossplane/crossplane-runtime/pkg/feature" +import "github.com/crossplane/crossplane-runtime/v2/pkg/feature" // Feature flags. const ( diff --git a/internal/terraform/terraform.go b/internal/terraform/terraform.go index f0536b7..3a4db6e 100644 --- a/internal/terraform/terraform.go +++ b/internal/terraform/terraform.go @@ -35,7 +35,7 @@ import ( "sync" "syscall" - "github.com/crossplane/crossplane-runtime/pkg/logging" + "github.com/crossplane/crossplane-runtime/v2/pkg/logging" "github.com/pkg/errors" ) diff --git a/internal/terraform/terraform_harness_test.go b/internal/terraform/terraform_harness_test.go index f9bcaf7..79d94a8 100644 --- a/internal/terraform/terraform_harness_test.go +++ b/internal/terraform/terraform_harness_test.go @@ -30,7 +30,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/pkg/errors" - "github.com/crossplane/crossplane-runtime/pkg/test" + "github.com/crossplane/crossplane-runtime/v2/pkg/test" ) // Terraform binary to invoke. diff --git a/internal/workdir/workdir.go b/internal/workdir/workdir.go index b684154..df25a38 100644 --- a/internal/workdir/workdir.go +++ b/internal/workdir/workdir.go @@ -22,13 +22,14 @@ import ( "strings" "time" - "github.com/crossplane/crossplane-runtime/pkg/logging" + "github.com/crossplane/crossplane-runtime/v2/pkg/logging" "github.com/google/uuid" "github.com/pkg/errors" "github.com/spf13/afero" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/upbound/provider-terraform/apis/v1beta1" + clusterv1beta1 "github.com/upbound/provider-terraform/apis/cluster/v1beta1" + namespacedv1beta1 "github.com/upbound/provider-terraform/apis/namespaced/v1beta1" ) // Error strings. @@ -87,14 +88,14 @@ func NewGarbageCollector(c client.Client, parentDir string, o ...GarbageCollecto } // Run the garbage collector. Blocks until the supplied context is done. -func (gc *GarbageCollector) Run(ctx context.Context) { +func (gc *GarbageCollector) Run(ctx context.Context, namespaced bool) { t := time.NewTicker(gc.interval) for { select { case <-ctx.Done(): return case <-t.C: - if err := gc.collect(ctx); err != nil { + if err := gc.collect(ctx, namespaced); err != nil { gc.log.Info("Garbage collection failed", "error", err) } } @@ -106,15 +107,25 @@ func isUUID(u string) bool { return err == nil } -func (gc *GarbageCollector) collect(ctx context.Context) error { - l := &v1beta1.WorkspaceList{} - if err := gc.kube.List(ctx, l); err != nil { - return errors.Wrap(err, errListWorkspaces) - } - +func (gc *GarbageCollector) collect(ctx context.Context, namespaced bool) error { exists := map[string]bool{} - for _, ws := range l.Items { - exists[string(ws.GetUID())] = true + + if namespaced { + l := &namespacedv1beta1.WorkspaceList{} + if err := gc.kube.List(ctx, l); err != nil { + return errors.Wrap(err, errListWorkspaces) + } + for _, ws := range l.Items { + exists[string(ws.GetUID())] = true + } + } else { + l := &clusterv1beta1.WorkspaceList{} + if err := gc.kube.List(ctx, l); err != nil { + return errors.Wrap(err, errListWorkspaces) + } + for _, ws := range l.Items { + exists[string(ws.GetUID())] = true + } } fis, err := gc.fs.ReadDir(gc.parentDir) if err != nil { diff --git a/internal/workdir/workdir_test.go b/internal/workdir/workdir_test.go index 062e13d..969e728 100644 --- a/internal/workdir/workdir_test.go +++ b/internal/workdir/workdir_test.go @@ -30,9 +30,9 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/crossplane/crossplane-runtime/pkg/test" + "github.com/crossplane/crossplane-runtime/v2/pkg/test" - "github.com/upbound/provider-terraform/apis/v1beta1" + "github.com/upbound/provider-terraform/apis/cluster/v1beta1" ) func withDirs(fs afero.Afero, dir ...string) afero.Afero { @@ -126,7 +126,7 @@ func TestCollect(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { gc := NewGarbageCollector(tc.fields.kube, tc.fields.parentdDir, WithFs(tc.fields.fs)) - err := gc.collect(tc.args.ctx) + err := gc.collect(tc.args.ctx, false) if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { t.Errorf("gc.collect(...): -want error, +got error:\n%s", diff) } diff --git a/package/crds/tf.m.upbound.io_clusterproviderconfigs.yaml b/package/crds/tf.m.upbound.io_clusterproviderconfigs.yaml new file mode 100644 index 0000000..54c1d8f --- /dev/null +++ b/package/crds/tf.m.upbound.io_clusterproviderconfigs.yaml @@ -0,0 +1,198 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: clusterproviderconfigs.tf.m.upbound.io +spec: + group: tf.m.upbound.io + names: + categories: + - crossplane + - provider + - terraform + kind: ClusterProviderConfig + listKind: ClusterProviderConfigList + plural: clusterproviderconfigs + singular: clusterproviderconfig + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + - jsonPath: .spec.credentials.secretRef.name + name: SECRET-NAME + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: A ClusterProviderConfig configures a Terraform provider. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: A ProviderConfigSpec defines the desired state of a ProviderConfig. + properties: + backendFile: + description: |- + Terraform backend file configuration content, + it has the contents of the backend block as top-level attributes, + without the need to wrap it in another terraform or backend block. + More details at https://developer.hashicorp.com/terraform/language/settings/backends/configuration#file. + type: string + configuration: + description: |- + Configuration that should be injected into all workspaces that use + this provider config, expressed as inline HCL. This can be used to + automatically inject Terraform provider configuration blocks. + type: string + credentials: + description: Credentials required to authenticate to this provider. + items: + description: ProviderCredentials required to authenticate. + properties: + env: + description: |- + Env is a reference to an environment variable that contains credentials + that must be used to connect to the provider. + properties: + name: + description: Name is the name of an environment variable. + type: string + required: + - name + type: object + filename: + description: |- + Filename (relative to main.tf) to which these provider credentials + should be written. + type: string + fs: + description: |- + Fs is a reference to a filesystem location that contains credentials that + must be used to connect to the provider. + properties: + path: + description: Path is a filesystem path. + type: string + required: + - path + type: object + secretRef: + description: |- + A SecretRef is a reference to a secret key that contains the credentials + that must be used to connect to the provider. + properties: + key: + description: The key to select. + type: string + name: + description: Name of the secret. + type: string + namespace: + description: Namespace of the secret. + type: string + required: + - key + - name + - namespace + type: object + source: + description: Source of the provider credentials. + enum: + - None + - Secret + - Environment + - Filesystem + type: string + required: + - filename + - source + type: object + type: array + pluginCache: + default: true + description: |- + PluginCache enables terraform provider plugin caching mechanism + https://developer.hashicorp.com/terraform/cli/config/config-file#provider-plugin-cache + type: boolean + type: object + status: + description: A ProviderConfigStatus reflects the observed state of a ProviderConfig. + properties: + conditions: + description: Conditions of the resource. + items: + description: A Condition that may apply to a resource. + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time this condition transitioned from one + status to another. + format: date-time + type: string + message: + description: |- + A Message containing details about this condition's last transition from + one status to another, if any. + type: string + observedGeneration: + description: |- + ObservedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + type: integer + reason: + description: A Reason for this condition's last transition from + one status to another. + type: string + status: + description: Status of this condition; is it currently True, + False, or Unknown? + type: string + type: + description: |- + Type of this condition. At most one of each condition type may apply to + a resource at any point in time. + type: string + required: + - lastTransitionTime + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + users: + description: Users of this provider configuration. + format: int64 + type: integer + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/package/crds/tf.m.upbound.io_providerconfigs.yaml b/package/crds/tf.m.upbound.io_providerconfigs.yaml new file mode 100644 index 0000000..a0b3f46 --- /dev/null +++ b/package/crds/tf.m.upbound.io_providerconfigs.yaml @@ -0,0 +1,198 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: providerconfigs.tf.m.upbound.io +spec: + group: tf.m.upbound.io + names: + categories: + - crossplane + - provider + - terraform + kind: ProviderConfig + listKind: ProviderConfigList + plural: providerconfigs + singular: providerconfig + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + - jsonPath: .spec.credentials.secretRef.name + name: SECRET-NAME + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: A ProviderConfig configures a Terraform provider. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: A ProviderConfigSpec defines the desired state of a ProviderConfig. + properties: + backendFile: + description: |- + Terraform backend file configuration content, + it has the contents of the backend block as top-level attributes, + without the need to wrap it in another terraform or backend block. + More details at https://developer.hashicorp.com/terraform/language/settings/backends/configuration#file. + type: string + configuration: + description: |- + Configuration that should be injected into all workspaces that use + this provider config, expressed as inline HCL. This can be used to + automatically inject Terraform provider configuration blocks. + type: string + credentials: + description: Credentials required to authenticate to this provider. + items: + description: ProviderCredentials required to authenticate. + properties: + env: + description: |- + Env is a reference to an environment variable that contains credentials + that must be used to connect to the provider. + properties: + name: + description: Name is the name of an environment variable. + type: string + required: + - name + type: object + filename: + description: |- + Filename (relative to main.tf) to which these provider credentials + should be written. + type: string + fs: + description: |- + Fs is a reference to a filesystem location that contains credentials that + must be used to connect to the provider. + properties: + path: + description: Path is a filesystem path. + type: string + required: + - path + type: object + secretRef: + description: |- + A SecretRef is a reference to a secret key that contains the credentials + that must be used to connect to the provider. + properties: + key: + description: The key to select. + type: string + name: + description: Name of the secret. + type: string + namespace: + description: Namespace of the secret. + type: string + required: + - key + - name + - namespace + type: object + source: + description: Source of the provider credentials. + enum: + - None + - Secret + - Environment + - Filesystem + type: string + required: + - filename + - source + type: object + type: array + pluginCache: + default: true + description: |- + PluginCache enables terraform provider plugin caching mechanism + https://developer.hashicorp.com/terraform/cli/config/config-file#provider-plugin-cache + type: boolean + type: object + status: + description: A ProviderConfigStatus reflects the observed state of a ProviderConfig. + properties: + conditions: + description: Conditions of the resource. + items: + description: A Condition that may apply to a resource. + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time this condition transitioned from one + status to another. + format: date-time + type: string + message: + description: |- + A Message containing details about this condition's last transition from + one status to another, if any. + type: string + observedGeneration: + description: |- + ObservedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + type: integer + reason: + description: A Reason for this condition's last transition from + one status to another. + type: string + status: + description: Status of this condition; is it currently True, + False, or Unknown? + type: string + type: + description: |- + Type of this condition. At most one of each condition type may apply to + a resource at any point in time. + type: string + required: + - lastTransitionTime + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + users: + description: Users of this provider configuration. + format: int64 + type: integer + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/package/crds/tf.m.upbound.io_providerconfigusages.yaml b/package/crds/tf.m.upbound.io_providerconfigusages.yaml new file mode 100644 index 0000000..f465e51 --- /dev/null +++ b/package/crds/tf.m.upbound.io_providerconfigusages.yaml @@ -0,0 +1,96 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: providerconfigusages.tf.m.upbound.io +spec: + group: tf.m.upbound.io + names: + categories: + - crossplane + - provider + - terraform + kind: ProviderConfigUsage + listKind: ProviderConfigUsageList + plural: providerconfigusages + singular: providerconfigusage + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + - jsonPath: .providerConfigRef.name + name: CONFIG-NAME + type: string + - jsonPath: .resourceRef.kind + name: RESOURCE-KIND + type: string + - jsonPath: .resourceRef.name + name: RESOURCE-NAME + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: A ProviderConfigUsage indicates that a resource is using a ProviderConfig. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + providerConfigRef: + description: ProviderConfigReference to the provider config being used. + properties: + kind: + description: Kind of the referenced object. + type: string + name: + description: Name of the referenced object. + type: string + required: + - kind + - name + type: object + resourceRef: + description: ResourceReference to the managed resource using the provider + config. + properties: + apiVersion: + description: APIVersion of the referenced object. + type: string + kind: + description: Kind of the referenced object. + type: string + name: + description: Name of the referenced object. + type: string + uid: + description: UID of the referenced object. + type: string + required: + - apiVersion + - kind + - name + type: object + required: + - providerConfigRef + - resourceRef + type: object + served: true + storage: true + subresources: {} diff --git a/package/crds/tf.m.upbound.io_workspaces.yaml b/package/crds/tf.m.upbound.io_workspaces.yaml new file mode 100644 index 0000000..46b6abc --- /dev/null +++ b/package/crds/tf.m.upbound.io_workspaces.yaml @@ -0,0 +1,364 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: workspaces.tf.m.upbound.io +spec: + group: tf.m.upbound.io + names: + categories: + - crossplane + - managed + - terraform + kind: Workspace + listKind: WorkspaceList + plural: workspaces + singular: workspace + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Synced')].status + name: SYNCED + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: READY + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: A Workspace of Terraform Configuration. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: A WorkspaceSpec defines the desired state of a Workspace. + properties: + forProvider: + description: WorkspaceParameters are the configurable fields of a + Workspace. + properties: + applyArgs: + description: Arguments to be included in the terraform apply CLI + command + items: + type: string + type: array + destroyArgs: + description: Arguments to be included in the terraform destroy + CLI command + items: + type: string + type: array + enableTerraformCLILogging: + description: Boolean value to indicate CLI logging of terraform + execution is enabled or not + type: boolean + entrypoint: + default: "" + description: Entrypoint for `terraform init` within the module + type: string + env: + description: Environment variables. + items: + description: An EnvVar specifies an environment variable to + be set for the workspace. + properties: + configMapKeyRef: + description: A ConfigMap key containing the desired env + var value. + properties: + key: + description: Key within the referenced resource. + type: string + name: + description: Name of the referenced resource. + type: string + required: + - key + - name + type: object + name: + type: string + secretKeyRef: + description: A Secret key containing the desired env var + value. + properties: + key: + description: Key within the referenced resource. + type: string + name: + description: Name of the referenced resource. + type: string + required: + - key + - name + type: object + value: + type: string + required: + - name + type: object + type: array + initArgs: + description: Arguments to be included in the terraform init CLI + command + items: + type: string + type: array + inlineFormat: + description: |- + Specifies the format of the inline Terraform content + if Source is 'Inline' + enum: + - HCL + - JSON + type: string + module: + description: |- + The root module of this workspace; i.e. the module containing its main.tf + file. When the workspace's source is 'Remote' (the default) this can be + any address supported by terraform init -from-module, for example a git + repository or an S3 bucket. When the workspace's source is 'Inline' the + content of a simple main.tf or main.tf.json file may be written inline. + When the workspace's source is 'Flux', use the FluxSourceKind::namespace/name format. + Example: + Module: "GitRepository::my-namespace/my-repo" + type: string + planArgs: + description: Arguments to be included in the terraform plan CLI + command + items: + type: string + type: array + source: + description: Source of the root module of this workspace. + enum: + - Remote + - Inline + - Flux + type: string + varFiles: + description: |- + Files of configuration variables. Explicitly declared vars take + precedence. + items: + description: A VarFile is a file containing many Terraform variables. + properties: + configMapKeyRef: + description: A ConfigMap key containing the vars file. + properties: + key: + description: Key within the referenced resource. + type: string + name: + description: Name of the referenced resource. + type: string + required: + - key + - name + type: object + format: + default: HCL + description: Format of this vars file. + enum: + - HCL + - JSON + type: string + secretKeyRef: + description: A Secret key containing the vars file. + properties: + key: + description: Key within the referenced resource. + type: string + name: + description: Name of the referenced resource. + type: string + required: + - key + - name + type: object + source: + description: Source of this vars file. + enum: + - ConfigMapKey + - SecretKey + type: string + required: + - source + type: object + type: array + varmap: + description: Terraform Variable Map. Should be a valid JSON representation + of the input vars + type: object + x-kubernetes-preserve-unknown-fields: true + vars: + description: Configuration variables. + items: + description: A Var represents a Terraform configuration variable. + properties: + key: + type: string + value: + type: string + required: + - key + - value + type: object + type: array + required: + - module + - source + type: object + managementPolicies: + default: + - '*' + description: |- + THIS IS A BETA FIELD. It is on by default but can be opted out + through a Crossplane feature flag. + ManagementPolicies specify the array of actions Crossplane is allowed to + take on the managed and external resources. + See the design doc for more information: https://github.com/crossplane/crossplane/blob/499895a25d1a1a0ba1604944ef98ac7a1a71f197/design/design-doc-observe-only-resources.md?plain=1#L223 + and this one: https://github.com/crossplane/crossplane/blob/444267e84783136daa93568b364a5f01228cacbe/design/one-pager-ignore-changes.md + items: + description: |- + A ManagementAction represents an action that the Crossplane controllers + can take on an external resource. + enum: + - Observe + - Create + - Update + - Delete + - LateInitialize + - '*' + type: string + type: array + providerConfigRef: + default: + kind: ClusterProviderConfig + name: default + description: |- + ProviderConfigReference specifies how the provider that will be used to + create, observe, update, and delete this managed resource should be + configured. + properties: + kind: + description: Kind of the referenced object. + type: string + name: + description: Name of the referenced object. + type: string + required: + - kind + - name + type: object + writeConnectionSecretToRef: + description: |- + WriteConnectionSecretToReference specifies the namespace and name of a + Secret to which any connection details for this managed resource should + be written. Connection details frequently include the endpoint, username, + and password required to connect to the managed resource. + properties: + name: + description: Name of the secret. + type: string + required: + - name + type: object + required: + - forProvider + type: object + status: + description: A WorkspaceStatus represents the observed state of a Workspace. + properties: + atProvider: + description: WorkspaceObservation are the observable fields of a Workspace. + properties: + checksum: + type: string + outputs: + additionalProperties: + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + conditions: + description: Conditions of the resource. + items: + description: A Condition that may apply to a resource. + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time this condition transitioned from one + status to another. + format: date-time + type: string + message: + description: |- + A Message containing details about this condition's last transition from + one status to another, if any. + type: string + observedGeneration: + description: |- + ObservedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + type: integer + reason: + description: A Reason for this condition's last transition from + one status to another. + type: string + status: + description: Status of this condition; is it currently True, + False, or Unknown? + type: string + type: + description: |- + Type of this condition. At most one of each condition type may apply to + a resource at any point in time. + type: string + required: + - lastTransitionTime + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + observedGeneration: + description: |- + ObservedGeneration is the latest metadata.generation + which resulted in either a ready state, or stalled due to error + it can not recover from without human intervention. + format: int64 + type: integer + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/package/crds/tf.upbound.io_providerconfigs.yaml b/package/crds/tf.upbound.io_providerconfigs.yaml index dca7409..e742637 100644 --- a/package/crds/tf.upbound.io_providerconfigs.yaml +++ b/package/crds/tf.upbound.io_providerconfigs.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.18.0 name: providerconfigs.tf.upbound.io spec: group: tf.upbound.io diff --git a/package/crds/tf.upbound.io_providerconfigusages.yaml b/package/crds/tf.upbound.io_providerconfigusages.yaml index 58bb29c..de18bcf 100644 --- a/package/crds/tf.upbound.io_providerconfigusages.yaml +++ b/package/crds/tf.upbound.io_providerconfigusages.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.18.0 name: providerconfigusages.tf.upbound.io spec: group: tf.upbound.io diff --git a/package/crds/tf.upbound.io_storeconfigs.yaml b/package/crds/tf.upbound.io_storeconfigs.yaml deleted file mode 100644 index 7acd7da..0000000 --- a/package/crds/tf.upbound.io_storeconfigs.yaml +++ /dev/null @@ -1,223 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.14.0 - name: storeconfigs.tf.upbound.io -spec: - group: tf.upbound.io - names: - categories: - - crossplane - - store - - gcp - kind: StoreConfig - listKind: StoreConfigList - plural: storeconfigs - singular: storeconfig - scope: Cluster - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: AGE - type: date - - jsonPath: .spec.type - name: TYPE - type: string - - jsonPath: .spec.defaultScope - name: DEFAULT-SCOPE - type: string - name: v1beta1 - schema: - openAPIV3Schema: - description: A StoreConfig configures how GCP controller should store connection - details. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: A StoreConfigSpec defines the desired state of a ProviderConfig. - properties: - defaultScope: - description: |- - DefaultScope used for scoping secrets for "cluster-scoped" resources. - If store type is "Kubernetes", this would mean the default namespace to - store connection secrets for cluster scoped resources. - In case of "Vault", this would be used as the default parent path. - Typically, should be set as Crossplane installation namespace. - type: string - kubernetes: - description: |- - Kubernetes configures a Kubernetes secret store. - If the "type" is "Kubernetes" but no config provided, in cluster config - will be used. - properties: - auth: - description: Credentials used to connect to the Kubernetes API. - properties: - env: - description: |- - Env is a reference to an environment variable that contains credentials - that must be used to connect to the provider. - properties: - name: - description: Name is the name of an environment variable. - type: string - required: - - name - type: object - fs: - description: |- - Fs is a reference to a filesystem location that contains credentials that - must be used to connect to the provider. - properties: - path: - description: Path is a filesystem path. - type: string - required: - - path - type: object - secretRef: - description: |- - A SecretRef is a reference to a secret key that contains the credentials - that must be used to connect to the provider. - properties: - key: - description: The key to select. - type: string - name: - description: Name of the secret. - type: string - namespace: - description: Namespace of the secret. - type: string - required: - - key - - name - - namespace - type: object - source: - description: Source of the credentials. - enum: - - None - - Secret - - Environment - - Filesystem - type: string - required: - - source - type: object - required: - - auth - type: object - plugin: - description: Plugin configures External secret store as a plugin. - properties: - configRef: - description: ConfigRef contains store config reference info. - properties: - apiVersion: - description: APIVersion of the referenced config. - type: string - kind: - description: Kind of the referenced config. - type: string - name: - description: Name of the referenced config. - type: string - required: - - apiVersion - - kind - - name - type: object - endpoint: - description: Endpoint is the endpoint of the gRPC server. - type: string - type: object - type: - default: Kubernetes - description: |- - Type configures which secret store to be used. Only the configuration - block for this store will be used and others will be ignored if provided. - Default is Kubernetes. - enum: - - Kubernetes - - Vault - - Plugin - type: string - required: - - defaultScope - type: object - status: - description: A StoreConfigStatus represents the status of a StoreConfig. - properties: - conditions: - description: Conditions of the resource. - items: - description: A Condition that may apply to a resource. - properties: - lastTransitionTime: - description: |- - LastTransitionTime is the last time this condition transitioned from one - status to another. - format: date-time - type: string - message: - description: |- - A Message containing details about this condition's last transition from - one status to another, if any. - type: string - observedGeneration: - description: |- - ObservedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - type: integer - reason: - description: A Reason for this condition's last transition from - one status to another. - type: string - status: - description: Status of this condition; is it currently True, - False, or Unknown? - type: string - type: - description: |- - Type of this condition. At most one of each condition type may apply to - a resource at any point in time. - type: string - required: - - lastTransitionTime - - reason - - status - - type - type: object - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} diff --git a/package/crds/tf.upbound.io_workspaces.yaml b/package/crds/tf.upbound.io_workspaces.yaml index dd78e3f..af4fdf1 100644 --- a/package/crds/tf.upbound.io_workspaces.yaml +++ b/package/crds/tf.upbound.io_workspaces.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.18.0 name: workspaces.tf.upbound.io spec: group: tf.upbound.io @@ -326,93 +326,12 @@ spec: required: - name type: object - publishConnectionDetailsTo: - description: |- - PublishConnectionDetailsTo specifies the connection secret config which - contains a name, metadata and a reference to secret store config to - which any connection details for this managed resource should be written. - Connection details frequently include the endpoint, username, - and password required to connect to the managed resource. - properties: - configRef: - default: - name: default - description: |- - SecretStoreConfigRef specifies which secret store config should be used - for this ConnectionSecret. - properties: - name: - description: Name of the referenced object. - type: string - policy: - description: Policies for referencing. - properties: - resolution: - default: Required - description: |- - Resolution specifies whether resolution of this reference is required. - The default is 'Required', which means the reconcile will fail if the - reference cannot be resolved. 'Optional' means this reference will be - a no-op if it cannot be resolved. - enum: - - Required - - Optional - type: string - resolve: - description: |- - Resolve specifies when this reference should be resolved. The default - is 'IfNotPresent', which will attempt to resolve the reference only when - the corresponding field is not present. Use 'Always' to resolve the - reference on every reconcile. - enum: - - Always - - IfNotPresent - type: string - type: object - required: - - name - type: object - metadata: - description: Metadata is the metadata for connection secret. - properties: - annotations: - additionalProperties: - type: string - description: |- - Annotations are the annotations to be added to connection secret. - - For Kubernetes secrets, this will be used as "metadata.annotations". - - It is up to Secret Store implementation for others store types. - type: object - labels: - additionalProperties: - type: string - description: |- - Labels are the labels/tags to be added to connection secret. - - For Kubernetes secrets, this will be used as "metadata.labels". - - It is up to Secret Store implementation for others store types. - type: object - type: - description: |- - Type is the SecretType for the connection secret. - - Only valid for Kubernetes Secret Stores. - type: string - type: object - name: - description: Name is the name of the connection secret. - type: string - required: - - name - type: object writeConnectionSecretToRef: description: |- WriteConnectionSecretToReference specifies the namespace and name of a Secret to which any connection details for this managed resource should be written. Connection details frequently include the endpoint, username, and password required to connect to the managed resource. - This field is planned to be replaced in a future release in favor of - PublishConnectionDetailsTo. Currently, both could be set independently - and connection details would be published to both without affecting - each other. properties: name: description: Name of the secret. diff --git a/package/crossplane.yaml b/package/crossplane.yaml index dfc098d..808db5e 100644 --- a/package/crossplane.yaml +++ b/package/crossplane.yaml @@ -13,3 +13,6 @@ metadata: Upbound. Available resources and their fields can be found in the [Upbound Marketplace](https://marketplace.upbound.io/providers/upbound/provider-terraform). friendly-name.meta.crossplane.io: Provider Terraform +spec: + capabilities: + - SafeStart diff --git a/scripts/check-examples.py b/scripts/check-examples.py index 8181e71..0579a4d 100755 --- a/scripts/check-examples.py +++ b/scripts/check-examples.py @@ -26,7 +26,8 @@ def load_crd_type(t): exception_set = { 'ProviderConfigUsage.tf.upbound.io/v1beta1', - 'StoreConfig.tf.upbound.io/v1beta1' + 'StoreConfig.tf.upbound.io/v1beta1', + 'ProviderConfigUsage.tf.m.upbound.io/v1beta1' }