diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 67f7cb52..ef65725f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ on: env: # Common versions - GO_VERSION: '1.23' + GO_VERSION: '1.24' GOLANGCI_VERSION: 'v2.1.2' DOCKER_BUILDX_VERSION: 'v0.23.0' diff --git a/.github/workflows/publish-provider-package.yml b/.github/workflows/publish-provider-package.yml index 53449d76..8f07ca30 100644 --- a/.github/workflows/publish-provider-package.yml +++ b/.github/workflows/publish-provider-package.yml @@ -9,7 +9,7 @@ on: required: false go-version: description: 'Go version to use if building needs to be done' - default: '1.23' + default: '1.24' required: false jobs: diff --git a/.golangci.yml b/.golangci.yml index 5b6c12b7..4cadf936 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,6 +1,6 @@ version: "2" run: - timeout: 2m + timeout: 10m formatters: # Enable specific formatter. diff --git a/Makefile b/Makefile index 715600c7..07c3981e 100644 --- a/Makefile +++ b/Makefile @@ -26,16 +26,16 @@ GO_TEST_PARALLEL := $(shell echo $$(( $(NPROCS) / 2 ))) GOLANGCILINT_VERSION ?= 2.1.2 GO_STATIC_PACKAGES = $(GO_PROJECT)/cmd/provider GO_LDFLAGS += -X $(GO_PROJECT)/pkg/version.Version=$(VERSION) -GO_SUBDIRS += cmd pkg apis +GO_SUBDIRS += generate cmd pkg apis GO111MODULE = on -include build/makelib/golang.mk # ==================================================================================== # Setup Kubernetes tools -KIND_NODE_IMAGE_TAG ?= v1.30.13 -KIND_VERSION ?= v0.29.0 -KUBECTL_VERSION ?= v1.30.13 -CROSSPLANE_CLI_VERSION ?= v1.20.0 +KIND_NODE_IMAGE_TAG ?= v1.32.8 +KIND_VERSION ?= v0.30.0 +KUBECTL_VERSION ?= v1.32.8 +CROSSPLANE_CLI_VERSION ?= v2.0.2 -include build/makelib/k8s_tools.mk # ==================================================================================== diff --git a/README.md b/README.md index 1c67f598..073c043d 100644 --- a/README.md +++ b/README.md @@ -75,3 +75,37 @@ Check the example: 4. Run `make` to initialize the "build". Make submodules used for CI/CD. 5. Run `make reviewable` to run code generation, linters, and tests. 6. Commit, push, and PR. + +## Developing locally + +**Pre-requisite:** A Kubernetes cluster with Crossplane installed + +To run the `provider-helm` controller against your existing local cluster, +simply run: + +```console +make run +``` + +Since the controller is running outside of the local cluster, you need to make +the API server accessible (on a separate terminal): + +```console +sudo kubectl proxy --port=8081 +``` + +Then we must prepare a `ProviderConfig` for the local cluster (assuming you are +using `kind` for local development): + +```console +KUBECONFIG=$(kind get kubeconfig | sed -e 's|server:\s*.*$|server: http://localhost:8081|g') +kubectl -n crossplane-system create secret generic cluster-config --from-literal=kubeconfig="${KUBECONFIG}" +kubectl apply -f examples/provider-config/provider-config-with-secret.yaml +``` + +Now you can create `Release` resources with this `ProviderConfig`, for example +[sample release.yaml](examples/sample/release.yaml). + +```console +kubectl create -f examples/sample/release.yaml +``` diff --git a/apis/mssql/v1alpha1/database_types.go b/apis/cluster/mssql/v1alpha1/database_types.go similarity index 96% rename from apis/mssql/v1alpha1/database_types.go rename to apis/cluster/mssql/v1alpha1/database_types.go index 7d5ae314..0186319e 100644 --- a/apis/mssql/v1alpha1/database_types.go +++ b/apis/cluster/mssql/v1alpha1/database_types.go @@ -19,7 +19,7 @@ package v1alpha1 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 DatabaseSpec defines the desired state of a Database. diff --git a/apis/mssql/v1alpha1/doc.go b/apis/cluster/mssql/v1alpha1/doc.go similarity index 100% rename from apis/mssql/v1alpha1/doc.go rename to apis/cluster/mssql/v1alpha1/doc.go diff --git a/apis/mssql/v1alpha1/grant_types.go b/apis/cluster/mssql/v1alpha1/grant_types.go similarity index 98% rename from apis/mssql/v1alpha1/grant_types.go rename to apis/cluster/mssql/v1alpha1/grant_types.go index d717c2ae..967d896f 100644 --- a/apis/mssql/v1alpha1/grant_types.go +++ b/apis/cluster/mssql/v1alpha1/grant_types.go @@ -17,7 +17,7 @@ limitations under the License. package v1alpha1 import ( - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) diff --git a/apis/mssql/v1alpha1/provider_types.go b/apis/cluster/mssql/v1alpha1/provider_types.go similarity index 98% rename from apis/mssql/v1alpha1/provider_types.go rename to apis/cluster/mssql/v1alpha1/provider_types.go index 38eb20c3..3727e09c 100644 --- a/apis/mssql/v1alpha1/provider_types.go +++ b/apis/cluster/mssql/v1alpha1/provider_types.go @@ -19,7 +19,7 @@ package v1alpha1 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/mssql/v1alpha1/register.go b/apis/cluster/mssql/v1alpha1/register.go similarity index 100% rename from apis/mssql/v1alpha1/register.go rename to apis/cluster/mssql/v1alpha1/register.go diff --git a/apis/mssql/v1alpha1/user_types.go b/apis/cluster/mssql/v1alpha1/user_types.go similarity index 98% rename from apis/mssql/v1alpha1/user_types.go rename to apis/cluster/mssql/v1alpha1/user_types.go index eb74775e..3b41bf01 100644 --- a/apis/mssql/v1alpha1/user_types.go +++ b/apis/cluster/mssql/v1alpha1/user_types.go @@ -19,7 +19,7 @@ package v1alpha1 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 UserSpec defines the desired state of a Database. diff --git a/apis/mssql/v1alpha1/zz_generated.deepcopy.go b/apis/cluster/mssql/v1alpha1/zz_generated.deepcopy.go similarity index 96% rename from apis/mssql/v1alpha1/zz_generated.deepcopy.go rename to apis/cluster/mssql/v1alpha1/zz_generated.deepcopy.go index bcaee39a..ec8dd3c1 100644 --- a/apis/mssql/v1alpha1/zz_generated.deepcopy.go +++ b/apis/cluster/mssql/v1alpha1/zz_generated.deepcopy.go @@ -1,27 +1,15 @@ //go:build !ignore_autogenerated -/* -Copyright 2021 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. -*/ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 // Code generated by controller-gen. DO NOT EDIT. package v1alpha1 import ( - "github.com/crossplane/crossplane-runtime/apis/common/v1" + "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) diff --git a/apis/mssql/v1alpha1/zz_generated.managed.go b/apis/cluster/mssql/v1alpha1/zz_generated.managed.go similarity index 72% rename from apis/mssql/v1alpha1/zz_generated.managed.go rename to apis/cluster/mssql/v1alpha1/zz_generated.managed.go index d169dcb2..007d37d6 100644 --- a/apis/mssql/v1alpha1/zz_generated.managed.go +++ b/apis/cluster/mssql/v1alpha1/zz_generated.managed.go @@ -1,23 +1,12 @@ -/* -Copyright 2021 The Crossplane Authors. +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 -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 v1alpha1 -import xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" +import xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" // GetCondition of this Database. func (mg *Database) GetCondition(ct xpv1.ConditionType) xpv1.Condition { @@ -39,11 +28,6 @@ func (mg *Database) GetProviderConfigReference() *xpv1.Reference { return mg.Spec.ProviderConfigReference } -// GetPublishConnectionDetailsTo of this Database. -func (mg *Database) GetPublishConnectionDetailsTo() *xpv1.PublishConnectionDetailsTo { - return mg.Spec.PublishConnectionDetailsTo -} - // GetWriteConnectionSecretToReference of this Database. func (mg *Database) GetWriteConnectionSecretToReference() *xpv1.SecretReference { return mg.Spec.WriteConnectionSecretToReference @@ -69,11 +53,6 @@ func (mg *Database) SetProviderConfigReference(r *xpv1.Reference) { mg.Spec.ProviderConfigReference = r } -// SetPublishConnectionDetailsTo of this Database. -func (mg *Database) SetPublishConnectionDetailsTo(r *xpv1.PublishConnectionDetailsTo) { - mg.Spec.PublishConnectionDetailsTo = r -} - // SetWriteConnectionSecretToReference of this Database. func (mg *Database) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) { mg.Spec.WriteConnectionSecretToReference = r @@ -99,11 +78,6 @@ func (mg *Grant) GetProviderConfigReference() *xpv1.Reference { return mg.Spec.ProviderConfigReference } -// GetPublishConnectionDetailsTo of this Grant. -func (mg *Grant) GetPublishConnectionDetailsTo() *xpv1.PublishConnectionDetailsTo { - return mg.Spec.PublishConnectionDetailsTo -} - // GetWriteConnectionSecretToReference of this Grant. func (mg *Grant) GetWriteConnectionSecretToReference() *xpv1.SecretReference { return mg.Spec.WriteConnectionSecretToReference @@ -129,11 +103,6 @@ func (mg *Grant) SetProviderConfigReference(r *xpv1.Reference) { mg.Spec.ProviderConfigReference = r } -// SetPublishConnectionDetailsTo of this Grant. -func (mg *Grant) SetPublishConnectionDetailsTo(r *xpv1.PublishConnectionDetailsTo) { - mg.Spec.PublishConnectionDetailsTo = r -} - // SetWriteConnectionSecretToReference of this Grant. func (mg *Grant) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) { mg.Spec.WriteConnectionSecretToReference = r @@ -159,11 +128,6 @@ func (mg *User) GetProviderConfigReference() *xpv1.Reference { return mg.Spec.ProviderConfigReference } -// GetPublishConnectionDetailsTo of this User. -func (mg *User) GetPublishConnectionDetailsTo() *xpv1.PublishConnectionDetailsTo { - return mg.Spec.PublishConnectionDetailsTo -} - // GetWriteConnectionSecretToReference of this User. func (mg *User) GetWriteConnectionSecretToReference() *xpv1.SecretReference { return mg.Spec.WriteConnectionSecretToReference @@ -189,11 +153,6 @@ func (mg *User) SetProviderConfigReference(r *xpv1.Reference) { mg.Spec.ProviderConfigReference = r } -// SetPublishConnectionDetailsTo of this User. -func (mg *User) SetPublishConnectionDetailsTo(r *xpv1.PublishConnectionDetailsTo) { - mg.Spec.PublishConnectionDetailsTo = r -} - // SetWriteConnectionSecretToReference of this User. func (mg *User) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) { mg.Spec.WriteConnectionSecretToReference = r diff --git a/apis/mssql/v1alpha1/zz_generated.managedlist.go b/apis/cluster/mssql/v1alpha1/zz_generated.managedlist.go similarity index 51% rename from apis/mssql/v1alpha1/zz_generated.managedlist.go rename to apis/cluster/mssql/v1alpha1/zz_generated.managedlist.go index 4da04725..258aaf8b 100644 --- a/apis/mssql/v1alpha1/zz_generated.managedlist.go +++ b/apis/cluster/mssql/v1alpha1/zz_generated.managedlist.go @@ -1,23 +1,12 @@ -/* -Copyright 2021 The Crossplane Authors. +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 -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 v1alpha1 -import resource "github.com/crossplane/crossplane-runtime/pkg/resource" +import resource "github.com/crossplane/crossplane-runtime/v2/pkg/resource" // GetItems of this DatabaseList. func (l *DatabaseList) GetItems() []resource.Managed { diff --git a/apis/cluster/mssql/v1alpha1/zz_generated.pc.go b/apis/cluster/mssql/v1alpha1/zz_generated.pc.go new file mode 100644 index 00000000..130321bf --- /dev/null +++ b/apis/cluster/mssql/v1alpha1/zz_generated.pc.go @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +import xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + +// 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/postgresql/v1alpha1/zz_generated.pcu.go b/apis/cluster/mssql/v1alpha1/zz_generated.pcu.go similarity index 53% rename from apis/postgresql/v1alpha1/zz_generated.pcu.go rename to apis/cluster/mssql/v1alpha1/zz_generated.pcu.go index 63d19fcf..765ebf2a 100644 --- a/apis/postgresql/v1alpha1/zz_generated.pcu.go +++ b/apis/cluster/mssql/v1alpha1/zz_generated.pcu.go @@ -1,23 +1,12 @@ -/* -Copyright 2021 The Crossplane Authors. +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 -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 v1alpha1 -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/cluster/mssql/v1alpha1/zz_generated.pculist.go b/apis/cluster/mssql/v1alpha1/zz_generated.pculist.go new file mode 100644 index 00000000..13fa5547 --- /dev/null +++ b/apis/cluster/mssql/v1alpha1/zz_generated.pculist.go @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +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/mssql/v1alpha1/zz_generated.resolvers.go b/apis/cluster/mssql/v1alpha1/zz_generated.resolvers.go similarity index 81% rename from apis/mssql/v1alpha1/zz_generated.resolvers.go rename to apis/cluster/mssql/v1alpha1/zz_generated.resolvers.go index 1be14579..dd3f4b24 100644 --- a/apis/mssql/v1alpha1/zz_generated.resolvers.go +++ b/apis/cluster/mssql/v1alpha1/zz_generated.resolvers.go @@ -1,25 +1,14 @@ -/* -Copyright 2021 The Crossplane Authors. +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 -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 v1alpha1 import ( "context" - reference "github.com/crossplane/crossplane-runtime/pkg/reference" + reference "github.com/crossplane/crossplane-runtime/v2/pkg/reference" errors "github.com/pkg/errors" client "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -34,6 +23,7 @@ func (mg *Grant) ResolveReferences(ctx context.Context, c client.Reader) error { rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.User), Extract: reference.ExternalName(), + Namespace: mg.GetNamespace(), Reference: mg.Spec.ForProvider.UserRef, Selector: mg.Spec.ForProvider.UserSelector, To: reference.To{ @@ -50,6 +40,7 @@ func (mg *Grant) ResolveReferences(ctx context.Context, c client.Reader) error { rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.Database), Extract: reference.ExternalName(), + Namespace: mg.GetNamespace(), Reference: mg.Spec.ForProvider.DatabaseRef, Selector: mg.Spec.ForProvider.DatabaseSelector, To: reference.To{ @@ -76,6 +67,7 @@ func (mg *User) ResolveReferences(ctx context.Context, c client.Reader) error { rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.Database), Extract: reference.ExternalName(), + Namespace: mg.GetNamespace(), Reference: mg.Spec.ForProvider.DatabaseRef, Selector: mg.Spec.ForProvider.DatabaseSelector, To: reference.To{ @@ -92,6 +84,7 @@ func (mg *User) ResolveReferences(ctx context.Context, c client.Reader) error { rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.LoginDatabase), Extract: reference.ExternalName(), + Namespace: mg.GetNamespace(), Reference: mg.Spec.ForProvider.LoginDatabaseRef, Selector: mg.Spec.ForProvider.LoginDatabaseSelector, To: reference.To{ diff --git a/apis/mysql/v1alpha1/database_types.go b/apis/cluster/mysql/v1alpha1/database_types.go similarity index 97% rename from apis/mysql/v1alpha1/database_types.go rename to apis/cluster/mysql/v1alpha1/database_types.go index 9ad8235f..1a0862bc 100644 --- a/apis/mysql/v1alpha1/database_types.go +++ b/apis/cluster/mysql/v1alpha1/database_types.go @@ -19,7 +19,7 @@ package v1alpha1 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 DatabaseSpec defines the desired state of a Database. diff --git a/apis/mysql/v1alpha1/doc.go b/apis/cluster/mysql/v1alpha1/doc.go similarity index 100% rename from apis/mysql/v1alpha1/doc.go rename to apis/cluster/mysql/v1alpha1/doc.go diff --git a/apis/mysql/v1alpha1/grant_types.go b/apis/cluster/mysql/v1alpha1/grant_types.go similarity index 74% rename from apis/mysql/v1alpha1/grant_types.go rename to apis/cluster/mysql/v1alpha1/grant_types.go index 4c5dc4c5..01f02691 100644 --- a/apis/mysql/v1alpha1/grant_types.go +++ b/apis/cluster/mysql/v1alpha1/grant_types.go @@ -17,14 +17,9 @@ limitations under the License. package v1alpha1 import ( - "context" - - "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - "github.com/crossplane/crossplane-runtime/pkg/reference" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" ) // A GrantSpec defines the desired state of a Grant. @@ -63,6 +58,7 @@ type GrantParameters struct { // User this grant is for. // +optional + // +crossplane:generate:reference:type=User User *string `json:"user,omitempty"` // UserRef references the user object this grant is for. @@ -81,6 +77,7 @@ type GrantParameters struct { // Database this grant is for, default *. // +optional + // +crossplane:generate:reference:type=Database Database *string `json:"database,omitempty" default:"*"` // DatabaseRef references the database object this grant it for. @@ -137,38 +134,3 @@ type GrantList struct { metav1.ListMeta `json:"metadata,omitempty"` Items []Grant `json:"items"` } - -// ResolveReferences of this Grant -func (mg *Grant) ResolveReferences(ctx context.Context, c client.Reader) error { - r := reference.NewAPIResolver(c, mg) - - // Resolve spec.forProvider.database - rsp, err := r.Resolve(ctx, reference.ResolutionRequest{ - CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.Database), - Reference: mg.Spec.ForProvider.DatabaseRef, - Selector: mg.Spec.ForProvider.DatabaseSelector, - To: reference.To{Managed: &Database{}, List: &DatabaseList{}}, - Extract: reference.ExternalName(), - }) - if err != nil { - return errors.Wrap(err, "spec.forProvider.database") - } - mg.Spec.ForProvider.Database = reference.ToPtrValue(rsp.ResolvedValue) - mg.Spec.ForProvider.DatabaseRef = rsp.ResolvedReference - - // Resolve spec.forProvider.user - rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ - CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.User), - Reference: mg.Spec.ForProvider.UserRef, - Selector: mg.Spec.ForProvider.UserSelector, - To: reference.To{Managed: &User{}, List: &UserList{}}, - Extract: reference.ExternalName(), - }) - if err != nil { - return errors.Wrap(err, "spec.forProvider.user") - } - mg.Spec.ForProvider.User = reference.ToPtrValue(rsp.ResolvedValue) - mg.Spec.ForProvider.UserRef = rsp.ResolvedReference - - return nil -} diff --git a/apis/mysql/v1alpha1/provider_types.go b/apis/cluster/mysql/v1alpha1/provider_types.go similarity index 98% rename from apis/mysql/v1alpha1/provider_types.go rename to apis/cluster/mysql/v1alpha1/provider_types.go index 0b8c892d..0160fdaa 100644 --- a/apis/mysql/v1alpha1/provider_types.go +++ b/apis/cluster/mysql/v1alpha1/provider_types.go @@ -19,7 +19,7 @@ package v1alpha1 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/mysql/v1alpha1/register.go b/apis/cluster/mysql/v1alpha1/register.go similarity index 100% rename from apis/mysql/v1alpha1/register.go rename to apis/cluster/mysql/v1alpha1/register.go diff --git a/apis/mysql/v1alpha1/user_types.go b/apis/cluster/mysql/v1alpha1/user_types.go similarity index 98% rename from apis/mysql/v1alpha1/user_types.go rename to apis/cluster/mysql/v1alpha1/user_types.go index 069d34e9..c59d4bae 100644 --- a/apis/mysql/v1alpha1/user_types.go +++ b/apis/cluster/mysql/v1alpha1/user_types.go @@ -19,7 +19,7 @@ package v1alpha1 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 UserSpec defines the desired state of a Database. diff --git a/apis/mysql/v1alpha1/zz_generated.deepcopy.go b/apis/cluster/mysql/v1alpha1/zz_generated.deepcopy.go similarity index 96% rename from apis/mysql/v1alpha1/zz_generated.deepcopy.go rename to apis/cluster/mysql/v1alpha1/zz_generated.deepcopy.go index 4dfc6379..546572bb 100644 --- a/apis/mysql/v1alpha1/zz_generated.deepcopy.go +++ b/apis/cluster/mysql/v1alpha1/zz_generated.deepcopy.go @@ -1,27 +1,15 @@ //go:build !ignore_autogenerated -/* -Copyright 2021 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. -*/ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 // Code generated by controller-gen. DO NOT EDIT. package v1alpha1 import ( - "github.com/crossplane/crossplane-runtime/apis/common/v1" + "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -400,7 +388,7 @@ func (in *ProviderConfigSpec) DeepCopyInto(out *ProviderConfigSpec) { if in.TLSConfig != nil { in, out := &in.TLSConfig, &out.TLSConfig *out = new(TLSConfig) - **out = **in + (*in).DeepCopyInto(*out) } } @@ -546,9 +534,9 @@ func (in *ResourceOptions) DeepCopy() *ResourceOptions { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TLSConfig) DeepCopyInto(out *TLSConfig) { *out = *in - out.CACert = in.CACert - out.ClientCert = in.ClientCert - out.ClientKey = in.ClientKey + in.CACert.DeepCopyInto(&out.CACert) + in.ClientCert.DeepCopyInto(&out.ClientCert) + in.ClientKey.DeepCopyInto(&out.ClientKey) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSConfig. @@ -564,7 +552,7 @@ func (in *TLSConfig) DeepCopy() *TLSConfig { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TLSSecret) DeepCopyInto(out *TLSSecret) { *out = *in - out.SecretRef = in.SecretRef + in.SecretRef.DeepCopyInto(&out.SecretRef) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSSecret. diff --git a/apis/mysql/v1alpha1/zz_generated.managed.go b/apis/cluster/mysql/v1alpha1/zz_generated.managed.go similarity index 72% rename from apis/mysql/v1alpha1/zz_generated.managed.go rename to apis/cluster/mysql/v1alpha1/zz_generated.managed.go index d169dcb2..007d37d6 100644 --- a/apis/mysql/v1alpha1/zz_generated.managed.go +++ b/apis/cluster/mysql/v1alpha1/zz_generated.managed.go @@ -1,23 +1,12 @@ -/* -Copyright 2021 The Crossplane Authors. +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 -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 v1alpha1 -import xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" +import xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" // GetCondition of this Database. func (mg *Database) GetCondition(ct xpv1.ConditionType) xpv1.Condition { @@ -39,11 +28,6 @@ func (mg *Database) GetProviderConfigReference() *xpv1.Reference { return mg.Spec.ProviderConfigReference } -// GetPublishConnectionDetailsTo of this Database. -func (mg *Database) GetPublishConnectionDetailsTo() *xpv1.PublishConnectionDetailsTo { - return mg.Spec.PublishConnectionDetailsTo -} - // GetWriteConnectionSecretToReference of this Database. func (mg *Database) GetWriteConnectionSecretToReference() *xpv1.SecretReference { return mg.Spec.WriteConnectionSecretToReference @@ -69,11 +53,6 @@ func (mg *Database) SetProviderConfigReference(r *xpv1.Reference) { mg.Spec.ProviderConfigReference = r } -// SetPublishConnectionDetailsTo of this Database. -func (mg *Database) SetPublishConnectionDetailsTo(r *xpv1.PublishConnectionDetailsTo) { - mg.Spec.PublishConnectionDetailsTo = r -} - // SetWriteConnectionSecretToReference of this Database. func (mg *Database) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) { mg.Spec.WriteConnectionSecretToReference = r @@ -99,11 +78,6 @@ func (mg *Grant) GetProviderConfigReference() *xpv1.Reference { return mg.Spec.ProviderConfigReference } -// GetPublishConnectionDetailsTo of this Grant. -func (mg *Grant) GetPublishConnectionDetailsTo() *xpv1.PublishConnectionDetailsTo { - return mg.Spec.PublishConnectionDetailsTo -} - // GetWriteConnectionSecretToReference of this Grant. func (mg *Grant) GetWriteConnectionSecretToReference() *xpv1.SecretReference { return mg.Spec.WriteConnectionSecretToReference @@ -129,11 +103,6 @@ func (mg *Grant) SetProviderConfigReference(r *xpv1.Reference) { mg.Spec.ProviderConfigReference = r } -// SetPublishConnectionDetailsTo of this Grant. -func (mg *Grant) SetPublishConnectionDetailsTo(r *xpv1.PublishConnectionDetailsTo) { - mg.Spec.PublishConnectionDetailsTo = r -} - // SetWriteConnectionSecretToReference of this Grant. func (mg *Grant) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) { mg.Spec.WriteConnectionSecretToReference = r @@ -159,11 +128,6 @@ func (mg *User) GetProviderConfigReference() *xpv1.Reference { return mg.Spec.ProviderConfigReference } -// GetPublishConnectionDetailsTo of this User. -func (mg *User) GetPublishConnectionDetailsTo() *xpv1.PublishConnectionDetailsTo { - return mg.Spec.PublishConnectionDetailsTo -} - // GetWriteConnectionSecretToReference of this User. func (mg *User) GetWriteConnectionSecretToReference() *xpv1.SecretReference { return mg.Spec.WriteConnectionSecretToReference @@ -189,11 +153,6 @@ func (mg *User) SetProviderConfigReference(r *xpv1.Reference) { mg.Spec.ProviderConfigReference = r } -// SetPublishConnectionDetailsTo of this User. -func (mg *User) SetPublishConnectionDetailsTo(r *xpv1.PublishConnectionDetailsTo) { - mg.Spec.PublishConnectionDetailsTo = r -} - // SetWriteConnectionSecretToReference of this User. func (mg *User) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) { mg.Spec.WriteConnectionSecretToReference = r diff --git a/apis/mysql/v1alpha1/zz_generated.managedlist.go b/apis/cluster/mysql/v1alpha1/zz_generated.managedlist.go similarity index 51% rename from apis/mysql/v1alpha1/zz_generated.managedlist.go rename to apis/cluster/mysql/v1alpha1/zz_generated.managedlist.go index 4da04725..258aaf8b 100644 --- a/apis/mysql/v1alpha1/zz_generated.managedlist.go +++ b/apis/cluster/mysql/v1alpha1/zz_generated.managedlist.go @@ -1,23 +1,12 @@ -/* -Copyright 2021 The Crossplane Authors. +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 -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 v1alpha1 -import resource "github.com/crossplane/crossplane-runtime/pkg/resource" +import resource "github.com/crossplane/crossplane-runtime/v2/pkg/resource" // GetItems of this DatabaseList. func (l *DatabaseList) GetItems() []resource.Managed { diff --git a/apis/cluster/mysql/v1alpha1/zz_generated.pc.go b/apis/cluster/mysql/v1alpha1/zz_generated.pc.go new file mode 100644 index 00000000..130321bf --- /dev/null +++ b/apis/cluster/mysql/v1alpha1/zz_generated.pc.go @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +import xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + +// 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/mysql/v1alpha1/zz_generated.pcu.go b/apis/cluster/mysql/v1alpha1/zz_generated.pcu.go similarity index 53% rename from apis/mysql/v1alpha1/zz_generated.pcu.go rename to apis/cluster/mysql/v1alpha1/zz_generated.pcu.go index 63d19fcf..765ebf2a 100644 --- a/apis/mysql/v1alpha1/zz_generated.pcu.go +++ b/apis/cluster/mysql/v1alpha1/zz_generated.pcu.go @@ -1,23 +1,12 @@ -/* -Copyright 2021 The Crossplane Authors. +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 -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 v1alpha1 -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/cluster/mysql/v1alpha1/zz_generated.pculist.go b/apis/cluster/mysql/v1alpha1/zz_generated.pculist.go new file mode 100644 index 00000000..13fa5547 --- /dev/null +++ b/apis/cluster/mysql/v1alpha1/zz_generated.pculist.go @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +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/cluster/mysql/v1alpha1/zz_generated.resolvers.go b/apis/cluster/mysql/v1alpha1/zz_generated.resolvers.go new file mode 100644 index 00000000..09890290 --- /dev/null +++ b/apis/cluster/mysql/v1alpha1/zz_generated.resolvers.go @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + reference "github.com/crossplane/crossplane-runtime/v2/pkg/reference" + errors "github.com/pkg/errors" + client "sigs.k8s.io/controller-runtime/pkg/client" +) + +// ResolveReferences of this Grant. +func (mg *Grant) ResolveReferences(ctx context.Context, c client.Reader) error { + r := reference.NewAPIResolver(c, mg) + + var rsp reference.ResolutionResponse + var err error + + rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ + CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.User), + Extract: reference.ExternalName(), + Namespace: mg.GetNamespace(), + Reference: mg.Spec.ForProvider.UserRef, + Selector: mg.Spec.ForProvider.UserSelector, + To: reference.To{ + List: &UserList{}, + Managed: &User{}, + }, + }) + if err != nil { + return errors.Wrap(err, "mg.Spec.ForProvider.User") + } + mg.Spec.ForProvider.User = reference.ToPtrValue(rsp.ResolvedValue) + mg.Spec.ForProvider.UserRef = rsp.ResolvedReference + + rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ + CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.Database), + Extract: reference.ExternalName(), + Namespace: mg.GetNamespace(), + Reference: mg.Spec.ForProvider.DatabaseRef, + Selector: mg.Spec.ForProvider.DatabaseSelector, + To: reference.To{ + List: &DatabaseList{}, + Managed: &Database{}, + }, + }) + if err != nil { + return errors.Wrap(err, "mg.Spec.ForProvider.Database") + } + mg.Spec.ForProvider.Database = reference.ToPtrValue(rsp.ResolvedValue) + mg.Spec.ForProvider.DatabaseRef = rsp.ResolvedReference + + return nil +} diff --git a/apis/postgresql/v1alpha1/database_types.go b/apis/cluster/postgresql/v1alpha1/database_types.go similarity index 98% rename from apis/postgresql/v1alpha1/database_types.go rename to apis/cluster/postgresql/v1alpha1/database_types.go index 7e176f90..ec2fb8b0 100644 --- a/apis/postgresql/v1alpha1/database_types.go +++ b/apis/cluster/postgresql/v1alpha1/database_types.go @@ -19,7 +19,7 @@ package v1alpha1 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" ) // DatabaseParameters are the configurable fields of a Database. diff --git a/apis/postgresql/v1alpha1/doc.go b/apis/cluster/postgresql/v1alpha1/doc.go similarity index 100% rename from apis/postgresql/v1alpha1/doc.go rename to apis/cluster/postgresql/v1alpha1/doc.go diff --git a/apis/cluster/postgresql/v1alpha1/extension_types.go b/apis/cluster/postgresql/v1alpha1/extension_types.go new file mode 100644 index 00000000..b9c68d98 --- /dev/null +++ b/apis/cluster/postgresql/v1alpha1/extension_types.go @@ -0,0 +1,92 @@ +/* +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 v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" +) + +// ExtensionParameters are the configurable fields of a Extension. +type ExtensionParameters struct { + // Extension name to be installed. + Extension string `json:"extension"` + + // Version of the extension to be installed. + // +immutable + // +optional + Version *string `json:"version,omitempty"` + + // Schema for extension install. + // +optional + Schema *string `json:"schema,omitempty"` + + // Database for extension install. + // +optional + // +crossplane:generate:reference:type=Database + Database *string `json:"database,omitempty"` + + // DatabaseRef references the database object this extension is for. + // +immutable + // +optional + DatabaseRef *xpv1.Reference `json:"databaseRef,omitempty"` + + // DatabaseSelector selects a reference to a Database this extension is for. + // +immutable + // +optional + DatabaseSelector *xpv1.Selector `json:"databaseSelector,omitempty"` +} + +// ExtensionSpec defines the desired state of an Extension. +type ExtensionSpec struct { + xpv1.ResourceSpec `json:",inline"` + ForProvider ExtensionParameters `json:"forProvider"` +} + +// A ExtensionStatus represents the observed state of a Extension. +type ExtensionStatus struct { + xpv1.ResourceStatus `json:",inline"` +} + +// +kubebuilder:object:root=true + +// An Extension represents the declarative state of a PostgreSQL Extension. +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +// +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status" +// +kubebuilder:printcolumn:name="DATABASE",type="string",JSONPath=".spec.forProvider.database" +// +kubebuilder:printcolumn:name="EXTENSION",type="string",JSONPath=".spec.forProvider.extension" +// +kubebuilder:printcolumn:name="VERSION",type="string",JSONPath=".spec.forProvider.version" +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:resource:scope=Cluster,categories={crossplane,managed,sql} +type Extension struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ExtensionSpec `json:"spec"` + Status ExtensionStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// ExtensionList contains a list of Extension +type ExtensionList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Extension `json:"items"` +} diff --git a/apis/postgresql/v1alpha1/grant_types.go b/apis/cluster/postgresql/v1alpha1/grant_types.go similarity index 75% rename from apis/postgresql/v1alpha1/grant_types.go rename to apis/cluster/postgresql/v1alpha1/grant_types.go index 1bf6fc7c..e3cfa6f3 100644 --- a/apis/postgresql/v1alpha1/grant_types.go +++ b/apis/cluster/postgresql/v1alpha1/grant_types.go @@ -17,14 +17,9 @@ limitations under the License. package v1alpha1 import ( - "context" - - "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - "github.com/crossplane/crossplane-runtime/pkg/reference" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" ) // A GrantSpec defines the desired state of a Grant. @@ -115,6 +110,7 @@ type GrantParameters struct { // Role this grant is for. // +optional + // +crossplane:generate:reference:type=Role Role *string `json:"role,omitempty"` // RoleRef references the role object this grant is for. @@ -129,6 +125,7 @@ type GrantParameters struct { // Database this grant is for. // +optional + // +crossplane:generate:reference:type=Database Database *string `json:"database,omitempty"` // DatabaseRef references the database object this grant it for. @@ -143,6 +140,7 @@ type GrantParameters struct { // MemberOf is the Role that this grant makes Role a member of. // +optional + // +crossplane:generate:reference:type=Role MemberOf *string `json:"memberOf,omitempty"` // MemberOfRef references the Role that this grant makes Role a member of. @@ -193,51 +191,3 @@ type GrantList struct { metav1.ListMeta `json:"metadata,omitempty"` Items []Grant `json:"items"` } - -// ResolveReferences of this Grant -func (mg *Grant) ResolveReferences(ctx context.Context, c client.Reader) error { - r := reference.NewAPIResolver(c, mg) - - // Resolve spec.forProvider.database - rsp, err := r.Resolve(ctx, reference.ResolutionRequest{ - CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.Database), - Reference: mg.Spec.ForProvider.DatabaseRef, - Selector: mg.Spec.ForProvider.DatabaseSelector, - To: reference.To{Managed: &Database{}, List: &DatabaseList{}}, - Extract: reference.ExternalName(), - }) - if err != nil { - return errors.Wrap(err, "spec.forProvider.database") - } - mg.Spec.ForProvider.Database = reference.ToPtrValue(rsp.ResolvedValue) - mg.Spec.ForProvider.DatabaseRef = rsp.ResolvedReference - - // Resolve spec.forProvider.role - rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ - CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.Role), - Reference: mg.Spec.ForProvider.RoleRef, - Selector: mg.Spec.ForProvider.RoleSelector, - To: reference.To{Managed: &Role{}, List: &RoleList{}}, - Extract: reference.ExternalName(), - }) - if err != nil { - return errors.Wrap(err, "spec.forProvider.role") - } - mg.Spec.ForProvider.Role = reference.ToPtrValue(rsp.ResolvedValue) - mg.Spec.ForProvider.RoleRef = rsp.ResolvedReference - - // Resolve spec.forProvider.memberOf - rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ - CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.MemberOf), - Reference: mg.Spec.ForProvider.MemberOfRef, - Selector: mg.Spec.ForProvider.MemberOfSelector, - To: reference.To{Managed: &Role{}, List: &RoleList{}}, - Extract: reference.ExternalName(), - }) - if err != nil { - return errors.Wrap(err, "spec.forProvider.memberOf") - } - mg.Spec.ForProvider.MemberOf = reference.ToPtrValue(rsp.ResolvedValue) - mg.Spec.ForProvider.MemberOfRef = rsp.ResolvedReference - return nil -} diff --git a/apis/postgresql/v1alpha1/provider_types.go b/apis/cluster/postgresql/v1alpha1/provider_types.go similarity index 98% rename from apis/postgresql/v1alpha1/provider_types.go rename to apis/cluster/postgresql/v1alpha1/provider_types.go index 1a0b3eba..276f3306 100644 --- a/apis/postgresql/v1alpha1/provider_types.go +++ b/apis/cluster/postgresql/v1alpha1/provider_types.go @@ -19,7 +19,7 @@ package v1alpha1 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/postgresql/v1alpha1/register.go b/apis/cluster/postgresql/v1alpha1/register.go similarity index 100% rename from apis/postgresql/v1alpha1/register.go rename to apis/cluster/postgresql/v1alpha1/register.go diff --git a/apis/postgresql/v1alpha1/role_types.go b/apis/cluster/postgresql/v1alpha1/role_types.go similarity index 98% rename from apis/postgresql/v1alpha1/role_types.go rename to apis/cluster/postgresql/v1alpha1/role_types.go index 749ec159..f00803dd 100644 --- a/apis/postgresql/v1alpha1/role_types.go +++ b/apis/cluster/postgresql/v1alpha1/role_types.go @@ -19,7 +19,7 @@ package v1alpha1 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 RoleSpec defines the desired state of a Role. diff --git a/apis/postgresql/v1alpha1/schema_types.go b/apis/cluster/postgresql/v1alpha1/schema_types.go similarity index 97% rename from apis/postgresql/v1alpha1/schema_types.go rename to apis/cluster/postgresql/v1alpha1/schema_types.go index 8f9a9d7f..bcde3965 100644 --- a/apis/postgresql/v1alpha1/schema_types.go +++ b/apis/cluster/postgresql/v1alpha1/schema_types.go @@ -19,7 +19,7 @@ package v1alpha1 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 SchemaSpec defines the desired state of a Schema. diff --git a/apis/postgresql/v1alpha1/zz_generated.deepcopy.go b/apis/cluster/postgresql/v1alpha1/zz_generated.deepcopy.go similarity index 97% rename from apis/postgresql/v1alpha1/zz_generated.deepcopy.go rename to apis/cluster/postgresql/v1alpha1/zz_generated.deepcopy.go index 70d15187..b483b745 100644 --- a/apis/postgresql/v1alpha1/zz_generated.deepcopy.go +++ b/apis/cluster/postgresql/v1alpha1/zz_generated.deepcopy.go @@ -1,27 +1,15 @@ //go:build !ignore_autogenerated -/* -Copyright 2021 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. -*/ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 // Code generated by controller-gen. DO NOT EDIT. package v1alpha1 import ( - "github.com/crossplane/crossplane-runtime/apis/common/v1" + "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) diff --git a/apis/postgresql/v1alpha1/zz_generated.managed.go b/apis/cluster/postgresql/v1alpha1/zz_generated.managed.go similarity index 75% rename from apis/postgresql/v1alpha1/zz_generated.managed.go rename to apis/cluster/postgresql/v1alpha1/zz_generated.managed.go index db72a823..6d5f5f99 100644 --- a/apis/postgresql/v1alpha1/zz_generated.managed.go +++ b/apis/cluster/postgresql/v1alpha1/zz_generated.managed.go @@ -1,23 +1,12 @@ -/* -Copyright 2021 The Crossplane Authors. +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 -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 v1alpha1 -import xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" +import xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" // GetCondition of this Database. func (mg *Database) GetCondition(ct xpv1.ConditionType) xpv1.Condition { @@ -39,11 +28,6 @@ func (mg *Database) GetProviderConfigReference() *xpv1.Reference { return mg.Spec.ProviderConfigReference } -// GetPublishConnectionDetailsTo of this Database. -func (mg *Database) GetPublishConnectionDetailsTo() *xpv1.PublishConnectionDetailsTo { - return mg.Spec.PublishConnectionDetailsTo -} - // GetWriteConnectionSecretToReference of this Database. func (mg *Database) GetWriteConnectionSecretToReference() *xpv1.SecretReference { return mg.Spec.WriteConnectionSecretToReference @@ -69,11 +53,6 @@ func (mg *Database) SetProviderConfigReference(r *xpv1.Reference) { mg.Spec.ProviderConfigReference = r } -// SetPublishConnectionDetailsTo of this Database. -func (mg *Database) SetPublishConnectionDetailsTo(r *xpv1.PublishConnectionDetailsTo) { - mg.Spec.PublishConnectionDetailsTo = r -} - // SetWriteConnectionSecretToReference of this Database. func (mg *Database) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) { mg.Spec.WriteConnectionSecretToReference = r @@ -99,11 +78,6 @@ func (mg *Extension) GetProviderConfigReference() *xpv1.Reference { return mg.Spec.ProviderConfigReference } -// GetPublishConnectionDetailsTo of this Extension. -func (mg *Extension) GetPublishConnectionDetailsTo() *xpv1.PublishConnectionDetailsTo { - return mg.Spec.PublishConnectionDetailsTo -} - // GetWriteConnectionSecretToReference of this Extension. func (mg *Extension) GetWriteConnectionSecretToReference() *xpv1.SecretReference { return mg.Spec.WriteConnectionSecretToReference @@ -129,11 +103,6 @@ func (mg *Extension) SetProviderConfigReference(r *xpv1.Reference) { mg.Spec.ProviderConfigReference = r } -// SetPublishConnectionDetailsTo of this Extension. -func (mg *Extension) SetPublishConnectionDetailsTo(r *xpv1.PublishConnectionDetailsTo) { - mg.Spec.PublishConnectionDetailsTo = r -} - // SetWriteConnectionSecretToReference of this Extension. func (mg *Extension) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) { mg.Spec.WriteConnectionSecretToReference = r @@ -159,11 +128,6 @@ func (mg *Grant) GetProviderConfigReference() *xpv1.Reference { return mg.Spec.ProviderConfigReference } -// GetPublishConnectionDetailsTo of this Grant. -func (mg *Grant) GetPublishConnectionDetailsTo() *xpv1.PublishConnectionDetailsTo { - return mg.Spec.PublishConnectionDetailsTo -} - // GetWriteConnectionSecretToReference of this Grant. func (mg *Grant) GetWriteConnectionSecretToReference() *xpv1.SecretReference { return mg.Spec.WriteConnectionSecretToReference @@ -189,11 +153,6 @@ func (mg *Grant) SetProviderConfigReference(r *xpv1.Reference) { mg.Spec.ProviderConfigReference = r } -// SetPublishConnectionDetailsTo of this Grant. -func (mg *Grant) SetPublishConnectionDetailsTo(r *xpv1.PublishConnectionDetailsTo) { - mg.Spec.PublishConnectionDetailsTo = r -} - // SetWriteConnectionSecretToReference of this Grant. func (mg *Grant) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) { mg.Spec.WriteConnectionSecretToReference = r @@ -219,11 +178,6 @@ func (mg *Role) GetProviderConfigReference() *xpv1.Reference { return mg.Spec.ProviderConfigReference } -// GetPublishConnectionDetailsTo of this Role. -func (mg *Role) GetPublishConnectionDetailsTo() *xpv1.PublishConnectionDetailsTo { - return mg.Spec.PublishConnectionDetailsTo -} - // GetWriteConnectionSecretToReference of this Role. func (mg *Role) GetWriteConnectionSecretToReference() *xpv1.SecretReference { return mg.Spec.WriteConnectionSecretToReference @@ -249,11 +203,6 @@ func (mg *Role) SetProviderConfigReference(r *xpv1.Reference) { mg.Spec.ProviderConfigReference = r } -// SetPublishConnectionDetailsTo of this Role. -func (mg *Role) SetPublishConnectionDetailsTo(r *xpv1.PublishConnectionDetailsTo) { - mg.Spec.PublishConnectionDetailsTo = r -} - // SetWriteConnectionSecretToReference of this Role. func (mg *Role) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) { mg.Spec.WriteConnectionSecretToReference = r @@ -279,11 +228,6 @@ func (mg *Schema) GetProviderConfigReference() *xpv1.Reference { return mg.Spec.ProviderConfigReference } -// GetPublishConnectionDetailsTo of this Schema. -func (mg *Schema) GetPublishConnectionDetailsTo() *xpv1.PublishConnectionDetailsTo { - return mg.Spec.PublishConnectionDetailsTo -} - // GetWriteConnectionSecretToReference of this Schema. func (mg *Schema) GetWriteConnectionSecretToReference() *xpv1.SecretReference { return mg.Spec.WriteConnectionSecretToReference @@ -309,11 +253,6 @@ func (mg *Schema) SetProviderConfigReference(r *xpv1.Reference) { mg.Spec.ProviderConfigReference = r } -// SetPublishConnectionDetailsTo of this Schema. -func (mg *Schema) SetPublishConnectionDetailsTo(r *xpv1.PublishConnectionDetailsTo) { - mg.Spec.PublishConnectionDetailsTo = r -} - // SetWriteConnectionSecretToReference of this Schema. func (mg *Schema) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) { mg.Spec.WriteConnectionSecretToReference = r diff --git a/apis/postgresql/v1alpha1/zz_generated.managedlist.go b/apis/cluster/postgresql/v1alpha1/zz_generated.managedlist.go similarity index 63% rename from apis/postgresql/v1alpha1/zz_generated.managedlist.go rename to apis/cluster/postgresql/v1alpha1/zz_generated.managedlist.go index d69f37e8..d6d2f04d 100644 --- a/apis/postgresql/v1alpha1/zz_generated.managedlist.go +++ b/apis/cluster/postgresql/v1alpha1/zz_generated.managedlist.go @@ -1,23 +1,12 @@ -/* -Copyright 2021 The Crossplane Authors. +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 -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 v1alpha1 -import resource "github.com/crossplane/crossplane-runtime/pkg/resource" +import resource "github.com/crossplane/crossplane-runtime/v2/pkg/resource" // GetItems of this DatabaseList. func (l *DatabaseList) GetItems() []resource.Managed { diff --git a/apis/cluster/postgresql/v1alpha1/zz_generated.pc.go b/apis/cluster/postgresql/v1alpha1/zz_generated.pc.go new file mode 100644 index 00000000..130321bf --- /dev/null +++ b/apis/cluster/postgresql/v1alpha1/zz_generated.pc.go @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +import xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + +// 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/mssql/v1alpha1/zz_generated.pcu.go b/apis/cluster/postgresql/v1alpha1/zz_generated.pcu.go similarity index 53% rename from apis/mssql/v1alpha1/zz_generated.pcu.go rename to apis/cluster/postgresql/v1alpha1/zz_generated.pcu.go index 63d19fcf..765ebf2a 100644 --- a/apis/mssql/v1alpha1/zz_generated.pcu.go +++ b/apis/cluster/postgresql/v1alpha1/zz_generated.pcu.go @@ -1,23 +1,12 @@ -/* -Copyright 2021 The Crossplane Authors. +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 -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 v1alpha1 -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/cluster/postgresql/v1alpha1/zz_generated.pculist.go b/apis/cluster/postgresql/v1alpha1/zz_generated.pculist.go new file mode 100644 index 00000000..13fa5547 --- /dev/null +++ b/apis/cluster/postgresql/v1alpha1/zz_generated.pculist.go @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +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/cluster/postgresql/v1alpha1/zz_generated.resolvers.go b/apis/cluster/postgresql/v1alpha1/zz_generated.resolvers.go new file mode 100644 index 00000000..b7fe93db --- /dev/null +++ b/apis/cluster/postgresql/v1alpha1/zz_generated.resolvers.go @@ -0,0 +1,146 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + reference "github.com/crossplane/crossplane-runtime/v2/pkg/reference" + errors "github.com/pkg/errors" + client "sigs.k8s.io/controller-runtime/pkg/client" +) + +// ResolveReferences of this Extension. +func (mg *Extension) ResolveReferences(ctx context.Context, c client.Reader) error { + r := reference.NewAPIResolver(c, mg) + + var rsp reference.ResolutionResponse + var err error + + rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ + CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.Database), + Extract: reference.ExternalName(), + Namespace: mg.GetNamespace(), + Reference: mg.Spec.ForProvider.DatabaseRef, + Selector: mg.Spec.ForProvider.DatabaseSelector, + To: reference.To{ + List: &DatabaseList{}, + Managed: &Database{}, + }, + }) + if err != nil { + return errors.Wrap(err, "mg.Spec.ForProvider.Database") + } + mg.Spec.ForProvider.Database = reference.ToPtrValue(rsp.ResolvedValue) + mg.Spec.ForProvider.DatabaseRef = rsp.ResolvedReference + + return nil +} + +// ResolveReferences of this Grant. +func (mg *Grant) ResolveReferences(ctx context.Context, c client.Reader) error { + r := reference.NewAPIResolver(c, mg) + + var rsp reference.ResolutionResponse + var err error + + rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ + CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.Role), + Extract: reference.ExternalName(), + Namespace: mg.GetNamespace(), + Reference: mg.Spec.ForProvider.RoleRef, + Selector: mg.Spec.ForProvider.RoleSelector, + To: reference.To{ + List: &RoleList{}, + Managed: &Role{}, + }, + }) + if err != nil { + return errors.Wrap(err, "mg.Spec.ForProvider.Role") + } + mg.Spec.ForProvider.Role = reference.ToPtrValue(rsp.ResolvedValue) + mg.Spec.ForProvider.RoleRef = rsp.ResolvedReference + + rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ + CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.Database), + Extract: reference.ExternalName(), + Namespace: mg.GetNamespace(), + Reference: mg.Spec.ForProvider.DatabaseRef, + Selector: mg.Spec.ForProvider.DatabaseSelector, + To: reference.To{ + List: &DatabaseList{}, + Managed: &Database{}, + }, + }) + if err != nil { + return errors.Wrap(err, "mg.Spec.ForProvider.Database") + } + mg.Spec.ForProvider.Database = reference.ToPtrValue(rsp.ResolvedValue) + mg.Spec.ForProvider.DatabaseRef = rsp.ResolvedReference + + rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ + CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.MemberOf), + Extract: reference.ExternalName(), + Namespace: mg.GetNamespace(), + Reference: mg.Spec.ForProvider.MemberOfRef, + Selector: mg.Spec.ForProvider.MemberOfSelector, + To: reference.To{ + List: &RoleList{}, + Managed: &Role{}, + }, + }) + if err != nil { + return errors.Wrap(err, "mg.Spec.ForProvider.MemberOf") + } + mg.Spec.ForProvider.MemberOf = reference.ToPtrValue(rsp.ResolvedValue) + mg.Spec.ForProvider.MemberOfRef = rsp.ResolvedReference + + return nil +} + +// ResolveReferences of this Schema. +func (mg *Schema) ResolveReferences(ctx context.Context, c client.Reader) error { + r := reference.NewAPIResolver(c, mg) + + var rsp reference.ResolutionResponse + var err error + + rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ + CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.Role), + Extract: reference.ExternalName(), + Namespace: mg.GetNamespace(), + Reference: mg.Spec.ForProvider.RoleRef, + Selector: mg.Spec.ForProvider.RoleSelector, + To: reference.To{ + List: &RoleList{}, + Managed: &Role{}, + }, + }) + if err != nil { + return errors.Wrap(err, "mg.Spec.ForProvider.Role") + } + mg.Spec.ForProvider.Role = reference.ToPtrValue(rsp.ResolvedValue) + mg.Spec.ForProvider.RoleRef = rsp.ResolvedReference + + rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ + CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.Database), + Extract: reference.ExternalName(), + Namespace: mg.GetNamespace(), + Reference: mg.Spec.ForProvider.DatabaseRef, + Selector: mg.Spec.ForProvider.DatabaseSelector, + To: reference.To{ + List: &DatabaseList{}, + Managed: &Database{}, + }, + }) + if err != nil { + return errors.Wrap(err, "mg.Spec.ForProvider.Database") + } + mg.Spec.ForProvider.Database = reference.ToPtrValue(rsp.ResolvedValue) + mg.Spec.ForProvider.DatabaseRef = rsp.ResolvedReference + + return nil +} diff --git a/apis/mssql/v1alpha1/zz_generated.pc.go b/apis/mssql/v1alpha1/zz_generated.pc.go deleted file mode 100644 index 4acf0166..00000000 --- a/apis/mssql/v1alpha1/zz_generated.pc.go +++ /dev/null @@ -1,40 +0,0 @@ -/* -Copyright 2021 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 v1alpha1 - -import xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - -// 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/mysql/v1alpha1/zz_generated.pc.go b/apis/mysql/v1alpha1/zz_generated.pc.go deleted file mode 100644 index 4acf0166..00000000 --- a/apis/mysql/v1alpha1/zz_generated.pc.go +++ /dev/null @@ -1,40 +0,0 @@ -/* -Copyright 2021 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 v1alpha1 - -import xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - -// 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/mssql/v1alpha1/cluster_provider_types.go b/apis/namespaced/mssql/v1alpha1/cluster_provider_types.go new file mode 100644 index 00000000..ccdce5eb --- /dev/null +++ b/apis/namespaced/mssql/v1alpha1/cluster_provider_types.go @@ -0,0 +1,71 @@ +/* +Copyright 2021 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 v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" +) + +// A ClusterProviderConfigSpec defines the desired state of a ClusterProviderConfig. +type ClusterProviderConfigSpec struct { + // Credentials required to authenticate to this provider. + Credentials ClusterProviderCredentials `json:"credentials"` +} + +// ClusterProviderCredentials required to authenticate. +type ClusterProviderCredentials struct { + // Source of the provider credentials. + // +kubebuilder:validation:Enum=MSSQLConnectionSecret + Source MSSQLConnectionSource `json:"source"` + + // A CredentialsSecretRef is a reference to a MSSQL connection secret + // that contains the credentials that must be used to connect to the + // provider. + // +optional + ConnectionSecretRef xpv1.SecretReference `json:"connectionSecretRef,omitempty"` +} + +// A ProviderConfigStatus reflects the observed state of a ProviderConfig. +type ClusterProviderConfigStatus struct { + xpv1.ProviderConfigStatus `json:",inline"` +} + +// +kubebuilder:object:root=true + +// A ClusterProviderConfig configures a SQL provider. +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:printcolumn:name="SECRET-NAME",type="string",JSONPath=".spec.credentials.connectionSecretRef.name",priority=1 +// +kubebuilder:resource:scope=Cluster,categories={crossplane,provider,sql} +type ClusterProviderConfig struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ClusterProviderConfigSpec `json:"spec"` + Status ClusterProviderConfigStatus `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/mssql/v1alpha1/database_types.go b/apis/namespaced/mssql/v1alpha1/database_types.go new file mode 100644 index 00000000..42fa18a2 --- /dev/null +++ b/apis/namespaced/mssql/v1alpha1/database_types.go @@ -0,0 +1,59 @@ +/* +Copyright 2021 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 v1alpha1 + +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 DatabaseSpec defines the desired state of a Database. +type DatabaseSpec struct { + xpv2.ManagedResourceSpec `json:",inline"` +} + +// A DatabaseStatus represents the observed state of a Database. +type DatabaseStatus struct { + xpv1.ResourceStatus `json:",inline"` +} + +// +kubebuilder:object:root=true + +// A Database represents the declarative state of a MSSQL database. +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +// +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status" +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:resource:scope=Namespaced,categories={crossplane,managed,sql} +type Database struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec DatabaseSpec `json:"spec"` + Status DatabaseStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// DatabaseList contains a list of Database +type DatabaseList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Database `json:"items"` +} diff --git a/apis/mssql/v1alpha1/zz_generated.pculist.go b/apis/namespaced/mssql/v1alpha1/doc.go similarity index 61% rename from apis/mssql/v1alpha1/zz_generated.pculist.go rename to apis/namespaced/mssql/v1alpha1/doc.go index 5f8fdede..38dc625a 100644 --- a/apis/mssql/v1alpha1/zz_generated.pculist.go +++ b/apis/namespaced/mssql/v1alpha1/doc.go @@ -13,17 +13,9 @@ 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 v1alpha1 contains the core resources of the SQL provider. +// +kubebuilder:object:generate=true +// +groupName=mssql.sql.m.crossplane.io +// +versionName=v1alpha1 package v1alpha1 - -import resource "github.com/crossplane/crossplane-runtime/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/namespaced/mssql/v1alpha1/grant_types.go b/apis/namespaced/mssql/v1alpha1/grant_types.go new file mode 100644 index 00000000..5c066d7b --- /dev/null +++ b/apis/namespaced/mssql/v1alpha1/grant_types.go @@ -0,0 +1,127 @@ +/* +Copyright 2021 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 v1alpha1 + +import ( + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpv2 "github.com/crossplane/crossplane-runtime/v2/apis/common/v2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// A GrantSpec defines the desired state of a Grant. +type GrantSpec struct { + xpv2.ManagedResourceSpec `json:",inline"` + ForProvider GrantParameters `json:"forProvider"` +} + +// GrantPermission represents a permission to be granted +// +kubebuilder:validation:Pattern:=^[A-Z_ ]+$ +type GrantPermission string + +// GrantPermissions is a list of the privileges to be granted +// If Permissions are specified, we should have at least one +// +kubebuilder:validation:MinItems:=1 +type GrantPermissions []GrantPermission + +// ToStringSlice converts the slice of privileges to strings +func (gp *GrantPermissions) ToStringSlice() []string { + if gp == nil { + return []string{} + } + out := make([]string, len(*gp)) + for i, v := range *gp { + out[i] = string(v) + } + return out +} + +// GrantParameters define the desired state of a MSSQL grant instance. +type GrantParameters struct { + // Permissions to be granted. + // See https://docs.microsoft.com/en-us/sql/t-sql/statements/grant-database-permissions-transact-sql?view=sql-server-ver15#remarks + // for available privileges. + Permissions GrantPermissions `json:"permissions"` + + // Schema for the permissions to be granted for. + // +immutable + // +optional + Schema *string `json:"schema,omitempty"` + + // User this grant is for. + // +optional + // +crossplane:generate:reference:type=User + User *string `json:"user,omitempty"` + + // UserRef references the user object this grant is for. + // +immutable + // +optional + UserRef *xpv1.NamespacedReference `json:"userRef,omitempty"` + + // UserSelector selects a reference to a User this grant is for. + // +immutable + // +optional + UserSelector *xpv1.NamespacedSelector `json:"userSelector,omitempty"` + + // Database this grant is for. + // +optional + // +crossplane:generate:reference:type=Database + Database *string `json:"database,omitempty"` + + // DatabaseRef references the database object this grant it for. + // +immutable + // +optional + DatabaseRef *xpv1.NamespacedReference `json:"databaseRef,omitempty"` + + // DatabaseSelector selects a reference to a Database this grant is for. + // +immutable + // +optional + DatabaseSelector *xpv1.NamespacedSelector `json:"databaseSelector,omitempty"` +} + +// A GrantStatus represents the observed state of a Grant. +type GrantStatus struct { + xpv1.ResourceStatus `json:",inline"` +} + +// +kubebuilder:object:root=true + +// A Grant represents the declarative state of a MSSQL grant. +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +// +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status" +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:printcolumn:name="ROLE",type="string",JSONPath=".spec.forProvider.user" +// +kubebuilder:printcolumn:name="DATABASE",type="string",JSONPath=".spec.forProvider.database" +// +kubebuilder:printcolumn:name="SCHEMA",type="string",JSONPath=".spec.forProvider.schema" +// +kubebuilder:printcolumn:name="PERMISSIONS",type="string",JSONPath=".spec.forProvider.permissions" +// +kubebuilder:resource:scope=Namespaced,categories={crossplane,managed,sql} +type Grant struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec GrantSpec `json:"spec"` + Status GrantStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// GrantList contains a list of Grant +type GrantList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Grant `json:"items"` +} diff --git a/apis/namespaced/mssql/v1alpha1/provider_types.go b/apis/namespaced/mssql/v1alpha1/provider_types.go new file mode 100644 index 00000000..c69709de --- /dev/null +++ b/apis/namespaced/mssql/v1alpha1/provider_types.go @@ -0,0 +1,105 @@ +/* +Copyright 2021 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 v1alpha1 + +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. + Credentials ProviderCredentials `json:"credentials"` +} + +type MSSQLConnectionSource string + +const ( + // CredentialsSourceMSSQLConnectionSecret indicates that a provider + // should acquire credentials from a connection secret written by a managed + // resource that represents a MSSQL server. + CredentialsSourceMSSQLConnectionSecret MSSQLConnectionSource = "MSSQLConnectionSecret" +) + +// ProviderCredentials required to authenticate. +type ProviderCredentials struct { + // Source of the provider credentials. + // +kubebuilder:validation:Enum=MSSQLConnectionSecret + Source MSSQLConnectionSource `json:"source"` + + // A CredentialsSecretRef is a reference to a MSSQL connection secret + // that contains the credentials that must be used to connect to the + // provider. + // +optional + ConnectionSecretRef xpv1.LocalSecretReference `json:"connectionSecretRef,omitempty"` +} + +// A ProviderConfigStatus reflects the observed state of a ProviderConfig. +type ProviderConfigStatus struct { + xpv1.ProviderConfigStatus `json:",inline"` +} + +// +kubebuilder:object:root=true + +// A ProviderConfig configures a SQL provider. +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:printcolumn:name="SECRET-NAME",type="string",JSONPath=".spec.credentialsSecretRef.name",priority=1 +// +kubebuilder:resource:categories={crossplane,provider,sql} +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,sql} +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"` +} diff --git a/apis/namespaced/mssql/v1alpha1/register.go b/apis/namespaced/mssql/v1alpha1/register.go new file mode 100644 index 00000000..8aed1254 --- /dev/null +++ b/apis/namespaced/mssql/v1alpha1/register.go @@ -0,0 +1,100 @@ +/* +Copyright 2021 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 v1alpha1 + +import ( + "reflect" + + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +// Package type metadata. +const ( + Group = "mssql.sql.m.crossplane.io" + Version = "v1alpha1" +) + +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) +) + +// ClusterProviderConfig type metadata. +var ( + ClusterProviderConfigKind = reflect.TypeOf(ClusterProviderConfig{}).Name() + ClusterProviderConfigGroupKind = schema.GroupKind{Group: Group, Kind: ClusterProviderConfigKind}.String() + ClusterProviderConfigKindAPIVersion = ClusterProviderConfigKind + "." + SchemeGroupVersion.String() + ClusterProviderConfigGroupVersionKind = SchemeGroupVersion.WithKind(ClusterProviderConfigKind) +) + +// Database type metadata. +var ( + DatabaseKind = reflect.TypeOf(Database{}).Name() + DatabaseGroupKind = schema.GroupKind{Group: Group, Kind: DatabaseKind}.String() + DatabaseKindAPIVersion = DatabaseKind + "." + SchemeGroupVersion.String() + DatabaseGroupVersionKind = SchemeGroupVersion.WithKind(DatabaseKind) +) + +// User type metadata. +var ( + UserKind = reflect.TypeOf(User{}).Name() + UserGroupKind = schema.GroupKind{Group: Group, Kind: UserKind}.String() + UserKindAPIVersion = UserKind + "." + SchemeGroupVersion.String() + UserGroupVersionKind = SchemeGroupVersion.WithKind(UserKind) +) + +// Grant type metadata. +var ( + GrantKind = reflect.TypeOf(Grant{}).Name() + GrantGroupKind = schema.GroupKind{Group: Group, Kind: GrantKind}.String() + GrantKindAPIVersion = GrantKind + "." + SchemeGroupVersion.String() + GrantGroupVersionKind = SchemeGroupVersion.WithKind(GrantKind) +) + +func init() { + SchemeBuilder.Register(&ClusterProviderConfig{}, &ClusterProviderConfigList{}) + SchemeBuilder.Register(&ProviderConfig{}, &ProviderConfigList{}) + SchemeBuilder.Register(&ProviderConfigUsage{}, &ProviderConfigUsageList{}) + SchemeBuilder.Register(&Database{}, &DatabaseList{}) + SchemeBuilder.Register(&User{}, &UserList{}) + SchemeBuilder.Register(&Grant{}, &GrantList{}) +} diff --git a/apis/namespaced/mssql/v1alpha1/user_types.go b/apis/namespaced/mssql/v1alpha1/user_types.go new file mode 100644 index 00000000..59a1cfb8 --- /dev/null +++ b/apis/namespaced/mssql/v1alpha1/user_types.go @@ -0,0 +1,89 @@ +/* +Copyright 2021 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 v1alpha1 + +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 UserSpec defines the desired state of a Database. +type UserSpec struct { + xpv2.ManagedResourceSpec `json:",inline"` + ForProvider UserParameters `json:"forProvider"` +} + +// A UserStatus represents the observed state of a User. +type UserStatus struct { + xpv1.ResourceStatus `json:",inline"` + AtProvider UserObservation `json:"atProvider,omitempty"` +} + +// UserParameters define the desired state of a MSSQL user instance. +type UserParameters struct { + // Database allows you to specify the name of the Database the USER is created for. + // +crossplane:generate:reference:type=Database + Database *string `json:"database,omitempty"` + // DatabaseRef allows you to specify custom resource name of the Database the USER is created for. + // to fill Database field. + DatabaseRef *xpv1.NamespacedReference `json:"databaseRef,omitempty"` + // DatabaseSelector allows you to use selector constraints to select a Database the USER is created for. + DatabaseSelector *xpv1.NamespacedSelector `json:"databaseSelector,omitempty"` + // PasswordSecretRef references the secret that contains the password used + // for this user. If no reference is given, a password will be auto-generated. + // +optional + PasswordSecretRef *xpv1.LocalSecretKeySelector `json:"passwordSecretRef,omitempty"` + // LoginDatabase allows you to specify the name of the Database to be used to create the user LOGIN in (normally master). + // +crossplane:generate:reference:type=Database + LoginDatabase *string `json:"loginDatabase,omitempty"` + // DatabaseRef allows you to specify custom resource name of the Database to be used to create the user LOGIN in (normally master). + // to fill Database field. + LoginDatabaseRef *xpv1.NamespacedReference `json:"loginDatabaseRef,omitempty"` + // DatabaseSelector allows you to use selector constraints to select a Database to be used to create the user LOGIN in (normally master). + LoginDatabaseSelector *xpv1.NamespacedSelector `json:"loginDatabaseSelector,omitempty"` +} + +// A UserObservation represents the observed state of a MSSQL user. +type UserObservation struct { +} + +// +kubebuilder:object:root=true + +// A User represents the declarative state of a MSSQL user. +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +// +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status" +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:resource,categories={crossplane,managed,sql} +type User struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec UserSpec `json:"spec"` + Status UserStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// UserList contains a list of User +type UserList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []User `json:"items"` +} diff --git a/apis/namespaced/mssql/v1alpha1/zz_generated.deepcopy.go b/apis/namespaced/mssql/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000..85d06f85 --- /dev/null +++ b/apis/namespaced/mssql/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,701 @@ +//go:build !ignore_autogenerated + +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + runtime "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 *ClusterProviderConfigSpec) DeepCopyInto(out *ClusterProviderConfigSpec) { + *out = *in + in.Credentials.DeepCopyInto(&out.Credentials) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterProviderConfigSpec. +func (in *ClusterProviderConfigSpec) DeepCopy() *ClusterProviderConfigSpec { + if in == nil { + return nil + } + out := new(ClusterProviderConfigSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterProviderConfigStatus) DeepCopyInto(out *ClusterProviderConfigStatus) { + *out = *in + in.ProviderConfigStatus.DeepCopyInto(&out.ProviderConfigStatus) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterProviderConfigStatus. +func (in *ClusterProviderConfigStatus) DeepCopy() *ClusterProviderConfigStatus { + if in == nil { + return nil + } + out := new(ClusterProviderConfigStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterProviderCredentials) DeepCopyInto(out *ClusterProviderCredentials) { + *out = *in + in.ConnectionSecretRef.DeepCopyInto(&out.ConnectionSecretRef) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterProviderCredentials. +func (in *ClusterProviderCredentials) DeepCopy() *ClusterProviderCredentials { + if in == nil { + return nil + } + out := new(ClusterProviderCredentials) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Database) DeepCopyInto(out *Database) { + *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 Database. +func (in *Database) DeepCopy() *Database { + if in == nil { + return nil + } + out := new(Database) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Database) 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 *DatabaseList) DeepCopyInto(out *DatabaseList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Database, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseList. +func (in *DatabaseList) DeepCopy() *DatabaseList { + if in == nil { + return nil + } + out := new(DatabaseList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DatabaseList) 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 *DatabaseSpec) DeepCopyInto(out *DatabaseSpec) { + *out = *in + in.ManagedResourceSpec.DeepCopyInto(&out.ManagedResourceSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseSpec. +func (in *DatabaseSpec) DeepCopy() *DatabaseSpec { + if in == nil { + return nil + } + out := new(DatabaseSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DatabaseStatus) DeepCopyInto(out *DatabaseStatus) { + *out = *in + in.ResourceStatus.DeepCopyInto(&out.ResourceStatus) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseStatus. +func (in *DatabaseStatus) DeepCopy() *DatabaseStatus { + if in == nil { + return nil + } + out := new(DatabaseStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Grant) DeepCopyInto(out *Grant) { + *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 Grant. +func (in *Grant) DeepCopy() *Grant { + if in == nil { + return nil + } + out := new(Grant) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Grant) 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 *GrantList) DeepCopyInto(out *GrantList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Grant, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrantList. +func (in *GrantList) DeepCopy() *GrantList { + if in == nil { + return nil + } + out := new(GrantList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GrantList) 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 *GrantParameters) DeepCopyInto(out *GrantParameters) { + *out = *in + if in.Permissions != nil { + in, out := &in.Permissions, &out.Permissions + *out = make(GrantPermissions, len(*in)) + copy(*out, *in) + } + if in.Schema != nil { + in, out := &in.Schema, &out.Schema + *out = new(string) + **out = **in + } + if in.User != nil { + in, out := &in.User, &out.User + *out = new(string) + **out = **in + } + if in.UserRef != nil { + in, out := &in.UserRef, &out.UserRef + *out = new(v1.NamespacedReference) + (*in).DeepCopyInto(*out) + } + if in.UserSelector != nil { + in, out := &in.UserSelector, &out.UserSelector + *out = new(v1.NamespacedSelector) + (*in).DeepCopyInto(*out) + } + if in.Database != nil { + in, out := &in.Database, &out.Database + *out = new(string) + **out = **in + } + if in.DatabaseRef != nil { + in, out := &in.DatabaseRef, &out.DatabaseRef + *out = new(v1.NamespacedReference) + (*in).DeepCopyInto(*out) + } + if in.DatabaseSelector != nil { + in, out := &in.DatabaseSelector, &out.DatabaseSelector + *out = new(v1.NamespacedSelector) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrantParameters. +func (in *GrantParameters) DeepCopy() *GrantParameters { + if in == nil { + return nil + } + out := new(GrantParameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in GrantPermissions) DeepCopyInto(out *GrantPermissions) { + { + in := &in + *out = make(GrantPermissions, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrantPermissions. +func (in GrantPermissions) DeepCopy() GrantPermissions { + if in == nil { + return nil + } + out := new(GrantPermissions) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GrantSpec) DeepCopyInto(out *GrantSpec) { + *out = *in + in.ManagedResourceSpec.DeepCopyInto(&out.ManagedResourceSpec) + in.ForProvider.DeepCopyInto(&out.ForProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrantSpec. +func (in *GrantSpec) DeepCopy() *GrantSpec { + if in == nil { + return nil + } + out := new(GrantSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GrantStatus) DeepCopyInto(out *GrantStatus) { + *out = *in + in.ResourceStatus.DeepCopyInto(&out.ResourceStatus) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrantStatus. +func (in *GrantStatus) DeepCopy() *GrantStatus { + if in == nil { + return nil + } + out := new(GrantStatus) + 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 + in.Credentials.DeepCopyInto(&out.Credentials) +} + +// 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.TypedProviderConfigUsage.DeepCopyInto(&out.TypedProviderConfigUsage) +} + +// 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.ConnectionSecretRef.DeepCopyInto(&out.ConnectionSecretRef) +} + +// 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 *User) DeepCopyInto(out *User) { + *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 User. +func (in *User) DeepCopy() *User { + if in == nil { + return nil + } + out := new(User) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *User) 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 *UserList) DeepCopyInto(out *UserList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]User, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserList. +func (in *UserList) DeepCopy() *UserList { + if in == nil { + return nil + } + out := new(UserList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *UserList) 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 *UserObservation) DeepCopyInto(out *UserObservation) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserObservation. +func (in *UserObservation) DeepCopy() *UserObservation { + if in == nil { + return nil + } + out := new(UserObservation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserParameters) DeepCopyInto(out *UserParameters) { + *out = *in + if in.Database != nil { + in, out := &in.Database, &out.Database + *out = new(string) + **out = **in + } + if in.DatabaseRef != nil { + in, out := &in.DatabaseRef, &out.DatabaseRef + *out = new(v1.NamespacedReference) + (*in).DeepCopyInto(*out) + } + if in.DatabaseSelector != nil { + in, out := &in.DatabaseSelector, &out.DatabaseSelector + *out = new(v1.NamespacedSelector) + (*in).DeepCopyInto(*out) + } + if in.PasswordSecretRef != nil { + in, out := &in.PasswordSecretRef, &out.PasswordSecretRef + *out = new(v1.LocalSecretKeySelector) + **out = **in + } + if in.LoginDatabase != nil { + in, out := &in.LoginDatabase, &out.LoginDatabase + *out = new(string) + **out = **in + } + if in.LoginDatabaseRef != nil { + in, out := &in.LoginDatabaseRef, &out.LoginDatabaseRef + *out = new(v1.NamespacedReference) + (*in).DeepCopyInto(*out) + } + if in.LoginDatabaseSelector != nil { + in, out := &in.LoginDatabaseSelector, &out.LoginDatabaseSelector + *out = new(v1.NamespacedSelector) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserParameters. +func (in *UserParameters) DeepCopy() *UserParameters { + if in == nil { + return nil + } + out := new(UserParameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserSpec) DeepCopyInto(out *UserSpec) { + *out = *in + in.ManagedResourceSpec.DeepCopyInto(&out.ManagedResourceSpec) + in.ForProvider.DeepCopyInto(&out.ForProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserSpec. +func (in *UserSpec) DeepCopy() *UserSpec { + if in == nil { + return nil + } + out := new(UserSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserStatus) DeepCopyInto(out *UserStatus) { + *out = *in + in.ResourceStatus.DeepCopyInto(&out.ResourceStatus) + out.AtProvider = in.AtProvider +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserStatus. +func (in *UserStatus) DeepCopy() *UserStatus { + if in == nil { + return nil + } + out := new(UserStatus) + in.DeepCopyInto(out) + return out +} diff --git a/apis/namespaced/mssql/v1alpha1/zz_generated.managed.go b/apis/namespaced/mssql/v1alpha1/zz_generated.managed.go new file mode 100644 index 00000000..c893e4cf --- /dev/null +++ b/apis/namespaced/mssql/v1alpha1/zz_generated.managed.go @@ -0,0 +1,129 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +import xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + +// GetCondition of this Database. +func (mg *Database) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return mg.Status.GetCondition(ct) +} + +// GetManagementPolicies of this Database. +func (mg *Database) GetManagementPolicies() xpv1.ManagementPolicies { + return mg.Spec.ManagementPolicies +} + +// GetProviderConfigReference of this Database. +func (mg *Database) GetProviderConfigReference() *xpv1.ProviderConfigReference { + return mg.Spec.ProviderConfigReference +} + +// GetWriteConnectionSecretToReference of this Database. +func (mg *Database) GetWriteConnectionSecretToReference() *xpv1.LocalSecretReference { + return mg.Spec.WriteConnectionSecretToReference +} + +// SetConditions of this Database. +func (mg *Database) SetConditions(c ...xpv1.Condition) { + mg.Status.SetConditions(c...) +} + +// SetManagementPolicies of this Database. +func (mg *Database) SetManagementPolicies(r xpv1.ManagementPolicies) { + mg.Spec.ManagementPolicies = r +} + +// SetProviderConfigReference of this Database. +func (mg *Database) SetProviderConfigReference(r *xpv1.ProviderConfigReference) { + mg.Spec.ProviderConfigReference = r +} + +// SetWriteConnectionSecretToReference of this Database. +func (mg *Database) SetWriteConnectionSecretToReference(r *xpv1.LocalSecretReference) { + mg.Spec.WriteConnectionSecretToReference = r +} + +// GetCondition of this Grant. +func (mg *Grant) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return mg.Status.GetCondition(ct) +} + +// GetManagementPolicies of this Grant. +func (mg *Grant) GetManagementPolicies() xpv1.ManagementPolicies { + return mg.Spec.ManagementPolicies +} + +// GetProviderConfigReference of this Grant. +func (mg *Grant) GetProviderConfigReference() *xpv1.ProviderConfigReference { + return mg.Spec.ProviderConfigReference +} + +// GetWriteConnectionSecretToReference of this Grant. +func (mg *Grant) GetWriteConnectionSecretToReference() *xpv1.LocalSecretReference { + return mg.Spec.WriteConnectionSecretToReference +} + +// SetConditions of this Grant. +func (mg *Grant) SetConditions(c ...xpv1.Condition) { + mg.Status.SetConditions(c...) +} + +// SetManagementPolicies of this Grant. +func (mg *Grant) SetManagementPolicies(r xpv1.ManagementPolicies) { + mg.Spec.ManagementPolicies = r +} + +// SetProviderConfigReference of this Grant. +func (mg *Grant) SetProviderConfigReference(r *xpv1.ProviderConfigReference) { + mg.Spec.ProviderConfigReference = r +} + +// SetWriteConnectionSecretToReference of this Grant. +func (mg *Grant) SetWriteConnectionSecretToReference(r *xpv1.LocalSecretReference) { + mg.Spec.WriteConnectionSecretToReference = r +} + +// GetCondition of this User. +func (mg *User) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return mg.Status.GetCondition(ct) +} + +// GetManagementPolicies of this User. +func (mg *User) GetManagementPolicies() xpv1.ManagementPolicies { + return mg.Spec.ManagementPolicies +} + +// GetProviderConfigReference of this User. +func (mg *User) GetProviderConfigReference() *xpv1.ProviderConfigReference { + return mg.Spec.ProviderConfigReference +} + +// GetWriteConnectionSecretToReference of this User. +func (mg *User) GetWriteConnectionSecretToReference() *xpv1.LocalSecretReference { + return mg.Spec.WriteConnectionSecretToReference +} + +// SetConditions of this User. +func (mg *User) SetConditions(c ...xpv1.Condition) { + mg.Status.SetConditions(c...) +} + +// SetManagementPolicies of this User. +func (mg *User) SetManagementPolicies(r xpv1.ManagementPolicies) { + mg.Spec.ManagementPolicies = r +} + +// SetProviderConfigReference of this User. +func (mg *User) SetProviderConfigReference(r *xpv1.ProviderConfigReference) { + mg.Spec.ProviderConfigReference = r +} + +// SetWriteConnectionSecretToReference of this User. +func (mg *User) SetWriteConnectionSecretToReference(r *xpv1.LocalSecretReference) { + mg.Spec.WriteConnectionSecretToReference = r +} diff --git a/apis/namespaced/mssql/v1alpha1/zz_generated.managedlist.go b/apis/namespaced/mssql/v1alpha1/zz_generated.managedlist.go new file mode 100644 index 00000000..258aaf8b --- /dev/null +++ b/apis/namespaced/mssql/v1alpha1/zz_generated.managedlist.go @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +import resource "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + +// GetItems of this DatabaseList. +func (l *DatabaseList) GetItems() []resource.Managed { + items := make([]resource.Managed, len(l.Items)) + for i := range l.Items { + items[i] = &l.Items[i] + } + return items +} + +// GetItems of this GrantList. +func (l *GrantList) GetItems() []resource.Managed { + items := make([]resource.Managed, len(l.Items)) + for i := range l.Items { + items[i] = &l.Items[i] + } + return items +} + +// GetItems of this UserList. +func (l *UserList) 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/mssql/v1alpha1/zz_generated.pc.go b/apis/namespaced/mssql/v1alpha1/zz_generated.pc.go new file mode 100644 index 00000000..1db11335 --- /dev/null +++ b/apis/namespaced/mssql/v1alpha1/zz_generated.pc.go @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +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/mssql/v1alpha1/zz_generated.pcu.go b/apis/namespaced/mssql/v1alpha1/zz_generated.pcu.go new file mode 100644 index 00000000..dfbdb116 --- /dev/null +++ b/apis/namespaced/mssql/v1alpha1/zz_generated.pcu.go @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +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/mssql/v1alpha1/zz_generated.pculist.go b/apis/namespaced/mssql/v1alpha1/zz_generated.pculist.go new file mode 100644 index 00000000..13fa5547 --- /dev/null +++ b/apis/namespaced/mssql/v1alpha1/zz_generated.pculist.go @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +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/namespaced/mssql/v1alpha1/zz_generated.resolvers.go b/apis/namespaced/mssql/v1alpha1/zz_generated.resolvers.go new file mode 100644 index 00000000..a4c6a710 --- /dev/null +++ b/apis/namespaced/mssql/v1alpha1/zz_generated.resolvers.go @@ -0,0 +1,102 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + reference "github.com/crossplane/crossplane-runtime/v2/pkg/reference" + errors "github.com/pkg/errors" + client "sigs.k8s.io/controller-runtime/pkg/client" +) + +// ResolveReferences of this Grant. +func (mg *Grant) ResolveReferences(ctx context.Context, c client.Reader) error { + r := reference.NewAPINamespacedResolver(c, mg) + + var rsp reference.NamespacedResolutionResponse + var err error + + rsp, err = r.Resolve(ctx, reference.NamespacedResolutionRequest{ + CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.User), + Extract: reference.ExternalName(), + Namespace: mg.GetNamespace(), + Reference: mg.Spec.ForProvider.UserRef, + Selector: mg.Spec.ForProvider.UserSelector, + To: reference.To{ + List: &UserList{}, + Managed: &User{}, + }, + }) + if err != nil { + return errors.Wrap(err, "mg.Spec.ForProvider.User") + } + mg.Spec.ForProvider.User = reference.ToPtrValue(rsp.ResolvedValue) + mg.Spec.ForProvider.UserRef = rsp.ResolvedReference + + rsp, err = r.Resolve(ctx, reference.NamespacedResolutionRequest{ + CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.Database), + Extract: reference.ExternalName(), + Namespace: mg.GetNamespace(), + Reference: mg.Spec.ForProvider.DatabaseRef, + Selector: mg.Spec.ForProvider.DatabaseSelector, + To: reference.To{ + List: &DatabaseList{}, + Managed: &Database{}, + }, + }) + if err != nil { + return errors.Wrap(err, "mg.Spec.ForProvider.Database") + } + mg.Spec.ForProvider.Database = reference.ToPtrValue(rsp.ResolvedValue) + mg.Spec.ForProvider.DatabaseRef = rsp.ResolvedReference + + return nil +} + +// ResolveReferences of this User. +func (mg *User) ResolveReferences(ctx context.Context, c client.Reader) error { + r := reference.NewAPINamespacedResolver(c, mg) + + var rsp reference.NamespacedResolutionResponse + var err error + + rsp, err = r.Resolve(ctx, reference.NamespacedResolutionRequest{ + CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.Database), + Extract: reference.ExternalName(), + Namespace: mg.GetNamespace(), + Reference: mg.Spec.ForProvider.DatabaseRef, + Selector: mg.Spec.ForProvider.DatabaseSelector, + To: reference.To{ + List: &DatabaseList{}, + Managed: &Database{}, + }, + }) + if err != nil { + return errors.Wrap(err, "mg.Spec.ForProvider.Database") + } + mg.Spec.ForProvider.Database = reference.ToPtrValue(rsp.ResolvedValue) + mg.Spec.ForProvider.DatabaseRef = rsp.ResolvedReference + + rsp, err = r.Resolve(ctx, reference.NamespacedResolutionRequest{ + CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.LoginDatabase), + Extract: reference.ExternalName(), + Namespace: mg.GetNamespace(), + Reference: mg.Spec.ForProvider.LoginDatabaseRef, + Selector: mg.Spec.ForProvider.LoginDatabaseSelector, + To: reference.To{ + List: &DatabaseList{}, + Managed: &Database{}, + }, + }) + if err != nil { + return errors.Wrap(err, "mg.Spec.ForProvider.LoginDatabase") + } + mg.Spec.ForProvider.LoginDatabase = reference.ToPtrValue(rsp.ResolvedValue) + mg.Spec.ForProvider.LoginDatabaseRef = rsp.ResolvedReference + + return nil +} diff --git a/apis/namespaced/mysql/v1alpha1/cluster_provider_types.go b/apis/namespaced/mysql/v1alpha1/cluster_provider_types.go new file mode 100644 index 00000000..a1cfccc4 --- /dev/null +++ b/apis/namespaced/mysql/v1alpha1/cluster_provider_types.go @@ -0,0 +1,84 @@ +/* +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 v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" +) + +// A ClusterProviderConfigSpec defines the desired state of a ClusterProviderConfig. +type ClusterProviderConfigSpec struct { + // Credentials required to authenticate to this provider. + Credentials ClusterProviderCredentials `json:"credentials"` + + // tls=true enables TLS / SSL encrypted connection to the server. + // Use skip-verify if you want to use a self-signed or invalid certificate (server side) + // or use preferred to use TLS only when advertised by the server. This is similar + // to skip-verify, but additionally allows a fallback to a connection which is + // not encrypted. Neither skip-verify nor preferred add any reliable security. + // Alternatively, set tls=custom and provide a custom TLS configuration via the tlsConfig field. + // +kubebuilder:validation:Enum="true";skip-verify;preferred;custom + // +optional + TLS *string `json:"tls"` + + // Optional TLS configuration for sql driver. Setting this field also requires the tls field to be set to custom. + // +optional + TLSConfig *TLSConfig `json:"tlsConfig"` +} + +// ClusterProviderCredentials required to authenticate. +type ClusterProviderCredentials struct { + // Source of the provider credentials. + // +kubebuilder:validation:Enum=MySQLConnectionSecret + Source MySQLConnectionSecretSource `json:"source"` + + // A CredentialsSecretRef is a reference to a MySQL connection secret + // that contains the credentials that must be used to connect to the + // provider. +optional + ConnectionSecretRef xpv1.SecretReference `json:"connectionSecretRef,omitempty"` +} + +// A ClusterProviderConfigStatus reflects the observed state of a ClusterProviderConfig. +type ClusterProviderConfigStatus struct { + xpv1.ProviderConfigStatus `json:",inline"` +} + +// +kubebuilder:object:root=true + +// A ClusterProviderConfig configures a Template provider. +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:printcolumn:name="SECRET-NAME",type="string",JSONPath=".spec.credentials.connectionSecretRef.name",priority=1 +// +kubebuilder:resource:scope=Cluster,categories={crossplane,provider,sql} +type ClusterProviderConfig struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ClusterProviderConfigSpec `json:"spec"` + Status ClusterProviderConfigStatus `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/mysql/v1alpha1/database_types.go b/apis/namespaced/mysql/v1alpha1/database_types.go new file mode 100644 index 00000000..dfef3e6f --- /dev/null +++ b/apis/namespaced/mysql/v1alpha1/database_types.go @@ -0,0 +1,68 @@ +/* +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 v1alpha1 + +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 DatabaseSpec defines the desired state of a Database. +type DatabaseSpec struct { + xpv2.ManagedResourceSpec `json:",inline"` + // +optional + ForProvider DatabaseParameters `json:"forProvider"` +} + +// A DatabaseStatus represents the observed state of a Database. +type DatabaseStatus struct { + xpv1.ResourceStatus `json:",inline"` +} + +// DatabaseParameters define the desired state of a MySQL database instance. +type DatabaseParameters struct { + // BinLog defines whether the create, delete, update operations of this database are propagated to replicas. Defaults to true + // +optional + BinLog *bool `json:"binlog,omitempty"` +} + +// +kubebuilder:object:root=true + +// A Database represents the declarative state of a MySQL database. +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +// +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status" +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:resource:scope=Namespaced,categories={crossplane,managed,sql} +type Database struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec DatabaseSpec `json:"spec"` + Status DatabaseStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// DatabaseList contains a list of Database +type DatabaseList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Database `json:"items"` +} diff --git a/apis/postgresql/v1alpha1/zz_generated.pculist.go b/apis/namespaced/mysql/v1alpha1/doc.go similarity index 57% rename from apis/postgresql/v1alpha1/zz_generated.pculist.go rename to apis/namespaced/mysql/v1alpha1/doc.go index 5f8fdede..0e8dae00 100644 --- a/apis/postgresql/v1alpha1/zz_generated.pculist.go +++ b/apis/namespaced/mysql/v1alpha1/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Crossplane Authors. +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. @@ -13,17 +13,9 @@ 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 v1alpha1 contains the core resources of the SQL provider. +// +kubebuilder:object:generate=true +// +groupName=mysql.sql.m.crossplane.io +// +versionName=v1alpha1 package v1alpha1 - -import resource "github.com/crossplane/crossplane-runtime/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/namespaced/mysql/v1alpha1/grant_types.go b/apis/namespaced/mysql/v1alpha1/grant_types.go new file mode 100644 index 00000000..2e0f88a6 --- /dev/null +++ b/apis/namespaced/mysql/v1alpha1/grant_types.go @@ -0,0 +1,138 @@ +/* +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 v1alpha1 + +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 GrantSpec defines the desired state of a Grant. +type GrantSpec struct { + xpv2.ManagedResourceSpec `json:",inline"` + // +optional + ForProvider GrantParameters `json:"forProvider"` +} + +// GrantPrivilege represents a privilege to be granted +// +kubebuilder:validation:Pattern:=^[A-Z_ ]+$ +type GrantPrivilege string + +// If Privileges are specified, we should have at least one + +// GrantPrivileges is a list of the privileges to be granted +// +kubebuilder:validation:MinItems:=1 +type GrantPrivileges []GrantPrivilege + +// ToStringSlice converts the slice of privileges to strings +func (gp *GrantPrivileges) ToStringSlice() []string { + if gp == nil { + return []string{} + } + out := make([]string, len(*gp)) + for i, v := range *gp { + out[i] = string(v) + } + return out +} + +// GrantParameters define the desired state of a MySQL grant instance. +type GrantParameters struct { + // Privileges to be granted. + // See https://mariadb.com/kb/en/grant/#database-privileges for available privileges. + Privileges GrantPrivileges `json:"privileges"` + + // User this grant is for. + // +optional + // +crossplane:generate:reference:type=User + User *string `json:"user,omitempty"` + + // UserRef references the user object this grant is for. + // +immutable + // +optional + UserRef *xpv1.NamespacedReference `json:"userRef,omitempty"` + + // UserSelector selects a reference to a User this grant is for. + // +immutable + // +optional + UserSelector *xpv1.NamespacedSelector `json:"userSelector,omitempty"` + + // Tables this grant is for, default *. + // +optional + Table *string `json:"table,omitempty" default:"*"` + + // Database this grant is for, default *. + // +optional + // +crossplane:generate:reference:type=Database + Database *string `json:"database,omitempty" default:"*"` + + // DatabaseRef references the database object this grant it for. + // +immutable + // +optional + DatabaseRef *xpv1.NamespacedReference `json:"databaseRef,omitempty"` + + // DatabaseSelector selects a reference to a Database this grant is for. + // +immutable + // +optional + DatabaseSelector *xpv1.NamespacedSelector `json:"databaseSelector,omitempty"` + + // BinLog defines whether the create, delete, update operations of this grant are propagated to replicas. Defaults to true + // +optional + BinLog *bool `json:"binlog,omitempty"` +} + +// A GrantStatus represents the observed state of a Grant. +type GrantStatus struct { + xpv1.ResourceStatus `json:",inline"` + AtProvider GrantObservation `json:"atProvider,omitempty"` +} + +// A GrantObservation represents the observed state of a MySQL grant. +type GrantObservation struct { + // Privileges represents the applied privileges + Privileges []string `json:"privileges,omitempty"` +} + +// +kubebuilder:object:root=true + +// A Grant represents the declarative state of a MySQL grant. +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +// +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status" +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:printcolumn:name="ROLE",type="string",JSONPath=".spec.forProvider.user" +// +kubebuilder:printcolumn:name="DATABASE",type="string",JSONPath=".spec.forProvider.database" +// +kubebuilder:printcolumn:name="PRIVILEGES",type="string",JSONPath=".spec.forProvider.privileges" +// +kubebuilder:resource:scope=Namespaced,categories={crossplane,managed,sql} +type Grant struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec GrantSpec `json:"spec"` + Status GrantStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// GrantList contains a list of Grant +type GrantList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Grant `json:"items"` +} diff --git a/apis/namespaced/mysql/v1alpha1/provider_types.go b/apis/namespaced/mysql/v1alpha1/provider_types.go new file mode 100644 index 00000000..ab86a690 --- /dev/null +++ b/apis/namespaced/mysql/v1alpha1/provider_types.go @@ -0,0 +1,130 @@ +/* +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 v1alpha1 + +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. + Credentials ProviderCredentials `json:"credentials"` + // tls=true enables TLS / SSL encrypted connection to the server. + // Use skip-verify if you want to use a self-signed or invalid certificate (server side) + // or use preferred to use TLS only when advertised by the server. This is similar + // to skip-verify, but additionally allows a fallback to a connection which is + // not encrypted. Neither skip-verify nor preferred add any reliable security. + // Alternatively, set tls=custom and provide a custom TLS configuration via the tlsConfig field. + // +kubebuilder:validation:Enum="true";skip-verify;preferred;custom + // +optional + TLS *string `json:"tls"` + + // Optional TLS configuration for sql driver. Setting this field also requires the tls field to be set to custom. + // +optional + TLSConfig *TLSConfig `json:"tlsConfig"` +} + +// TLSConfig defines the TLS configuration for the provider when tls=custom. +type TLSConfig struct { + CACert TLSSecret `json:"caCert,omitempty"` + ClientCert TLSSecret `json:"clientCert,omitempty"` + ClientKey TLSSecret `json:"clientKey,omitempty"` + InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"` +} + +// TLSSecret defines a reference to a K8s secret and its specific internal key that contains the TLS cert/keys in PEM format. +type TLSSecret struct { + SecretRef xpv1.SecretKeySelector `json:"secretRef,omitempty"` +} + +type MySQLConnectionSecretSource string + +const ( + // CredentialsSourceMySQLConnectionSecret indicates that a provider + // should acquire credentials from a connection secret written by a managed + // resource that represents a MySQL server. + CredentialsSourceMySQLConnectionSecret MySQLConnectionSecretSource = "MySQLConnectionSecret" +) + +// ProviderCredentials required to authenticate. +type ProviderCredentials struct { + // Source of the provider credentials. + // +kubebuilder:validation:Enum=MySQLConnectionSecret + Source MySQLConnectionSecretSource `json:"source"` + + // A CredentialsSecretRef is a reference to a MySQL connection secret + // that contains the credentials that must be used to connect to the + // provider. +optional + ConnectionSecretRef xpv1.LocalSecretReference `json:"connectionSecretRef,omitempty"` +} + +// A ProviderConfigStatus reflects the observed state of a ProviderConfig. +type ProviderConfigStatus struct { + xpv1.ProviderConfigStatus `json:",inline"` +} + +// +kubebuilder:object:root=true + +// A ProviderConfig configures a Template provider. +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:printcolumn:name="SECRET-NAME",type="string",JSONPath=".spec.credentials.connectionSecretRef.name",priority=1 +// +kubebuilder:resource:scope=Namespaced,categories={crossplane,provider,sql} +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 + +// A ProviderConfigList contains a list of ClusterProviderConfig. +type ProviderConfigList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ClusterProviderConfig `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,sql} +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"` +} diff --git a/apis/namespaced/mysql/v1alpha1/register.go b/apis/namespaced/mysql/v1alpha1/register.go new file mode 100644 index 00000000..1b5a1a8a --- /dev/null +++ b/apis/namespaced/mysql/v1alpha1/register.go @@ -0,0 +1,100 @@ +/* +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 v1alpha1 + +import ( + "reflect" + + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +// Package type metadata. +const ( + Group = "mysql.sql.m.crossplane.io" + Version = "v1alpha1" +) + +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) +) + +// ClusterProviderConfig type metadata. +var ( + ClusterProviderConfigKind = reflect.TypeOf(ClusterProviderConfig{}).Name() + ClusterProviderConfigGroupKind = schema.GroupKind{Group: Group, Kind: ClusterProviderConfigKind}.String() + ClusterProviderConfigKindAPIVersion = ClusterProviderConfigKind + "." + SchemeGroupVersion.String() + ClusterProviderConfigGroupVersionKind = SchemeGroupVersion.WithKind(ClusterProviderConfigKind) +) + +// Database type metadata. +var ( + DatabaseKind = reflect.TypeOf(Database{}).Name() + DatabaseGroupKind = schema.GroupKind{Group: Group, Kind: DatabaseKind}.String() + DatabaseKindAPIVersion = DatabaseKind + "." + SchemeGroupVersion.String() + DatabaseGroupVersionKind = SchemeGroupVersion.WithKind(DatabaseKind) +) + +// User type metadata. +var ( + UserKind = reflect.TypeOf(User{}).Name() + UserGroupKind = schema.GroupKind{Group: Group, Kind: UserKind}.String() + UserKindAPIVersion = UserKind + "." + SchemeGroupVersion.String() + UserGroupVersionKind = SchemeGroupVersion.WithKind(UserKind) +) + +// Grant type metadata. +var ( + GrantKind = reflect.TypeOf(Grant{}).Name() + GrantGroupKind = schema.GroupKind{Group: Group, Kind: GrantKind}.String() + GrantKindAPIVersion = GrantKind + "." + SchemeGroupVersion.String() + GrantGroupVersionKind = SchemeGroupVersion.WithKind(GrantKind) +) + +func init() { + SchemeBuilder.Register(&ClusterProviderConfig{}, &ClusterProviderConfigList{}) + SchemeBuilder.Register(&ProviderConfig{}, &ProviderConfigList{}) + SchemeBuilder.Register(&ProviderConfigUsage{}, &ProviderConfigUsageList{}) + SchemeBuilder.Register(&Database{}, &DatabaseList{}) + SchemeBuilder.Register(&User{}, &UserList{}) + SchemeBuilder.Register(&Grant{}, &GrantList{}) +} diff --git a/apis/namespaced/mysql/v1alpha1/user_types.go b/apis/namespaced/mysql/v1alpha1/user_types.go new file mode 100644 index 00000000..d8a8a1c8 --- /dev/null +++ b/apis/namespaced/mysql/v1alpha1/user_types.go @@ -0,0 +1,103 @@ +/* +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 v1alpha1 + +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 UserSpec defines the desired state of a Database. +type UserSpec struct { + xpv2.ManagedResourceSpec `json:",inline"` + ForProvider UserParameters `json:"forProvider"` +} + +// A UserStatus represents the observed state of a User. +type UserStatus struct { + xpv1.ResourceStatus `json:",inline"` + AtProvider UserObservation `json:"atProvider,omitempty"` +} + +// UserParameters define the desired state of a MySQL user instance. +type UserParameters struct { + // PasswordSecretRef references the secret that contains the password used + // for this user. If no reference is given, a password will be auto-generated. + // +optional + PasswordSecretRef *xpv1.LocalSecretKeySelector `json:"passwordSecretRef,omitempty"` + + // ResourceOptions sets account specific resource limits. + // See https://dev.mysql.com/doc/refman/8.0/en/user-resources.html + // +optional + ResourceOptions *ResourceOptions `json:"resourceOptions,omitempty"` + + // BinLog defines whether the create, delete, update operations of this user are propagated to replicas. Defaults to true + // +optional + BinLog *bool `json:"binlog,omitempty"` +} + +// ResourceOptions define the account specific resource limits. +type ResourceOptions struct { + // MaxQueriesPerHour sets the number of queries an account can issue per hour + // +optional + MaxQueriesPerHour *int `json:"maxQueriesPerHour,omitempty"` + + // MaxUpdatesPerHour sets the number of updates an account can issue per hour + // +optional + MaxUpdatesPerHour *int `json:"maxUpdatesPerHour,omitempty"` + + // MaxConnectionsPerHour sets the number of times an account can connect to the server per hour + // +optional + MaxConnectionsPerHour *int `json:"maxConnectionsPerHour,omitempty"` + + // MaxUserConnections sets The number of simultaneous connections to the server by an account + // +optional + MaxUserConnections *int `json:"maxUserConnections,omitempty"` +} + +// A UserObservation represents the observed state of a MySQL user. +type UserObservation struct { + // ResourceOptionsAsClauses represents the applied resource options + ResourceOptionsAsClauses []string `json:"resourceOptionsAsClauses,omitempty"` +} + +// +kubebuilder:object:root=true + +// A User represents the declarative state of a MySQL user. +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +// +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status" +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:resource:scope=Namespaced,categories={crossplane,managed,sql} +type User struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec UserSpec `json:"spec"` + Status UserStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// UserList contains a list of User +type UserList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []User `json:"items"` +} diff --git a/apis/namespaced/mysql/v1alpha1/zz_generated.deepcopy.go b/apis/namespaced/mysql/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000..542e5a78 --- /dev/null +++ b/apis/namespaced/mysql/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,822 @@ +//go:build !ignore_autogenerated + +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + runtime "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 *ClusterProviderConfigSpec) DeepCopyInto(out *ClusterProviderConfigSpec) { + *out = *in + in.Credentials.DeepCopyInto(&out.Credentials) + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + *out = new(string) + **out = **in + } + if in.TLSConfig != nil { + in, out := &in.TLSConfig, &out.TLSConfig + *out = new(TLSConfig) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterProviderConfigSpec. +func (in *ClusterProviderConfigSpec) DeepCopy() *ClusterProviderConfigSpec { + if in == nil { + return nil + } + out := new(ClusterProviderConfigSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterProviderConfigStatus) DeepCopyInto(out *ClusterProviderConfigStatus) { + *out = *in + in.ProviderConfigStatus.DeepCopyInto(&out.ProviderConfigStatus) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterProviderConfigStatus. +func (in *ClusterProviderConfigStatus) DeepCopy() *ClusterProviderConfigStatus { + if in == nil { + return nil + } + out := new(ClusterProviderConfigStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterProviderCredentials) DeepCopyInto(out *ClusterProviderCredentials) { + *out = *in + in.ConnectionSecretRef.DeepCopyInto(&out.ConnectionSecretRef) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterProviderCredentials. +func (in *ClusterProviderCredentials) DeepCopy() *ClusterProviderCredentials { + if in == nil { + return nil + } + out := new(ClusterProviderCredentials) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Database) DeepCopyInto(out *Database) { + *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 Database. +func (in *Database) DeepCopy() *Database { + if in == nil { + return nil + } + out := new(Database) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Database) 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 *DatabaseList) DeepCopyInto(out *DatabaseList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Database, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseList. +func (in *DatabaseList) DeepCopy() *DatabaseList { + if in == nil { + return nil + } + out := new(DatabaseList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DatabaseList) 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 *DatabaseParameters) DeepCopyInto(out *DatabaseParameters) { + *out = *in + if in.BinLog != nil { + in, out := &in.BinLog, &out.BinLog + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseParameters. +func (in *DatabaseParameters) DeepCopy() *DatabaseParameters { + if in == nil { + return nil + } + out := new(DatabaseParameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DatabaseSpec) DeepCopyInto(out *DatabaseSpec) { + *out = *in + in.ManagedResourceSpec.DeepCopyInto(&out.ManagedResourceSpec) + in.ForProvider.DeepCopyInto(&out.ForProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseSpec. +func (in *DatabaseSpec) DeepCopy() *DatabaseSpec { + if in == nil { + return nil + } + out := new(DatabaseSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DatabaseStatus) DeepCopyInto(out *DatabaseStatus) { + *out = *in + in.ResourceStatus.DeepCopyInto(&out.ResourceStatus) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseStatus. +func (in *DatabaseStatus) DeepCopy() *DatabaseStatus { + if in == nil { + return nil + } + out := new(DatabaseStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Grant) DeepCopyInto(out *Grant) { + *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 Grant. +func (in *Grant) DeepCopy() *Grant { + if in == nil { + return nil + } + out := new(Grant) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Grant) 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 *GrantList) DeepCopyInto(out *GrantList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Grant, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrantList. +func (in *GrantList) DeepCopy() *GrantList { + if in == nil { + return nil + } + out := new(GrantList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GrantList) 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 *GrantObservation) DeepCopyInto(out *GrantObservation) { + *out = *in + if in.Privileges != nil { + in, out := &in.Privileges, &out.Privileges + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrantObservation. +func (in *GrantObservation) DeepCopy() *GrantObservation { + if in == nil { + return nil + } + out := new(GrantObservation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GrantParameters) DeepCopyInto(out *GrantParameters) { + *out = *in + if in.Privileges != nil { + in, out := &in.Privileges, &out.Privileges + *out = make(GrantPrivileges, len(*in)) + copy(*out, *in) + } + if in.User != nil { + in, out := &in.User, &out.User + *out = new(string) + **out = **in + } + if in.UserRef != nil { + in, out := &in.UserRef, &out.UserRef + *out = new(v1.NamespacedReference) + (*in).DeepCopyInto(*out) + } + if in.UserSelector != nil { + in, out := &in.UserSelector, &out.UserSelector + *out = new(v1.NamespacedSelector) + (*in).DeepCopyInto(*out) + } + if in.Table != nil { + in, out := &in.Table, &out.Table + *out = new(string) + **out = **in + } + if in.Database != nil { + in, out := &in.Database, &out.Database + *out = new(string) + **out = **in + } + if in.DatabaseRef != nil { + in, out := &in.DatabaseRef, &out.DatabaseRef + *out = new(v1.NamespacedReference) + (*in).DeepCopyInto(*out) + } + if in.DatabaseSelector != nil { + in, out := &in.DatabaseSelector, &out.DatabaseSelector + *out = new(v1.NamespacedSelector) + (*in).DeepCopyInto(*out) + } + if in.BinLog != nil { + in, out := &in.BinLog, &out.BinLog + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrantParameters. +func (in *GrantParameters) DeepCopy() *GrantParameters { + if in == nil { + return nil + } + out := new(GrantParameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in GrantPrivileges) DeepCopyInto(out *GrantPrivileges) { + { + in := &in + *out = make(GrantPrivileges, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrantPrivileges. +func (in GrantPrivileges) DeepCopy() GrantPrivileges { + if in == nil { + return nil + } + out := new(GrantPrivileges) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GrantSpec) DeepCopyInto(out *GrantSpec) { + *out = *in + in.ManagedResourceSpec.DeepCopyInto(&out.ManagedResourceSpec) + in.ForProvider.DeepCopyInto(&out.ForProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrantSpec. +func (in *GrantSpec) DeepCopy() *GrantSpec { + if in == nil { + return nil + } + out := new(GrantSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GrantStatus) DeepCopyInto(out *GrantStatus) { + *out = *in + in.ResourceStatus.DeepCopyInto(&out.ResourceStatus) + in.AtProvider.DeepCopyInto(&out.AtProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrantStatus. +func (in *GrantStatus) DeepCopy() *GrantStatus { + if in == nil { + return nil + } + out := new(GrantStatus) + 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([]ClusterProviderConfig, 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 + in.Credentials.DeepCopyInto(&out.Credentials) + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + *out = new(string) + **out = **in + } + if in.TLSConfig != nil { + in, out := &in.TLSConfig, &out.TLSConfig + *out = new(TLSConfig) + (*in).DeepCopyInto(*out) + } +} + +// 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.TypedProviderConfigUsage.DeepCopyInto(&out.TypedProviderConfigUsage) +} + +// 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.ConnectionSecretRef.DeepCopyInto(&out.ConnectionSecretRef) +} + +// 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 *ResourceOptions) DeepCopyInto(out *ResourceOptions) { + *out = *in + if in.MaxQueriesPerHour != nil { + in, out := &in.MaxQueriesPerHour, &out.MaxQueriesPerHour + *out = new(int) + **out = **in + } + if in.MaxUpdatesPerHour != nil { + in, out := &in.MaxUpdatesPerHour, &out.MaxUpdatesPerHour + *out = new(int) + **out = **in + } + if in.MaxConnectionsPerHour != nil { + in, out := &in.MaxConnectionsPerHour, &out.MaxConnectionsPerHour + *out = new(int) + **out = **in + } + if in.MaxUserConnections != nil { + in, out := &in.MaxUserConnections, &out.MaxUserConnections + *out = new(int) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceOptions. +func (in *ResourceOptions) DeepCopy() *ResourceOptions { + if in == nil { + return nil + } + out := new(ResourceOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSConfig) DeepCopyInto(out *TLSConfig) { + *out = *in + in.CACert.DeepCopyInto(&out.CACert) + in.ClientCert.DeepCopyInto(&out.ClientCert) + in.ClientKey.DeepCopyInto(&out.ClientKey) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSConfig. +func (in *TLSConfig) DeepCopy() *TLSConfig { + if in == nil { + return nil + } + out := new(TLSConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSSecret) DeepCopyInto(out *TLSSecret) { + *out = *in + in.SecretRef.DeepCopyInto(&out.SecretRef) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSSecret. +func (in *TLSSecret) DeepCopy() *TLSSecret { + if in == nil { + return nil + } + out := new(TLSSecret) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *User) DeepCopyInto(out *User) { + *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 User. +func (in *User) DeepCopy() *User { + if in == nil { + return nil + } + out := new(User) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *User) 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 *UserList) DeepCopyInto(out *UserList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]User, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserList. +func (in *UserList) DeepCopy() *UserList { + if in == nil { + return nil + } + out := new(UserList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *UserList) 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 *UserObservation) DeepCopyInto(out *UserObservation) { + *out = *in + if in.ResourceOptionsAsClauses != nil { + in, out := &in.ResourceOptionsAsClauses, &out.ResourceOptionsAsClauses + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserObservation. +func (in *UserObservation) DeepCopy() *UserObservation { + if in == nil { + return nil + } + out := new(UserObservation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserParameters) DeepCopyInto(out *UserParameters) { + *out = *in + if in.PasswordSecretRef != nil { + in, out := &in.PasswordSecretRef, &out.PasswordSecretRef + *out = new(v1.LocalSecretKeySelector) + **out = **in + } + if in.ResourceOptions != nil { + in, out := &in.ResourceOptions, &out.ResourceOptions + *out = new(ResourceOptions) + (*in).DeepCopyInto(*out) + } + if in.BinLog != nil { + in, out := &in.BinLog, &out.BinLog + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserParameters. +func (in *UserParameters) DeepCopy() *UserParameters { + if in == nil { + return nil + } + out := new(UserParameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserSpec) DeepCopyInto(out *UserSpec) { + *out = *in + in.ManagedResourceSpec.DeepCopyInto(&out.ManagedResourceSpec) + in.ForProvider.DeepCopyInto(&out.ForProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserSpec. +func (in *UserSpec) DeepCopy() *UserSpec { + if in == nil { + return nil + } + out := new(UserSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserStatus) DeepCopyInto(out *UserStatus) { + *out = *in + in.ResourceStatus.DeepCopyInto(&out.ResourceStatus) + in.AtProvider.DeepCopyInto(&out.AtProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserStatus. +func (in *UserStatus) DeepCopy() *UserStatus { + if in == nil { + return nil + } + out := new(UserStatus) + in.DeepCopyInto(out) + return out +} diff --git a/apis/namespaced/mysql/v1alpha1/zz_generated.managed.go b/apis/namespaced/mysql/v1alpha1/zz_generated.managed.go new file mode 100644 index 00000000..c893e4cf --- /dev/null +++ b/apis/namespaced/mysql/v1alpha1/zz_generated.managed.go @@ -0,0 +1,129 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +import xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + +// GetCondition of this Database. +func (mg *Database) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return mg.Status.GetCondition(ct) +} + +// GetManagementPolicies of this Database. +func (mg *Database) GetManagementPolicies() xpv1.ManagementPolicies { + return mg.Spec.ManagementPolicies +} + +// GetProviderConfigReference of this Database. +func (mg *Database) GetProviderConfigReference() *xpv1.ProviderConfigReference { + return mg.Spec.ProviderConfigReference +} + +// GetWriteConnectionSecretToReference of this Database. +func (mg *Database) GetWriteConnectionSecretToReference() *xpv1.LocalSecretReference { + return mg.Spec.WriteConnectionSecretToReference +} + +// SetConditions of this Database. +func (mg *Database) SetConditions(c ...xpv1.Condition) { + mg.Status.SetConditions(c...) +} + +// SetManagementPolicies of this Database. +func (mg *Database) SetManagementPolicies(r xpv1.ManagementPolicies) { + mg.Spec.ManagementPolicies = r +} + +// SetProviderConfigReference of this Database. +func (mg *Database) SetProviderConfigReference(r *xpv1.ProviderConfigReference) { + mg.Spec.ProviderConfigReference = r +} + +// SetWriteConnectionSecretToReference of this Database. +func (mg *Database) SetWriteConnectionSecretToReference(r *xpv1.LocalSecretReference) { + mg.Spec.WriteConnectionSecretToReference = r +} + +// GetCondition of this Grant. +func (mg *Grant) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return mg.Status.GetCondition(ct) +} + +// GetManagementPolicies of this Grant. +func (mg *Grant) GetManagementPolicies() xpv1.ManagementPolicies { + return mg.Spec.ManagementPolicies +} + +// GetProviderConfigReference of this Grant. +func (mg *Grant) GetProviderConfigReference() *xpv1.ProviderConfigReference { + return mg.Spec.ProviderConfigReference +} + +// GetWriteConnectionSecretToReference of this Grant. +func (mg *Grant) GetWriteConnectionSecretToReference() *xpv1.LocalSecretReference { + return mg.Spec.WriteConnectionSecretToReference +} + +// SetConditions of this Grant. +func (mg *Grant) SetConditions(c ...xpv1.Condition) { + mg.Status.SetConditions(c...) +} + +// SetManagementPolicies of this Grant. +func (mg *Grant) SetManagementPolicies(r xpv1.ManagementPolicies) { + mg.Spec.ManagementPolicies = r +} + +// SetProviderConfigReference of this Grant. +func (mg *Grant) SetProviderConfigReference(r *xpv1.ProviderConfigReference) { + mg.Spec.ProviderConfigReference = r +} + +// SetWriteConnectionSecretToReference of this Grant. +func (mg *Grant) SetWriteConnectionSecretToReference(r *xpv1.LocalSecretReference) { + mg.Spec.WriteConnectionSecretToReference = r +} + +// GetCondition of this User. +func (mg *User) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return mg.Status.GetCondition(ct) +} + +// GetManagementPolicies of this User. +func (mg *User) GetManagementPolicies() xpv1.ManagementPolicies { + return mg.Spec.ManagementPolicies +} + +// GetProviderConfigReference of this User. +func (mg *User) GetProviderConfigReference() *xpv1.ProviderConfigReference { + return mg.Spec.ProviderConfigReference +} + +// GetWriteConnectionSecretToReference of this User. +func (mg *User) GetWriteConnectionSecretToReference() *xpv1.LocalSecretReference { + return mg.Spec.WriteConnectionSecretToReference +} + +// SetConditions of this User. +func (mg *User) SetConditions(c ...xpv1.Condition) { + mg.Status.SetConditions(c...) +} + +// SetManagementPolicies of this User. +func (mg *User) SetManagementPolicies(r xpv1.ManagementPolicies) { + mg.Spec.ManagementPolicies = r +} + +// SetProviderConfigReference of this User. +func (mg *User) SetProviderConfigReference(r *xpv1.ProviderConfigReference) { + mg.Spec.ProviderConfigReference = r +} + +// SetWriteConnectionSecretToReference of this User. +func (mg *User) SetWriteConnectionSecretToReference(r *xpv1.LocalSecretReference) { + mg.Spec.WriteConnectionSecretToReference = r +} diff --git a/apis/namespaced/mysql/v1alpha1/zz_generated.managedlist.go b/apis/namespaced/mysql/v1alpha1/zz_generated.managedlist.go new file mode 100644 index 00000000..258aaf8b --- /dev/null +++ b/apis/namespaced/mysql/v1alpha1/zz_generated.managedlist.go @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +import resource "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + +// GetItems of this DatabaseList. +func (l *DatabaseList) GetItems() []resource.Managed { + items := make([]resource.Managed, len(l.Items)) + for i := range l.Items { + items[i] = &l.Items[i] + } + return items +} + +// GetItems of this GrantList. +func (l *GrantList) GetItems() []resource.Managed { + items := make([]resource.Managed, len(l.Items)) + for i := range l.Items { + items[i] = &l.Items[i] + } + return items +} + +// GetItems of this UserList. +func (l *UserList) 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/mysql/v1alpha1/zz_generated.pc.go b/apis/namespaced/mysql/v1alpha1/zz_generated.pc.go new file mode 100644 index 00000000..1db11335 --- /dev/null +++ b/apis/namespaced/mysql/v1alpha1/zz_generated.pc.go @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +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/mysql/v1alpha1/zz_generated.pcu.go b/apis/namespaced/mysql/v1alpha1/zz_generated.pcu.go new file mode 100644 index 00000000..dfbdb116 --- /dev/null +++ b/apis/namespaced/mysql/v1alpha1/zz_generated.pcu.go @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +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/mysql/v1alpha1/zz_generated.pculist.go b/apis/namespaced/mysql/v1alpha1/zz_generated.pculist.go new file mode 100644 index 00000000..13fa5547 --- /dev/null +++ b/apis/namespaced/mysql/v1alpha1/zz_generated.pculist.go @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +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/namespaced/mysql/v1alpha1/zz_generated.resolvers.go b/apis/namespaced/mysql/v1alpha1/zz_generated.resolvers.go new file mode 100644 index 00000000..78bb2c5c --- /dev/null +++ b/apis/namespaced/mysql/v1alpha1/zz_generated.resolvers.go @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + reference "github.com/crossplane/crossplane-runtime/v2/pkg/reference" + errors "github.com/pkg/errors" + client "sigs.k8s.io/controller-runtime/pkg/client" +) + +// ResolveReferences of this Grant. +func (mg *Grant) ResolveReferences(ctx context.Context, c client.Reader) error { + r := reference.NewAPINamespacedResolver(c, mg) + + var rsp reference.NamespacedResolutionResponse + var err error + + rsp, err = r.Resolve(ctx, reference.NamespacedResolutionRequest{ + CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.User), + Extract: reference.ExternalName(), + Namespace: mg.GetNamespace(), + Reference: mg.Spec.ForProvider.UserRef, + Selector: mg.Spec.ForProvider.UserSelector, + To: reference.To{ + List: &UserList{}, + Managed: &User{}, + }, + }) + if err != nil { + return errors.Wrap(err, "mg.Spec.ForProvider.User") + } + mg.Spec.ForProvider.User = reference.ToPtrValue(rsp.ResolvedValue) + mg.Spec.ForProvider.UserRef = rsp.ResolvedReference + + rsp, err = r.Resolve(ctx, reference.NamespacedResolutionRequest{ + CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.Database), + Extract: reference.ExternalName(), + Namespace: mg.GetNamespace(), + Reference: mg.Spec.ForProvider.DatabaseRef, + Selector: mg.Spec.ForProvider.DatabaseSelector, + To: reference.To{ + List: &DatabaseList{}, + Managed: &Database{}, + }, + }) + if err != nil { + return errors.Wrap(err, "mg.Spec.ForProvider.Database") + } + mg.Spec.ForProvider.Database = reference.ToPtrValue(rsp.ResolvedValue) + mg.Spec.ForProvider.DatabaseRef = rsp.ResolvedReference + + return nil +} diff --git a/apis/namespaced/postgresql/v1alpha1/cluster_provider_types.go b/apis/namespaced/postgresql/v1alpha1/cluster_provider_types.go new file mode 100644 index 00000000..4370c955 --- /dev/null +++ b/apis/namespaced/postgresql/v1alpha1/cluster_provider_types.go @@ -0,0 +1,80 @@ +/* +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 v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" +) + +// A ClusterProviderConfigSpec defines the desired state of a ClusterProviderConfig. +type ClusterProviderConfigSpec struct { + // Credentials required to authenticate to this provider. + Credentials ClusterProviderCredentials `json:"credentials"` + // Defines the database name used to set up a connection to the provided + // PostgreSQL instance. Same as PGDATABASE environment variable. + // +kubebuilder:default="postgres" + DefaultDatabase string `json:"defaultDatabase,omitempty"` + // Defines the SSL mode used to set up a connection to the provided + // PostgreSQL instance + // +kubebuilder:validation:Enum=disable;require;verify-ca;verify-full + // +kubebuilder:default=verify-full + // +kubebuilder:validation:Optional + SSLMode *string `json:"sslMode,omitempty"` +} + +// ClusterProviderCredentials required to authenticate. +type ClusterProviderCredentials struct { + // Source of the provider credentials. + // +kubebuilder:validation:Enum=PostgreSQLConnectionSecret + Source PostgreSQLConnectionSource `json:"source"` + + // A CredentialsSecretRef is a reference to a PostgreSQL connection secret + // that contains the credentials that must be used to connect to the + // provider. +optional + ConnectionSecretRef xpv1.SecretReference `json:"connectionSecretRef,omitempty"` +} + +// A ClusterProviderConfigStatus reflects the observed state of a ClusterProviderConfig. +type ClusterProviderConfigStatus struct { + xpv1.ProviderConfigStatus `json:",inline"` +} + +// +kubebuilder:object:root=true + +// A ClusterProviderConfig configures a Template provider. +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:printcolumn:name="SECRET-NAME",type="string",JSONPath=".spec.credentials.connectionSecretRef.name",priority=1 +// +kubebuilder:resource:scope=Cluster,categories={crossplane,provider,sql} +type ClusterProviderConfig struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ClusterProviderConfigSpec `json:"spec"` + Status ClusterProviderConfigStatus `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/postgresql/v1alpha1/database_types.go b/apis/namespaced/postgresql/v1alpha1/database_types.go new file mode 100644 index 00000000..a412349f --- /dev/null +++ b/apis/namespaced/postgresql/v1alpha1/database_types.go @@ -0,0 +1,113 @@ +/* +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 v1alpha1 + +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" +) + +// DatabaseParameters are the configurable fields of a Database. +type DatabaseParameters struct { + // The role name of the user who will own the new database, or DEFAULT to + // use the default (namely, the user executing the command). To create a + // database owned by another role, you must be a direct or indirect member + // of that role, or be a superuser. + Owner *string `json:"owner,omitempty"` + + // The name of the template from which to create the new database, or + // DEFAULT to use the default template (template1). + Template *string `json:"template,omitempty"` + + // Character set encoding to use in the new database. Specify a string + // constant (e.g., 'SQL_ASCII'), or an integer encoding number, or DEFAULT + // to use the default encoding (namely, the encoding of the template + // database). The character sets supported by the PostgreSQL server are + // described in Section 23.3.1. See below for additional restrictions. + Encoding *string `json:"encoding,omitempty"` + + // Collation order (LC_COLLATE) to use in the new database. This affects the + // sort order applied to strings, e.g. in queries with ORDER BY, as well as + // the order used in indexes on text columns. The default is to use the + // collation order of the template database. See below for additional + // restrictions. + LCCollate *string `json:"lcCollate,omitempty"` + + // Character classification (LC_CTYPE) to use in the new database. This + // affects the categorization of characters, e.g. lower, upper and digit. + // The default is to use the character classification of the template + // database. See below for additional restrictions. + LCCType *string `json:"lcCType,omitempty"` + + // The name of the tablespace that will be associated with the new database, + // or DEFAULT to use the template database's tablespace. This tablespace + // will be the default tablespace used for objects created in this database. + // See CREATE TABLESPACE for more information. + Tablespace *string `json:"tablespace,omitempty"` + + // If false then no one can connect to this database. The default is true, + // allowing connections (except as restricted by other mechanisms, such as + // GRANT/REVOKE CONNECT). + AllowConnections *bool `json:"allowConnections,omitempty"` + + // How many concurrent connections can be made to this database. -1 (the + // default) means no limit. + ConnectionLimit *int `json:"connectionLimit,omitempty"` + + // If true, then this database can be cloned by any user with CREATEDB + // privileges; if false (the default), then only superusers or the owner of + // the database can clone it. + IsTemplate *bool `json:"isTemplate,omitempty"` +} + +// A DatabaseSpec defines the desired state of a Database. +type DatabaseSpec struct { + xpv2.ManagedResourceSpec `json:",inline"` + ForProvider DatabaseParameters `json:"forProvider"` +} + +// A DatabaseStatus represents the observed state of a Database. +type DatabaseStatus struct { + xpv1.ResourceStatus `json:",inline"` +} + +// +kubebuilder:object:root=true + +// A Database represents the declarative state of a PostgreSQL database. +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +// +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status" +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:resource:scope=Namespaced,categories={crossplane,managed,sql} +type Database struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec DatabaseSpec `json:"spec"` + Status DatabaseStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// DatabaseList contains a list of Database +type DatabaseList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Database `json:"items"` +} diff --git a/apis/mysql/v1alpha1/zz_generated.pculist.go b/apis/namespaced/postgresql/v1alpha1/doc.go similarity index 57% rename from apis/mysql/v1alpha1/zz_generated.pculist.go rename to apis/namespaced/postgresql/v1alpha1/doc.go index 5f8fdede..ad3666c3 100644 --- a/apis/mysql/v1alpha1/zz_generated.pculist.go +++ b/apis/namespaced/postgresql/v1alpha1/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Crossplane Authors. +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. @@ -13,17 +13,9 @@ 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 v1alpha1 contains the core resources of the SQL provider. +// +kubebuilder:object:generate=true +// +groupName=postgresql.sql.m.crossplane.io +// +versionName=v1alpha1 package v1alpha1 - -import resource "github.com/crossplane/crossplane-runtime/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/postgresql/v1alpha1/extension_types.go b/apis/namespaced/postgresql/v1alpha1/extension_types.go similarity index 87% rename from apis/postgresql/v1alpha1/extension_types.go rename to apis/namespaced/postgresql/v1alpha1/extension_types.go index c6aab189..68d91ba1 100644 --- a/apis/postgresql/v1alpha1/extension_types.go +++ b/apis/namespaced/postgresql/v1alpha1/extension_types.go @@ -20,11 +20,12 @@ import ( "context" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" + client "sigs.k8s.io/controller-runtime/pkg/client" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - "github.com/crossplane/crossplane-runtime/pkg/reference" - "github.com/pkg/errors" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpv2 "github.com/crossplane/crossplane-runtime/v2/apis/common/v2" + reference "github.com/crossplane/crossplane-runtime/v2/pkg/reference" + errors "github.com/pkg/errors" ) // ExtensionParameters are the configurable fields of a Extension. @@ -43,6 +44,7 @@ type ExtensionParameters struct { // Database for extension install. // +optional + // +crossplane:generate:reference:type=Database Database *string `json:"database,omitempty"` // DatabaseRef references the database object this extension is for. @@ -58,8 +60,8 @@ type ExtensionParameters struct { // ExtensionSpec defines the desired state of an Extension. type ExtensionSpec struct { - xpv1.ResourceSpec `json:",inline"` - ForProvider ExtensionParameters `json:"forProvider"` + xpv2.ManagedResourceSpec `json:",inline"` + ForProvider ExtensionParameters `json:"forProvider"` } // A ExtensionStatus represents the observed state of a Extension. @@ -77,7 +79,7 @@ type ExtensionStatus struct { // +kubebuilder:printcolumn:name="EXTENSION",type="string",JSONPath=".spec.forProvider.extension" // +kubebuilder:printcolumn:name="VERSION",type="string",JSONPath=".spec.forProvider.version" // +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" -// +kubebuilder:resource:scope=Cluster,categories={crossplane,managed,sql} +// +kubebuilder:resource:scope=Namespaced,categories={crossplane,managed,sql} type Extension struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/apis/namespaced/postgresql/v1alpha1/grant_types.go b/apis/namespaced/postgresql/v1alpha1/grant_types.go new file mode 100644 index 00000000..f83eab20 --- /dev/null +++ b/apis/namespaced/postgresql/v1alpha1/grant_types.go @@ -0,0 +1,194 @@ +/* +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 v1alpha1 + +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 GrantSpec defines the desired state of a Grant. +type GrantSpec struct { + xpv2.ManagedResourceSpec `json:",inline"` + ForProvider GrantParameters `json:"forProvider"` +} + +// GrantPrivilege represents a privilege to be granted +// +kubebuilder:validation:Pattern:=^[A-Z]+$ +type GrantPrivilege string + +// If Privileges are specified, we should have at least one + +// GrantPrivileges is a list of the privileges to be granted +// +kubebuilder:validation:MinItems:=1 +type GrantPrivileges []GrantPrivilege + +// Some privileges are shorthands for multiple privileges. These translations +// happen internally inside postgresql when making grants. When we query the +// privileges back, we need to look for the expanded set. +// https://www.postgresql.org/docs/15/ddl-priv.html +var grantReplacements = map[GrantPrivilege]GrantPrivileges{ + "ALL": {"CREATE", "TEMPORARY", "CONNECT"}, + "ALL PRIVILEGES": {"CREATE", "TEMPORARY", "CONNECT"}, + "TEMP": {"TEMPORARY"}, +} + +// ExpandPrivileges expands any shorthand privileges to their full equivalents. +func (gp *GrantPrivileges) ExpandPrivileges() GrantPrivileges { + privilegeSet := make(map[GrantPrivilege]struct{}) + + // Replace any shorthand privileges with their full equivalents + for _, p := range *gp { + if _, ok := grantReplacements[p]; ok { + for _, rp := range grantReplacements[p] { + privilegeSet[rp] = struct{}{} + } + } else { + privilegeSet[p] = struct{}{} + } + } + + privileges := make([]GrantPrivilege, 0, len(privilegeSet)) + for p := range privilegeSet { + privileges = append(privileges, p) + } + + return privileges +} + +// ToStringSlice converts the slice of privileges to strings +func (gp *GrantPrivileges) ToStringSlice() []string { + if gp == nil { + return []string{} + } + out := make([]string, len(*gp)) + for i, v := range *gp { + out[i] = string(v) + } + return out +} + +// GrantOption represents an OPTION that will be applied to a grant. +// This modifies the behaviour of the grant depending on the type of +// grant and option applied. +type GrantOption string + +// The possible values for grant option type. +const ( + GrantOptionAdmin GrantOption = "ADMIN" + GrantOptionGrant GrantOption = "GRANT" +) + +// GrantParameters define the desired state of a PostgreSQL grant instance. +type GrantParameters struct { + // Privileges to be granted. + // See https://www.postgresql.org/docs/current/sql-grant.html for available privileges. + // +optional + Privileges GrantPrivileges `json:"privileges,omitempty"` + + // WithOption allows an option to be set on the grant. + // See https://www.postgresql.org/docs/current/sql-grant.html for available + // options for each grant type, and the effects of applying the option. + // +kubebuilder:validation:Enum=ADMIN;GRANT + // +optional + WithOption *GrantOption `json:"withOption,omitempty"` + + // Role this grant is for. + // +optional + // +crossplane:generate:reference:type=Role + Role *string `json:"role,omitempty"` + + // RoleRef references the role object this grant is for. + // +immutable + // +optional + RoleRef *xpv1.NamespacedReference `json:"roleRef,omitempty"` + + // RoleSelector selects a reference to a Role this grant is for. + // +immutable + // +optional + RoleSelector *xpv1.NamespacedSelector `json:"roleSelector,omitempty"` + + // Database this grant is for. + // +optional + // +crossplane:generate:reference:type=Database + Database *string `json:"database,omitempty"` + + // DatabaseRef references the database object this grant it for. + // +immutable + // +optional + DatabaseRef *xpv1.NamespacedReference `json:"databaseRef,omitempty"` + + // DatabaseSelector selects a reference to a Database this grant is for. + // +immutable + // +optional + DatabaseSelector *xpv1.NamespacedSelector `json:"databaseSelector,omitempty"` + + // MemberOf is the Role that this grant makes Role a member of. + // +optional + // +crossplane:generate:reference:type=Role + MemberOf *string `json:"memberOf,omitempty"` + + // MemberOfRef references the Role that this grant makes Role a member of. + // +immutable + // +optional + MemberOfRef *xpv1.NamespacedReference `json:"memberOfRef,omitempty"` + + // MemberOfSelector selects a reference to a Role that this grant makes Role a member of. + // +immutable + // +optional + MemberOfSelector *xpv1.NamespacedSelector `json:"memberOfSelector,omitempty"` + + // RevokePublicOnDb apply the statement "REVOKE ALL ON DATABASE %s FROM PUBLIC" to make database unreachable from public + // +optional + RevokePublicOnDb *bool `json:"revokePublicOnDb,omitempty" default:"false"` +} + +// A GrantStatus represents the observed state of a Grant. +type GrantStatus struct { + xpv1.ResourceStatus `json:",inline"` +} + +// +kubebuilder:object:root=true + +// A Grant represents the declarative state of a PostgreSQL grant. +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +// +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status" +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:printcolumn:name="ROLE",type="string",JSONPath=".spec.forProvider.role" +// +kubebuilder:printcolumn:name="MEMBER OF",type="string",JSONPath=".spec.forProvider.memberOf" +// +kubebuilder:printcolumn:name="DATABASE",type="string",JSONPath=".spec.forProvider.database" +// +kubebuilder:printcolumn:name="PRIVILEGES",type="string",JSONPath=".spec.forProvider.privileges" +// +kubebuilder:resource:scope=Namespaced,categories={crossplane,managed,sql} +type Grant struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec GrantSpec `json:"spec"` + Status GrantStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// GrantList contains a list of Grant +type GrantList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Grant `json:"items"` +} diff --git a/apis/namespaced/postgresql/v1alpha1/provider_types.go b/apis/namespaced/postgresql/v1alpha1/provider_types.go new file mode 100644 index 00000000..7af9e065 --- /dev/null +++ b/apis/namespaced/postgresql/v1alpha1/provider_types.go @@ -0,0 +1,114 @@ +/* +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 v1alpha1 + +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. + Credentials ProviderCredentials `json:"credentials"` + // Defines the database name used to set up a connection to the provided + // PostgreSQL instance. Same as PGDATABASE environment variable. + // +kubebuilder:default="postgres" + DefaultDatabase string `json:"defaultDatabase,omitempty"` + // Defines the SSL mode used to set up a connection to the provided + // PostgreSQL instance + // +kubebuilder:validation:Enum=disable;require;verify-ca;verify-full + // +kubebuilder:default=verify-full + // +kubebuilder:validation:Optional + SSLMode *string `json:"sslMode,omitempty"` +} + +type PostgreSQLConnectionSource string + +const ( + // CredentialsSourcePostgreSQLConnectionSecret indicates that a provider + // should acquire credentials from a connection secret written by a managed + // resource that represents a PostgreSQL server. + CredentialsSourcePostgreSQLConnectionSecret PostgreSQLConnectionSource = "PostgreSQLConnectionSecret" +) + +// ProviderCredentials required to authenticate. +type ProviderCredentials struct { + // Source of the provider credentials. + // +kubebuilder:validation:Enum=PostgreSQLConnectionSecret + Source PostgreSQLConnectionSource `json:"source"` + + // A CredentialsSecretRef is a reference to a PostgreSQL connection secret + // that contains the credentials that must be used to connect to the + // provider. +optional + ConnectionSecretRef xpv1.LocalSecretReference `json:"connectionSecretRef,omitempty"` +} + +// A ProviderConfigStatus reflects the observed state of a ProviderConfig. +type ProviderConfigStatus struct { + xpv1.ProviderConfigStatus `json:",inline"` +} + +// +kubebuilder:object:root=true + +// A ProviderConfig configures a Template provider. +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:printcolumn:name="SECRET-NAME",type="string",JSONPath=".spec.credentials.connectionSecretRef.name",priority=1 +// +kubebuilder:resource:scope=Namespaced,categories={crossplane,provider,sql} +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,sql} +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"` +} diff --git a/apis/namespaced/postgresql/v1alpha1/register.go b/apis/namespaced/postgresql/v1alpha1/register.go new file mode 100644 index 00000000..e11d0bd1 --- /dev/null +++ b/apis/namespaced/postgresql/v1alpha1/register.go @@ -0,0 +1,115 @@ +/* +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 v1alpha1 + +import ( + "reflect" + + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +// Package type metadata. +const ( + Group = "postgresql.sql.m.crossplane.io" + Version = "v1alpha1" +) + +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) +) + +// ClusterProviderConfig type metadata. +var ( + ClusterProviderConfigKind = reflect.TypeOf(ClusterProviderConfig{}).Name() + ClusterProviderConfigGroupKind = schema.GroupKind{Group: Group, Kind: ClusterProviderConfigKind}.String() + ClusterProviderConfigKindAPIVersion = ClusterProviderConfigKind + "." + SchemeGroupVersion.String() + ClusterProviderConfigGroupVersionKind = SchemeGroupVersion.WithKind(ClusterProviderConfigKind) +) + +// Extension type metadata. +var ( + ExtensionKind = reflect.TypeOf(Extension{}).Name() + ExtensionGroupKind = schema.GroupKind{Group: Group, Kind: ExtensionKind}.String() + ExtensionKindAPIVersion = ExtensionKind + "." + SchemeGroupVersion.String() + ExtensionGroupVersionKind = SchemeGroupVersion.WithKind(ExtensionKind) +) + +// Database type metadata. +var ( + DatabaseKind = reflect.TypeOf(Database{}).Name() + DatabaseGroupKind = schema.GroupKind{Group: Group, Kind: DatabaseKind}.String() + DatabaseKindAPIVersion = DatabaseKind + "." + SchemeGroupVersion.String() + DatabaseGroupVersionKind = SchemeGroupVersion.WithKind(DatabaseKind) +) + +// Role type metadata. +var ( + RoleKind = reflect.TypeOf(Role{}).Name() + RoleGroupKind = schema.GroupKind{Group: Group, Kind: RoleKind}.String() + RoleKindAPIVersion = RoleKind + "." + SchemeGroupVersion.String() + RoleGroupVersionKind = SchemeGroupVersion.WithKind(RoleKind) +) + +// Grant type metadata. +var ( + GrantKind = reflect.TypeOf(Grant{}).Name() + GrantGroupKind = schema.GroupKind{Group: Group, Kind: GrantKind}.String() + GrantKindAPIVersion = GrantKind + "." + SchemeGroupVersion.String() + GrantGroupVersionKind = SchemeGroupVersion.WithKind(GrantKind) +) + +// Schema type metadata. +var ( + SchemaKind = reflect.TypeOf(Schema{}).Name() + SchemaGroupKind = schema.GroupKind{Group: Group, Kind: SchemaKind}.String() + SchemaKindAPIVersion = SchemaKind + "." + SchemeGroupVersion.String() + SchemaGroupVersionKind = SchemeGroupVersion.WithKind(SchemaKind) +) + +func init() { + SchemeBuilder.Register(&ClusterProviderConfig{}, &ClusterProviderConfigList{}) + SchemeBuilder.Register(&ProviderConfig{}, &ProviderConfigList{}) + SchemeBuilder.Register(&ProviderConfigUsage{}, &ProviderConfigUsageList{}) + SchemeBuilder.Register(&Database{}, &DatabaseList{}) + SchemeBuilder.Register(&Role{}, &RoleList{}) + SchemeBuilder.Register(&Grant{}, &GrantList{}) + SchemeBuilder.Register(&Extension{}, &ExtensionList{}) + SchemeBuilder.Register(&Schema{}, &SchemaList{}) +} diff --git a/apis/namespaced/postgresql/v1alpha1/role_types.go b/apis/namespaced/postgresql/v1alpha1/role_types.go new file mode 100644 index 00000000..cd5e8afc --- /dev/null +++ b/apis/namespaced/postgresql/v1alpha1/role_types.go @@ -0,0 +1,135 @@ +/* +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 v1alpha1 + +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 RoleSpec defines the desired state of a Role. +type RoleSpec struct { + xpv2.ManagedResourceSpec `json:",inline"` + ForProvider RoleParameters `json:"forProvider"` +} + +// A RoleStatus represents the observed state of a Role. +type RoleStatus struct { + xpv1.ResourceStatus `json:",inline"` + AtProvider RoleObservation `json:"atProvider,omitempty"` +} + +// RolePrivilege is the PostgreSQL identifier to add or remove a permission +// on a role. +// See https://www.postgresql.org/docs/current/sql-createrole.html for available privileges. +type RolePrivilege struct { + // SuperUser grants SUPERUSER privilege when true. + // +optional + SuperUser *bool `json:"superUser,omitempty"` + + // CreateDb grants CREATEDB when true, allowing the role to create databases. + // +optional + CreateDb *bool `json:"createDb,omitempty"` + + // CreateRole grants CREATEROLE when true, allowing this role to create other roles. + // +optional + CreateRole *bool `json:"createRole,omitempty"` + + // Login grants LOGIN when true, allowing the role to login to the server. + // +optional + Login *bool `json:"login,omitempty"` + + // Inherit grants INHERIT when true, allowing the role to inherit permissions + // from other roles it is a member of. + // +optional + Inherit *bool `json:"inherit,omitempty"` + + // Replication grants REPLICATION when true, allowing the role to connect in replication mode. + // +optional + Replication *bool `json:"replication,omitempty"` + + // BypassRls grants BYPASSRLS when true, allowing the role to bypass row-level security policies. + // +optional + BypassRls *bool `json:"bypassRls,omitempty"` +} + +// RoleParameters define the desired state of a PostgreSQL role instance. +type RoleParameters struct { + // ConnectionLimit to be applied to the role. + // +kubebuilder:validation:Min=-1 + // +optional + ConnectionLimit *int32 `json:"connectionLimit,omitempty"` + + // Privileges to be granted. + // +optional + Privileges RolePrivilege `json:"privileges,omitempty"` + + // PasswordSecretRef references the secret that contains the password used + // for this role. If no reference is given, a password will be auto-generated. + // +optional + PasswordSecretRef *xpv1.LocalSecretKeySelector `json:"passwordSecretRef,omitempty"` + + // ConfigurationParameters to be applied to the role. If specified, any other configuration parameters set on the + // role in the database will be reset. + // + // See https://www.postgresql.org/docs/current/runtime-config-client.html for some available configuration parameters. + // +optional + ConfigurationParameters *[]RoleConfigurationParameter `json:"configurationParameters,omitempty"` +} + +// RoleConfigurationParameter is a role configuration parameter. +type RoleConfigurationParameter struct { + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` +} + +// A RoleObservation represents the observed state of a PostgreSQL role. +type RoleObservation struct { + // PrivilegesAsClauses represents the applied privileges state, taking into account + // any defaults applied by Postgres, and expressed as a list of ROLE PRIVILEGE clauses. + PrivilegesAsClauses []string `json:"privilegesAsClauses,omitempty"` + // ConfigurationParameters represents the applied configuration parameters for the PostgreSQL role. + ConfigurationParameters *[]RoleConfigurationParameter `json:"configurationParameters,omitempty"` +} + +// +kubebuilder:object:root=true + +// A Role represents the declarative state of a PostgreSQL role. +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +// +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status" +// +kubebuilder:printcolumn:name="CONN LIMIT",type="integer",JSONPath=".spec.forProvider.connectionLimit" +// +kubebuilder:printcolumn:name="PRIVILEGES",type="string",JSONPath=".status.atProvider.privilegesAsClauses" +// +kubebuilder:resource:scope=Namespaced,categories={crossplane,managed,sql} +type Role struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec RoleSpec `json:"spec"` + Status RoleStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// RoleList contains a list of Role +type RoleList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Role `json:"items"` +} diff --git a/apis/namespaced/postgresql/v1alpha1/schema_types.go b/apis/namespaced/postgresql/v1alpha1/schema_types.go new file mode 100644 index 00000000..393c32b3 --- /dev/null +++ b/apis/namespaced/postgresql/v1alpha1/schema_types.go @@ -0,0 +1,99 @@ +/* +Copyright 2024 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 v1alpha1 + +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 SchemaSpec defines the desired state of a Schema. +type SchemaSpec struct { + xpv2.ManagedResourceSpec `json:",inline"` + ForProvider SchemaParameters `json:"forProvider"` +} + +// SchemaParameters define the desired state of a PostgreSQL schema. +type SchemaParameters struct { + // Role for ownership of this schema. + // +optional + // +crossplane:generate:reference:type=Role + Role *string `json:"role,omitempty"` + + // RoleRef references the role object this schema is for. + // +immutable + // +optional + RoleRef *xpv1.NamespacedReference `json:"roleRef,omitempty"` + + // RoleSelector selects a reference to a Role this schema is for. + // +immutable + // +optional + RoleSelector *xpv1.NamespacedSelector `json:"roleSelector,omitempty"` + + // Database this schema is for. + // +optional + // +crossplane:generate:reference:type=Database + Database *string `json:"database,omitempty"` + + // DatabaseRef references the database object this schema is for. + // +immutable + // +optional + DatabaseRef *xpv1.NamespacedReference `json:"databaseRef,omitempty"` + + // DatabaseSelector selects a reference to a Database this schema is for. + // +immutable + // +optional + DatabaseSelector *xpv1.NamespacedSelector `json:"databaseSelector,omitempty"` + + // RevokePublicOnSchema apply a "REVOKE ALL ON SCHEMA public FROM public" statement + // +optional + RevokePublicOnSchema *bool `json:"revokePublicOnSchema,omitempty" default:"false"` +} + +// A SchemaStatus represents the observed state of a Schema. +type SchemaStatus struct { + xpv1.ResourceStatus `json:",inline"` +} + +// +kubebuilder:object:root=true + +// A Schema represents the declarative state of a PostgreSQL schema. +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +// +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status" +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:printcolumn:name="ROLE",type="string",JSONPath=".spec.forProvider.role" +// +kubebuilder:printcolumn:name="DATABASE",type="string",JSONPath=".spec.forProvider.database" +// +kubebuilder:resource:scope=Namespaced,categories={crossplane,managed,sql} +type Schema struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec SchemaSpec `json:"spec"` + Status SchemaStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// SchemaList contains a list of Schema +type SchemaList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Schema `json:"items"` +} diff --git a/apis/namespaced/postgresql/v1alpha1/zz_generated.deepcopy.go b/apis/namespaced/postgresql/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000..84ca2b7b --- /dev/null +++ b/apis/namespaced/postgresql/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,1130 @@ +//go:build !ignore_autogenerated + +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + runtime "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 *ClusterProviderConfigSpec) DeepCopyInto(out *ClusterProviderConfigSpec) { + *out = *in + in.Credentials.DeepCopyInto(&out.Credentials) + if in.SSLMode != nil { + in, out := &in.SSLMode, &out.SSLMode + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterProviderConfigSpec. +func (in *ClusterProviderConfigSpec) DeepCopy() *ClusterProviderConfigSpec { + if in == nil { + return nil + } + out := new(ClusterProviderConfigSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterProviderConfigStatus) DeepCopyInto(out *ClusterProviderConfigStatus) { + *out = *in + in.ProviderConfigStatus.DeepCopyInto(&out.ProviderConfigStatus) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterProviderConfigStatus. +func (in *ClusterProviderConfigStatus) DeepCopy() *ClusterProviderConfigStatus { + if in == nil { + return nil + } + out := new(ClusterProviderConfigStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterProviderCredentials) DeepCopyInto(out *ClusterProviderCredentials) { + *out = *in + in.ConnectionSecretRef.DeepCopyInto(&out.ConnectionSecretRef) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterProviderCredentials. +func (in *ClusterProviderCredentials) DeepCopy() *ClusterProviderCredentials { + if in == nil { + return nil + } + out := new(ClusterProviderCredentials) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Database) DeepCopyInto(out *Database) { + *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 Database. +func (in *Database) DeepCopy() *Database { + if in == nil { + return nil + } + out := new(Database) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Database) 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 *DatabaseList) DeepCopyInto(out *DatabaseList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Database, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseList. +func (in *DatabaseList) DeepCopy() *DatabaseList { + if in == nil { + return nil + } + out := new(DatabaseList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DatabaseList) 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 *DatabaseParameters) DeepCopyInto(out *DatabaseParameters) { + *out = *in + if in.Owner != nil { + in, out := &in.Owner, &out.Owner + *out = new(string) + **out = **in + } + if in.Template != nil { + in, out := &in.Template, &out.Template + *out = new(string) + **out = **in + } + if in.Encoding != nil { + in, out := &in.Encoding, &out.Encoding + *out = new(string) + **out = **in + } + if in.LCCollate != nil { + in, out := &in.LCCollate, &out.LCCollate + *out = new(string) + **out = **in + } + if in.LCCType != nil { + in, out := &in.LCCType, &out.LCCType + *out = new(string) + **out = **in + } + if in.Tablespace != nil { + in, out := &in.Tablespace, &out.Tablespace + *out = new(string) + **out = **in + } + if in.AllowConnections != nil { + in, out := &in.AllowConnections, &out.AllowConnections + *out = new(bool) + **out = **in + } + if in.ConnectionLimit != nil { + in, out := &in.ConnectionLimit, &out.ConnectionLimit + *out = new(int) + **out = **in + } + if in.IsTemplate != nil { + in, out := &in.IsTemplate, &out.IsTemplate + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseParameters. +func (in *DatabaseParameters) DeepCopy() *DatabaseParameters { + if in == nil { + return nil + } + out := new(DatabaseParameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DatabaseSpec) DeepCopyInto(out *DatabaseSpec) { + *out = *in + in.ManagedResourceSpec.DeepCopyInto(&out.ManagedResourceSpec) + in.ForProvider.DeepCopyInto(&out.ForProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseSpec. +func (in *DatabaseSpec) DeepCopy() *DatabaseSpec { + if in == nil { + return nil + } + out := new(DatabaseSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DatabaseStatus) DeepCopyInto(out *DatabaseStatus) { + *out = *in + in.ResourceStatus.DeepCopyInto(&out.ResourceStatus) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseStatus. +func (in *DatabaseStatus) DeepCopy() *DatabaseStatus { + if in == nil { + return nil + } + out := new(DatabaseStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Extension) DeepCopyInto(out *Extension) { + *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 Extension. +func (in *Extension) DeepCopy() *Extension { + if in == nil { + return nil + } + out := new(Extension) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Extension) 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 *ExtensionList) DeepCopyInto(out *ExtensionList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Extension, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtensionList. +func (in *ExtensionList) DeepCopy() *ExtensionList { + if in == nil { + return nil + } + out := new(ExtensionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ExtensionList) 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 *ExtensionParameters) DeepCopyInto(out *ExtensionParameters) { + *out = *in + if in.Version != nil { + in, out := &in.Version, &out.Version + *out = new(string) + **out = **in + } + if in.Schema != nil { + in, out := &in.Schema, &out.Schema + *out = new(string) + **out = **in + } + if in.Database != nil { + in, out := &in.Database, &out.Database + *out = new(string) + **out = **in + } + if in.DatabaseRef != nil { + in, out := &in.DatabaseRef, &out.DatabaseRef + *out = new(v1.Reference) + (*in).DeepCopyInto(*out) + } + if in.DatabaseSelector != nil { + in, out := &in.DatabaseSelector, &out.DatabaseSelector + *out = new(v1.Selector) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtensionParameters. +func (in *ExtensionParameters) DeepCopy() *ExtensionParameters { + if in == nil { + return nil + } + out := new(ExtensionParameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtensionSpec) DeepCopyInto(out *ExtensionSpec) { + *out = *in + in.ManagedResourceSpec.DeepCopyInto(&out.ManagedResourceSpec) + in.ForProvider.DeepCopyInto(&out.ForProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtensionSpec. +func (in *ExtensionSpec) DeepCopy() *ExtensionSpec { + if in == nil { + return nil + } + out := new(ExtensionSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtensionStatus) DeepCopyInto(out *ExtensionStatus) { + *out = *in + in.ResourceStatus.DeepCopyInto(&out.ResourceStatus) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtensionStatus. +func (in *ExtensionStatus) DeepCopy() *ExtensionStatus { + if in == nil { + return nil + } + out := new(ExtensionStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Grant) DeepCopyInto(out *Grant) { + *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 Grant. +func (in *Grant) DeepCopy() *Grant { + if in == nil { + return nil + } + out := new(Grant) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Grant) 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 *GrantList) DeepCopyInto(out *GrantList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Grant, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrantList. +func (in *GrantList) DeepCopy() *GrantList { + if in == nil { + return nil + } + out := new(GrantList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GrantList) 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 *GrantParameters) DeepCopyInto(out *GrantParameters) { + *out = *in + if in.Privileges != nil { + in, out := &in.Privileges, &out.Privileges + *out = make(GrantPrivileges, len(*in)) + copy(*out, *in) + } + if in.WithOption != nil { + in, out := &in.WithOption, &out.WithOption + *out = new(GrantOption) + **out = **in + } + if in.Role != nil { + in, out := &in.Role, &out.Role + *out = new(string) + **out = **in + } + if in.RoleRef != nil { + in, out := &in.RoleRef, &out.RoleRef + *out = new(v1.NamespacedReference) + (*in).DeepCopyInto(*out) + } + if in.RoleSelector != nil { + in, out := &in.RoleSelector, &out.RoleSelector + *out = new(v1.NamespacedSelector) + (*in).DeepCopyInto(*out) + } + if in.Database != nil { + in, out := &in.Database, &out.Database + *out = new(string) + **out = **in + } + if in.DatabaseRef != nil { + in, out := &in.DatabaseRef, &out.DatabaseRef + *out = new(v1.NamespacedReference) + (*in).DeepCopyInto(*out) + } + if in.DatabaseSelector != nil { + in, out := &in.DatabaseSelector, &out.DatabaseSelector + *out = new(v1.NamespacedSelector) + (*in).DeepCopyInto(*out) + } + if in.MemberOf != nil { + in, out := &in.MemberOf, &out.MemberOf + *out = new(string) + **out = **in + } + if in.MemberOfRef != nil { + in, out := &in.MemberOfRef, &out.MemberOfRef + *out = new(v1.NamespacedReference) + (*in).DeepCopyInto(*out) + } + if in.MemberOfSelector != nil { + in, out := &in.MemberOfSelector, &out.MemberOfSelector + *out = new(v1.NamespacedSelector) + (*in).DeepCopyInto(*out) + } + if in.RevokePublicOnDb != nil { + in, out := &in.RevokePublicOnDb, &out.RevokePublicOnDb + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrantParameters. +func (in *GrantParameters) DeepCopy() *GrantParameters { + if in == nil { + return nil + } + out := new(GrantParameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in GrantPrivileges) DeepCopyInto(out *GrantPrivileges) { + { + in := &in + *out = make(GrantPrivileges, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrantPrivileges. +func (in GrantPrivileges) DeepCopy() GrantPrivileges { + if in == nil { + return nil + } + out := new(GrantPrivileges) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GrantSpec) DeepCopyInto(out *GrantSpec) { + *out = *in + in.ManagedResourceSpec.DeepCopyInto(&out.ManagedResourceSpec) + in.ForProvider.DeepCopyInto(&out.ForProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrantSpec. +func (in *GrantSpec) DeepCopy() *GrantSpec { + if in == nil { + return nil + } + out := new(GrantSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GrantStatus) DeepCopyInto(out *GrantStatus) { + *out = *in + in.ResourceStatus.DeepCopyInto(&out.ResourceStatus) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrantStatus. +func (in *GrantStatus) DeepCopy() *GrantStatus { + if in == nil { + return nil + } + out := new(GrantStatus) + 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 + in.Credentials.DeepCopyInto(&out.Credentials) + if in.SSLMode != nil { + in, out := &in.SSLMode, &out.SSLMode + *out = new(string) + **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.TypedProviderConfigUsage.DeepCopyInto(&out.TypedProviderConfigUsage) +} + +// 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.ConnectionSecretRef.DeepCopyInto(&out.ConnectionSecretRef) +} + +// 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 *Role) DeepCopyInto(out *Role) { + *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 Role. +func (in *Role) DeepCopy() *Role { + if in == nil { + return nil + } + out := new(Role) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Role) 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 *RoleConfigurationParameter) DeepCopyInto(out *RoleConfigurationParameter) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleConfigurationParameter. +func (in *RoleConfigurationParameter) DeepCopy() *RoleConfigurationParameter { + if in == nil { + return nil + } + out := new(RoleConfigurationParameter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleList) DeepCopyInto(out *RoleList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Role, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleList. +func (in *RoleList) DeepCopy() *RoleList { + if in == nil { + return nil + } + out := new(RoleList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RoleList) 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 *RoleObservation) DeepCopyInto(out *RoleObservation) { + *out = *in + if in.PrivilegesAsClauses != nil { + in, out := &in.PrivilegesAsClauses, &out.PrivilegesAsClauses + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ConfigurationParameters != nil { + in, out := &in.ConfigurationParameters, &out.ConfigurationParameters + *out = new([]RoleConfigurationParameter) + if **in != nil { + in, out := *in, *out + *out = make([]RoleConfigurationParameter, len(*in)) + copy(*out, *in) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleObservation. +func (in *RoleObservation) DeepCopy() *RoleObservation { + if in == nil { + return nil + } + out := new(RoleObservation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleParameters) DeepCopyInto(out *RoleParameters) { + *out = *in + if in.ConnectionLimit != nil { + in, out := &in.ConnectionLimit, &out.ConnectionLimit + *out = new(int32) + **out = **in + } + in.Privileges.DeepCopyInto(&out.Privileges) + if in.PasswordSecretRef != nil { + in, out := &in.PasswordSecretRef, &out.PasswordSecretRef + *out = new(v1.LocalSecretKeySelector) + **out = **in + } + if in.ConfigurationParameters != nil { + in, out := &in.ConfigurationParameters, &out.ConfigurationParameters + *out = new([]RoleConfigurationParameter) + if **in != nil { + in, out := *in, *out + *out = make([]RoleConfigurationParameter, len(*in)) + copy(*out, *in) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleParameters. +func (in *RoleParameters) DeepCopy() *RoleParameters { + if in == nil { + return nil + } + out := new(RoleParameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RolePrivilege) DeepCopyInto(out *RolePrivilege) { + *out = *in + if in.SuperUser != nil { + in, out := &in.SuperUser, &out.SuperUser + *out = new(bool) + **out = **in + } + if in.CreateDb != nil { + in, out := &in.CreateDb, &out.CreateDb + *out = new(bool) + **out = **in + } + if in.CreateRole != nil { + in, out := &in.CreateRole, &out.CreateRole + *out = new(bool) + **out = **in + } + if in.Login != nil { + in, out := &in.Login, &out.Login + *out = new(bool) + **out = **in + } + if in.Inherit != nil { + in, out := &in.Inherit, &out.Inherit + *out = new(bool) + **out = **in + } + if in.Replication != nil { + in, out := &in.Replication, &out.Replication + *out = new(bool) + **out = **in + } + if in.BypassRls != nil { + in, out := &in.BypassRls, &out.BypassRls + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RolePrivilege. +func (in *RolePrivilege) DeepCopy() *RolePrivilege { + if in == nil { + return nil + } + out := new(RolePrivilege) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleSpec) DeepCopyInto(out *RoleSpec) { + *out = *in + in.ManagedResourceSpec.DeepCopyInto(&out.ManagedResourceSpec) + in.ForProvider.DeepCopyInto(&out.ForProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleSpec. +func (in *RoleSpec) DeepCopy() *RoleSpec { + if in == nil { + return nil + } + out := new(RoleSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleStatus) DeepCopyInto(out *RoleStatus) { + *out = *in + in.ResourceStatus.DeepCopyInto(&out.ResourceStatus) + in.AtProvider.DeepCopyInto(&out.AtProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleStatus. +func (in *RoleStatus) DeepCopy() *RoleStatus { + if in == nil { + return nil + } + out := new(RoleStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Schema) DeepCopyInto(out *Schema) { + *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 Schema. +func (in *Schema) DeepCopy() *Schema { + if in == nil { + return nil + } + out := new(Schema) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Schema) 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 *SchemaList) DeepCopyInto(out *SchemaList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Schema, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SchemaList. +func (in *SchemaList) DeepCopy() *SchemaList { + if in == nil { + return nil + } + out := new(SchemaList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SchemaList) 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 *SchemaParameters) DeepCopyInto(out *SchemaParameters) { + *out = *in + if in.Role != nil { + in, out := &in.Role, &out.Role + *out = new(string) + **out = **in + } + if in.RoleRef != nil { + in, out := &in.RoleRef, &out.RoleRef + *out = new(v1.NamespacedReference) + (*in).DeepCopyInto(*out) + } + if in.RoleSelector != nil { + in, out := &in.RoleSelector, &out.RoleSelector + *out = new(v1.NamespacedSelector) + (*in).DeepCopyInto(*out) + } + if in.Database != nil { + in, out := &in.Database, &out.Database + *out = new(string) + **out = **in + } + if in.DatabaseRef != nil { + in, out := &in.DatabaseRef, &out.DatabaseRef + *out = new(v1.NamespacedReference) + (*in).DeepCopyInto(*out) + } + if in.DatabaseSelector != nil { + in, out := &in.DatabaseSelector, &out.DatabaseSelector + *out = new(v1.NamespacedSelector) + (*in).DeepCopyInto(*out) + } + if in.RevokePublicOnSchema != nil { + in, out := &in.RevokePublicOnSchema, &out.RevokePublicOnSchema + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SchemaParameters. +func (in *SchemaParameters) DeepCopy() *SchemaParameters { + if in == nil { + return nil + } + out := new(SchemaParameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SchemaSpec) DeepCopyInto(out *SchemaSpec) { + *out = *in + in.ManagedResourceSpec.DeepCopyInto(&out.ManagedResourceSpec) + in.ForProvider.DeepCopyInto(&out.ForProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SchemaSpec. +func (in *SchemaSpec) DeepCopy() *SchemaSpec { + if in == nil { + return nil + } + out := new(SchemaSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SchemaStatus) DeepCopyInto(out *SchemaStatus) { + *out = *in + in.ResourceStatus.DeepCopyInto(&out.ResourceStatus) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SchemaStatus. +func (in *SchemaStatus) DeepCopy() *SchemaStatus { + if in == nil { + return nil + } + out := new(SchemaStatus) + in.DeepCopyInto(out) + return out +} diff --git a/apis/namespaced/postgresql/v1alpha1/zz_generated.managed.go b/apis/namespaced/postgresql/v1alpha1/zz_generated.managed.go new file mode 100644 index 00000000..c2f14eef --- /dev/null +++ b/apis/namespaced/postgresql/v1alpha1/zz_generated.managed.go @@ -0,0 +1,209 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +import xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + +// GetCondition of this Database. +func (mg *Database) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return mg.Status.GetCondition(ct) +} + +// GetManagementPolicies of this Database. +func (mg *Database) GetManagementPolicies() xpv1.ManagementPolicies { + return mg.Spec.ManagementPolicies +} + +// GetProviderConfigReference of this Database. +func (mg *Database) GetProviderConfigReference() *xpv1.ProviderConfigReference { + return mg.Spec.ProviderConfigReference +} + +// GetWriteConnectionSecretToReference of this Database. +func (mg *Database) GetWriteConnectionSecretToReference() *xpv1.LocalSecretReference { + return mg.Spec.WriteConnectionSecretToReference +} + +// SetConditions of this Database. +func (mg *Database) SetConditions(c ...xpv1.Condition) { + mg.Status.SetConditions(c...) +} + +// SetManagementPolicies of this Database. +func (mg *Database) SetManagementPolicies(r xpv1.ManagementPolicies) { + mg.Spec.ManagementPolicies = r +} + +// SetProviderConfigReference of this Database. +func (mg *Database) SetProviderConfigReference(r *xpv1.ProviderConfigReference) { + mg.Spec.ProviderConfigReference = r +} + +// SetWriteConnectionSecretToReference of this Database. +func (mg *Database) SetWriteConnectionSecretToReference(r *xpv1.LocalSecretReference) { + mg.Spec.WriteConnectionSecretToReference = r +} + +// GetCondition of this Extension. +func (mg *Extension) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return mg.Status.GetCondition(ct) +} + +// GetManagementPolicies of this Extension. +func (mg *Extension) GetManagementPolicies() xpv1.ManagementPolicies { + return mg.Spec.ManagementPolicies +} + +// GetProviderConfigReference of this Extension. +func (mg *Extension) GetProviderConfigReference() *xpv1.ProviderConfigReference { + return mg.Spec.ProviderConfigReference +} + +// GetWriteConnectionSecretToReference of this Extension. +func (mg *Extension) GetWriteConnectionSecretToReference() *xpv1.LocalSecretReference { + return mg.Spec.WriteConnectionSecretToReference +} + +// SetConditions of this Extension. +func (mg *Extension) SetConditions(c ...xpv1.Condition) { + mg.Status.SetConditions(c...) +} + +// SetManagementPolicies of this Extension. +func (mg *Extension) SetManagementPolicies(r xpv1.ManagementPolicies) { + mg.Spec.ManagementPolicies = r +} + +// SetProviderConfigReference of this Extension. +func (mg *Extension) SetProviderConfigReference(r *xpv1.ProviderConfigReference) { + mg.Spec.ProviderConfigReference = r +} + +// SetWriteConnectionSecretToReference of this Extension. +func (mg *Extension) SetWriteConnectionSecretToReference(r *xpv1.LocalSecretReference) { + mg.Spec.WriteConnectionSecretToReference = r +} + +// GetCondition of this Grant. +func (mg *Grant) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return mg.Status.GetCondition(ct) +} + +// GetManagementPolicies of this Grant. +func (mg *Grant) GetManagementPolicies() xpv1.ManagementPolicies { + return mg.Spec.ManagementPolicies +} + +// GetProviderConfigReference of this Grant. +func (mg *Grant) GetProviderConfigReference() *xpv1.ProviderConfigReference { + return mg.Spec.ProviderConfigReference +} + +// GetWriteConnectionSecretToReference of this Grant. +func (mg *Grant) GetWriteConnectionSecretToReference() *xpv1.LocalSecretReference { + return mg.Spec.WriteConnectionSecretToReference +} + +// SetConditions of this Grant. +func (mg *Grant) SetConditions(c ...xpv1.Condition) { + mg.Status.SetConditions(c...) +} + +// SetManagementPolicies of this Grant. +func (mg *Grant) SetManagementPolicies(r xpv1.ManagementPolicies) { + mg.Spec.ManagementPolicies = r +} + +// SetProviderConfigReference of this Grant. +func (mg *Grant) SetProviderConfigReference(r *xpv1.ProviderConfigReference) { + mg.Spec.ProviderConfigReference = r +} + +// SetWriteConnectionSecretToReference of this Grant. +func (mg *Grant) SetWriteConnectionSecretToReference(r *xpv1.LocalSecretReference) { + mg.Spec.WriteConnectionSecretToReference = r +} + +// GetCondition of this Role. +func (mg *Role) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return mg.Status.GetCondition(ct) +} + +// GetManagementPolicies of this Role. +func (mg *Role) GetManagementPolicies() xpv1.ManagementPolicies { + return mg.Spec.ManagementPolicies +} + +// GetProviderConfigReference of this Role. +func (mg *Role) GetProviderConfigReference() *xpv1.ProviderConfigReference { + return mg.Spec.ProviderConfigReference +} + +// GetWriteConnectionSecretToReference of this Role. +func (mg *Role) GetWriteConnectionSecretToReference() *xpv1.LocalSecretReference { + return mg.Spec.WriteConnectionSecretToReference +} + +// SetConditions of this Role. +func (mg *Role) SetConditions(c ...xpv1.Condition) { + mg.Status.SetConditions(c...) +} + +// SetManagementPolicies of this Role. +func (mg *Role) SetManagementPolicies(r xpv1.ManagementPolicies) { + mg.Spec.ManagementPolicies = r +} + +// SetProviderConfigReference of this Role. +func (mg *Role) SetProviderConfigReference(r *xpv1.ProviderConfigReference) { + mg.Spec.ProviderConfigReference = r +} + +// SetWriteConnectionSecretToReference of this Role. +func (mg *Role) SetWriteConnectionSecretToReference(r *xpv1.LocalSecretReference) { + mg.Spec.WriteConnectionSecretToReference = r +} + +// GetCondition of this Schema. +func (mg *Schema) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return mg.Status.GetCondition(ct) +} + +// GetManagementPolicies of this Schema. +func (mg *Schema) GetManagementPolicies() xpv1.ManagementPolicies { + return mg.Spec.ManagementPolicies +} + +// GetProviderConfigReference of this Schema. +func (mg *Schema) GetProviderConfigReference() *xpv1.ProviderConfigReference { + return mg.Spec.ProviderConfigReference +} + +// GetWriteConnectionSecretToReference of this Schema. +func (mg *Schema) GetWriteConnectionSecretToReference() *xpv1.LocalSecretReference { + return mg.Spec.WriteConnectionSecretToReference +} + +// SetConditions of this Schema. +func (mg *Schema) SetConditions(c ...xpv1.Condition) { + mg.Status.SetConditions(c...) +} + +// SetManagementPolicies of this Schema. +func (mg *Schema) SetManagementPolicies(r xpv1.ManagementPolicies) { + mg.Spec.ManagementPolicies = r +} + +// SetProviderConfigReference of this Schema. +func (mg *Schema) SetProviderConfigReference(r *xpv1.ProviderConfigReference) { + mg.Spec.ProviderConfigReference = r +} + +// SetWriteConnectionSecretToReference of this Schema. +func (mg *Schema) SetWriteConnectionSecretToReference(r *xpv1.LocalSecretReference) { + mg.Spec.WriteConnectionSecretToReference = r +} diff --git a/apis/namespaced/postgresql/v1alpha1/zz_generated.managedlist.go b/apis/namespaced/postgresql/v1alpha1/zz_generated.managedlist.go new file mode 100644 index 00000000..d6d2f04d --- /dev/null +++ b/apis/namespaced/postgresql/v1alpha1/zz_generated.managedlist.go @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +import resource "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + +// GetItems of this DatabaseList. +func (l *DatabaseList) GetItems() []resource.Managed { + items := make([]resource.Managed, len(l.Items)) + for i := range l.Items { + items[i] = &l.Items[i] + } + return items +} + +// GetItems of this ExtensionList. +func (l *ExtensionList) GetItems() []resource.Managed { + items := make([]resource.Managed, len(l.Items)) + for i := range l.Items { + items[i] = &l.Items[i] + } + return items +} + +// GetItems of this GrantList. +func (l *GrantList) GetItems() []resource.Managed { + items := make([]resource.Managed, len(l.Items)) + for i := range l.Items { + items[i] = &l.Items[i] + } + return items +} + +// GetItems of this RoleList. +func (l *RoleList) GetItems() []resource.Managed { + items := make([]resource.Managed, len(l.Items)) + for i := range l.Items { + items[i] = &l.Items[i] + } + return items +} + +// GetItems of this SchemaList. +func (l *SchemaList) 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/postgresql/v1alpha1/zz_generated.pc.go b/apis/namespaced/postgresql/v1alpha1/zz_generated.pc.go new file mode 100644 index 00000000..1db11335 --- /dev/null +++ b/apis/namespaced/postgresql/v1alpha1/zz_generated.pc.go @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +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/postgresql/v1alpha1/zz_generated.pcu.go b/apis/namespaced/postgresql/v1alpha1/zz_generated.pcu.go new file mode 100644 index 00000000..dfbdb116 --- /dev/null +++ b/apis/namespaced/postgresql/v1alpha1/zz_generated.pcu.go @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +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/postgresql/v1alpha1/zz_generated.pculist.go b/apis/namespaced/postgresql/v1alpha1/zz_generated.pculist.go new file mode 100644 index 00000000..13fa5547 --- /dev/null +++ b/apis/namespaced/postgresql/v1alpha1/zz_generated.pculist.go @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +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/namespaced/postgresql/v1alpha1/zz_generated.resolvers.go b/apis/namespaced/postgresql/v1alpha1/zz_generated.resolvers.go new file mode 100644 index 00000000..025da17d --- /dev/null +++ b/apis/namespaced/postgresql/v1alpha1/zz_generated.resolvers.go @@ -0,0 +1,119 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + reference "github.com/crossplane/crossplane-runtime/v2/pkg/reference" + errors "github.com/pkg/errors" + client "sigs.k8s.io/controller-runtime/pkg/client" +) + +// ResolveReferences of this Grant. +func (mg *Grant) ResolveReferences(ctx context.Context, c client.Reader) error { + r := reference.NewAPINamespacedResolver(c, mg) + + var rsp reference.NamespacedResolutionResponse + var err error + + rsp, err = r.Resolve(ctx, reference.NamespacedResolutionRequest{ + CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.Role), + Extract: reference.ExternalName(), + Namespace: mg.GetNamespace(), + Reference: mg.Spec.ForProvider.RoleRef, + Selector: mg.Spec.ForProvider.RoleSelector, + To: reference.To{ + List: &RoleList{}, + Managed: &Role{}, + }, + }) + if err != nil { + return errors.Wrap(err, "mg.Spec.ForProvider.Role") + } + mg.Spec.ForProvider.Role = reference.ToPtrValue(rsp.ResolvedValue) + mg.Spec.ForProvider.RoleRef = rsp.ResolvedReference + + rsp, err = r.Resolve(ctx, reference.NamespacedResolutionRequest{ + CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.Database), + Extract: reference.ExternalName(), + Namespace: mg.GetNamespace(), + Reference: mg.Spec.ForProvider.DatabaseRef, + Selector: mg.Spec.ForProvider.DatabaseSelector, + To: reference.To{ + List: &DatabaseList{}, + Managed: &Database{}, + }, + }) + if err != nil { + return errors.Wrap(err, "mg.Spec.ForProvider.Database") + } + mg.Spec.ForProvider.Database = reference.ToPtrValue(rsp.ResolvedValue) + mg.Spec.ForProvider.DatabaseRef = rsp.ResolvedReference + + rsp, err = r.Resolve(ctx, reference.NamespacedResolutionRequest{ + CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.MemberOf), + Extract: reference.ExternalName(), + Namespace: mg.GetNamespace(), + Reference: mg.Spec.ForProvider.MemberOfRef, + Selector: mg.Spec.ForProvider.MemberOfSelector, + To: reference.To{ + List: &RoleList{}, + Managed: &Role{}, + }, + }) + if err != nil { + return errors.Wrap(err, "mg.Spec.ForProvider.MemberOf") + } + mg.Spec.ForProvider.MemberOf = reference.ToPtrValue(rsp.ResolvedValue) + mg.Spec.ForProvider.MemberOfRef = rsp.ResolvedReference + + return nil +} + +// ResolveReferences of this Schema. +func (mg *Schema) ResolveReferences(ctx context.Context, c client.Reader) error { + r := reference.NewAPINamespacedResolver(c, mg) + + var rsp reference.NamespacedResolutionResponse + var err error + + rsp, err = r.Resolve(ctx, reference.NamespacedResolutionRequest{ + CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.Role), + Extract: reference.ExternalName(), + Namespace: mg.GetNamespace(), + Reference: mg.Spec.ForProvider.RoleRef, + Selector: mg.Spec.ForProvider.RoleSelector, + To: reference.To{ + List: &RoleList{}, + Managed: &Role{}, + }, + }) + if err != nil { + return errors.Wrap(err, "mg.Spec.ForProvider.Role") + } + mg.Spec.ForProvider.Role = reference.ToPtrValue(rsp.ResolvedValue) + mg.Spec.ForProvider.RoleRef = rsp.ResolvedReference + + rsp, err = r.Resolve(ctx, reference.NamespacedResolutionRequest{ + CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.Database), + Extract: reference.ExternalName(), + Namespace: mg.GetNamespace(), + Reference: mg.Spec.ForProvider.DatabaseRef, + Selector: mg.Spec.ForProvider.DatabaseSelector, + To: reference.To{ + List: &DatabaseList{}, + Managed: &Database{}, + }, + }) + if err != nil { + return errors.Wrap(err, "mg.Spec.ForProvider.Database") + } + mg.Spec.ForProvider.Database = reference.ToPtrValue(rsp.ResolvedValue) + mg.Spec.ForProvider.DatabaseRef = rsp.ResolvedReference + + return nil +} diff --git a/apis/postgresql/v1alpha1/zz_generated.pc.go b/apis/postgresql/v1alpha1/zz_generated.pc.go deleted file mode 100644 index 4acf0166..00000000 --- a/apis/postgresql/v1alpha1/zz_generated.pc.go +++ /dev/null @@ -1,40 +0,0 @@ -/* -Copyright 2021 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 v1alpha1 - -import xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - -// 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/postgresql/v1alpha1/zz_generated.resolvers.go b/apis/postgresql/v1alpha1/zz_generated.resolvers.go deleted file mode 100644 index c1200141..00000000 --- a/apis/postgresql/v1alpha1/zz_generated.resolvers.go +++ /dev/null @@ -1,67 +0,0 @@ -/* -Copyright 2021 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 v1alpha1 - -import ( - "context" - reference "github.com/crossplane/crossplane-runtime/pkg/reference" - errors "github.com/pkg/errors" - client "sigs.k8s.io/controller-runtime/pkg/client" -) - -// ResolveReferences of this Schema. -func (mg *Schema) ResolveReferences(ctx context.Context, c client.Reader) error { - r := reference.NewAPIResolver(c, mg) - - var rsp reference.ResolutionResponse - var err error - - rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ - CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.Role), - Extract: reference.ExternalName(), - Reference: mg.Spec.ForProvider.RoleRef, - Selector: mg.Spec.ForProvider.RoleSelector, - To: reference.To{ - List: &RoleList{}, - Managed: &Role{}, - }, - }) - if err != nil { - return errors.Wrap(err, "mg.Spec.ForProvider.Role") - } - mg.Spec.ForProvider.Role = reference.ToPtrValue(rsp.ResolvedValue) - mg.Spec.ForProvider.RoleRef = rsp.ResolvedReference - - rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ - CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.Database), - Extract: reference.ExternalName(), - Reference: mg.Spec.ForProvider.DatabaseRef, - Selector: mg.Spec.ForProvider.DatabaseSelector, - To: reference.To{ - List: &DatabaseList{}, - Managed: &Database{}, - }, - }) - if err != nil { - return errors.Wrap(err, "mg.Spec.ForProvider.Database") - } - mg.Spec.ForProvider.Database = reference.ToPtrValue(rsp.ResolvedValue) - mg.Spec.ForProvider.DatabaseRef = rsp.ResolvedReference - - return nil -} diff --git a/apis/sql.go b/apis/sql.go index 4fdae725..73644ccc 100644 --- a/apis/sql.go +++ b/apis/sql.go @@ -20,17 +20,23 @@ package apis import ( "k8s.io/apimachinery/pkg/runtime" - mssql "github.com/crossplane-contrib/provider-sql/apis/mssql/v1alpha1" - mysql "github.com/crossplane-contrib/provider-sql/apis/mysql/v1alpha1" - postgresql "github.com/crossplane-contrib/provider-sql/apis/postgresql/v1alpha1" + clustermssql "github.com/crossplane-contrib/provider-sql/apis/cluster/mssql/v1alpha1" + clustermysql "github.com/crossplane-contrib/provider-sql/apis/cluster/mysql/v1alpha1" + clusterpostgresql "github.com/crossplane-contrib/provider-sql/apis/cluster/postgresql/v1alpha1" + namespacedmssql "github.com/crossplane-contrib/provider-sql/apis/namespaced/mssql/v1alpha1" + namespacedmysql "github.com/crossplane-contrib/provider-sql/apis/namespaced/mysql/v1alpha1" + namespacedpostgresql "github.com/crossplane-contrib/provider-sql/apis/namespaced/postgresql/v1alpha1" ) func init() { // Register the types with the Scheme so the components can map objects to GroupVersionKinds and back AddToSchemes = append(AddToSchemes, - mssql.SchemeBuilder.AddToScheme, - mysql.SchemeBuilder.AddToScheme, - postgresql.SchemeBuilder.AddToScheme, + clustermssql.SchemeBuilder.AddToScheme, + clustermysql.SchemeBuilder.AddToScheme, + clusterpostgresql.SchemeBuilder.AddToScheme, + namespacedmssql.SchemeBuilder.AddToScheme, + namespacedmysql.SchemeBuilder.AddToScheme, + namespacedpostgresql.SchemeBuilder.AddToScheme, ) } diff --git a/cluster/local/integration_tests.sh b/cluster/local/integration_tests.sh index e8669b78..d9db3c72 100755 --- a/cluster/local/integration_tests.sh +++ b/cluster/local/integration_tests.sh @@ -62,6 +62,9 @@ if [ "$skipcleanup" != true ]; then trap cleanup EXIT fi +# Global variable to control API type +API_TYPE="cluster" + SCRIPT_DIR="$(dirname "$(realpath "$0")")" # shellcheck source="$SCRIPT_DIR/postgresdb_functions.sh" source "$SCRIPT_DIR/postgresdb_functions.sh" @@ -80,10 +83,10 @@ setup_cluster() { echo_step "setting up local package cache" local cache_path="${projectdir}/.work/inttest-package-cache" - mkdir -p "${cache_path}" + mkdir -p "${cache_path}/xpkg.crossplane.io" echo "created cache dir at ${cache_path}" - "${UP}" alpha xpkg xp-extract --from-xpkg "${OUTPUT_DIR}"/xpkg/linux_"${SAFEHOSTARCH}"/"${PACKAGE_NAME}"-"${VERSION}".xpkg -o "${cache_path}/${PACKAGE_NAME}.gz" - chmod 644 "${cache_path}/${PACKAGE_NAME}.gz" + "${UP}" alpha xpkg xp-extract --from-xpkg "${OUTPUT_DIR}"/xpkg/linux_"${SAFEHOSTARCH}"/"${PACKAGE_NAME}"-"${VERSION}".xpkg -o "${cache_path}/xpkg.crossplane.io/${PACKAGE_NAME}:latest.gz" + chmod 644 "${cache_path}/xpkg.crossplane.io/${PACKAGE_NAME}:latest.gz" local node_image="kindest/node:${KIND_NODE_IMAGE_TAG}" echo_step "creating k8s cluster using kind ${KIND_VERSION} and node image ${node_image}" @@ -195,7 +198,7 @@ metadata: spec: runtimeConfigRef: name: debug-config - package: "${PACKAGE_NAME}" + package: "xpkg.crossplane.io/${PACKAGE_NAME}:latest" packagePullPolicy: Never EOF )" @@ -268,9 +271,25 @@ cleanup_tls_certs() { } setup_provider_config_no_tls() { - echo_step "creating ProviderConfig with no TLS" - local yaml="$( cat < /dev/null echo_step_completed echo_step "check if database is ready" -"${KUBECTL}" wait --timeout 2m --for condition=Ready -f ${projectdir}/examples/postgresql/database.yaml +"${KUBECTL}" wait --timeout 2m --for condition=Ready -f ${projectdir}/examples/${API_TYPE}/postgresql/database.yaml > /dev/null echo_step_completed echo_step "check if grant is ready" -"${KUBECTL}" wait --timeout 2m --for condition=Ready -f ${projectdir}/examples/postgresql/grant.yaml +"${KUBECTL}" wait --timeout 2m --for condition=Ready -f ${projectdir}/examples/${API_TYPE}/postgresql/grant.yaml > /dev/null echo_step_completed echo_step "check if schema is ready" -"${KUBECTL}" wait --timeout 2m --for condition=Ready -f ${projectdir}/examples/postgresql/schema.yaml +"${KUBECTL}" wait --timeout 2m --for condition=Ready -f ${projectdir}/examples/${API_TYPE}/postgresql/schema.yaml > /dev/null echo_step_completed } @@ -149,7 +160,7 @@ check_observe_only_database(){ echo_step "check if observe only database is preserved after deletion" # Delete the database kubernetes object, it should not delete the database - kubectl delete database.postgresql.sql.crossplane.io db-observe + "${KUBECTL}" delete database.postgresql.sql.${APIGROUP_SUFFIX}crossplane.io db-observe local datname datname="$(PGPASSWORD="${postgres_root_pw}" psql -h localhost -p 5432 -U postgres -wtAc "SELECT datname FROM pg_database WHERE datname = 'db-observe';")" @@ -171,10 +182,10 @@ check_observe_only_database(){ delete_postgresdb_resources(){ # uninstall echo_step "uninstalling ${PROJECT_NAME}" - "${KUBECTL}" delete -f "${projectdir}/examples/postgresql/grant.yaml" - "${KUBECTL}" delete --ignore-not-found=true -f "${projectdir}/examples/postgresql/database.yaml" - "${KUBECTL}" delete -f "${projectdir}/examples/postgresql/role.yaml" - "${KUBECTL}" delete -f "${projectdir}/examples/postgresql/schema.yaml" + "${KUBECTL}" delete -f "${projectdir}/examples/${API_TYPE}/postgresql/grant.yaml" + "${KUBECTL}" delete --ignore-not-found=true -f "${projectdir}/examples/${API_TYPE}/postgresql/database.yaml" + "${KUBECTL}" delete -f "${projectdir}/examples/${API_TYPE}/postgresql/role.yaml" + "${KUBECTL}" delete -f "${projectdir}/examples/${API_TYPE}/postgresql/schema.yaml" echo "${PROVIDER_CONFIG_POSTGRES_YAML}" | "${KUBECTL}" delete -f - # ----------- cleaning postgres related resources @@ -198,4 +209,4 @@ integration_tests_postgres() { check_all_roles_privileges check_schema_privileges delete_postgresdb_resources -} \ No newline at end of file +} diff --git a/cmd/provider/main.go b/cmd/provider/main.go index 300a4eb6..b01c0848 100644 --- a/cmd/provider/main.go +++ b/cmd/provider/main.go @@ -29,10 +29,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/log/zap" - xpcontroller "github.com/crossplane/crossplane-runtime/pkg/controller" - "github.com/crossplane/crossplane-runtime/pkg/feature" - "github.com/crossplane/crossplane-runtime/pkg/logging" - "github.com/crossplane/crossplane-runtime/pkg/ratelimiter" + xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/feature" + "github.com/crossplane/crossplane-runtime/v2/pkg/logging" + "github.com/crossplane/crossplane-runtime/v2/pkg/ratelimiter" "github.com/crossplane-contrib/provider-sql/apis" "github.com/crossplane-contrib/provider-sql/pkg/controller" diff --git a/examples/cluster/deploymentRuntimeConfig.yaml b/examples/cluster/deploymentRuntimeConfig.yaml new file mode 100644 index 00000000..f8f58ef9 --- /dev/null +++ b/examples/cluster/deploymentRuntimeConfig.yaml @@ -0,0 +1,22 @@ +# This DeploymentRuntimeConfig will override the provider-sql deployment tolerations. +--- +apiVersion: pkg.crossplane.io/v1beta1 +kind: DeploymentRuntimeConfig +metadata: + name: provider-sql +spec: + deploymentTemplate: + spec: + selector: {} + template: + spec: + containers: [] + tolerations: + - effect: NoSchedule + key: node.kubernetes.io/role + operator: Equal + value: crossplane + - effect: NoSchedule + key: node.kubernetes.io/arch + operator: Equal + value: arm64 \ No newline at end of file diff --git a/examples/mssql/config.yaml b/examples/cluster/mssql/config.yaml similarity index 100% rename from examples/mssql/config.yaml rename to examples/cluster/mssql/config.yaml diff --git a/examples/mssql/database.yaml b/examples/cluster/mssql/database.yaml similarity index 100% rename from examples/mssql/database.yaml rename to examples/cluster/mssql/database.yaml diff --git a/examples/mssql/grant.yaml b/examples/cluster/mssql/grant.yaml similarity index 100% rename from examples/mssql/grant.yaml rename to examples/cluster/mssql/grant.yaml diff --git a/examples/mssql/user.yaml b/examples/cluster/mssql/user.yaml similarity index 100% rename from examples/mssql/user.yaml rename to examples/cluster/mssql/user.yaml diff --git a/examples/mysql/config.yaml b/examples/cluster/mysql/config.yaml similarity index 100% rename from examples/mysql/config.yaml rename to examples/cluster/mysql/config.yaml diff --git a/examples/mysql/config_tls.yaml b/examples/cluster/mysql/config_tls.yaml similarity index 100% rename from examples/mysql/config_tls.yaml rename to examples/cluster/mysql/config_tls.yaml diff --git a/examples/mysql/database.yaml b/examples/cluster/mysql/database.yaml similarity index 100% rename from examples/mysql/database.yaml rename to examples/cluster/mysql/database.yaml diff --git a/examples/mysql/grant_all_databases.yaml b/examples/cluster/mysql/grant_all_databases.yaml similarity index 100% rename from examples/mysql/grant_all_databases.yaml rename to examples/cluster/mysql/grant_all_databases.yaml diff --git a/examples/mysql/grant_database.yaml b/examples/cluster/mysql/grant_database.yaml similarity index 100% rename from examples/mysql/grant_database.yaml rename to examples/cluster/mysql/grant_database.yaml diff --git a/examples/mysql/grant_table.yaml b/examples/cluster/mysql/grant_table.yaml similarity index 100% rename from examples/mysql/grant_table.yaml rename to examples/cluster/mysql/grant_table.yaml diff --git a/examples/mysql/user.yaml b/examples/cluster/mysql/user.yaml similarity index 100% rename from examples/mysql/user.yaml rename to examples/cluster/mysql/user.yaml diff --git a/examples/postgresql/config.yaml b/examples/cluster/postgresql/config.yaml similarity index 100% rename from examples/postgresql/config.yaml rename to examples/cluster/postgresql/config.yaml diff --git a/examples/postgresql/database.yaml b/examples/cluster/postgresql/database.yaml similarity index 100% rename from examples/postgresql/database.yaml rename to examples/cluster/postgresql/database.yaml diff --git a/examples/postgresql/extension.yaml b/examples/cluster/postgresql/extension.yaml similarity index 100% rename from examples/postgresql/extension.yaml rename to examples/cluster/postgresql/extension.yaml diff --git a/examples/postgresql/grant.yaml b/examples/cluster/postgresql/grant.yaml similarity index 100% rename from examples/postgresql/grant.yaml rename to examples/cluster/postgresql/grant.yaml diff --git a/examples/postgresql/role.yaml b/examples/cluster/postgresql/role.yaml similarity index 100% rename from examples/postgresql/role.yaml rename to examples/cluster/postgresql/role.yaml diff --git a/examples/postgresql/schema.yaml b/examples/cluster/postgresql/schema.yaml similarity index 100% rename from examples/postgresql/schema.yaml rename to examples/cluster/postgresql/schema.yaml diff --git a/examples/cluster/provider.yaml b/examples/cluster/provider.yaml new file mode 100644 index 00000000..d4dfb5cc --- /dev/null +++ b/examples/cluster/provider.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: provider-sql +spec: + package: xpkg.upbound.io/crossplane-contrib/provider-sql:v0.9.0 + runtimeConfigRef: # Optional + name: provider-sql # This is referencing the DeploymentRuntimeConfig in deploymentRuntimeConfig.yaml file diff --git a/examples/namespaced/deploymentRuntimeConfig.yaml b/examples/namespaced/deploymentRuntimeConfig.yaml new file mode 100644 index 00000000..f8f58ef9 --- /dev/null +++ b/examples/namespaced/deploymentRuntimeConfig.yaml @@ -0,0 +1,22 @@ +# This DeploymentRuntimeConfig will override the provider-sql deployment tolerations. +--- +apiVersion: pkg.crossplane.io/v1beta1 +kind: DeploymentRuntimeConfig +metadata: + name: provider-sql +spec: + deploymentTemplate: + spec: + selector: {} + template: + spec: + containers: [] + tolerations: + - effect: NoSchedule + key: node.kubernetes.io/role + operator: Equal + value: crossplane + - effect: NoSchedule + key: node.kubernetes.io/arch + operator: Equal + value: arm64 \ No newline at end of file diff --git a/examples/namespaced/mssql/config.yaml b/examples/namespaced/mssql/config.yaml new file mode 100644 index 00000000..1f1ea596 --- /dev/null +++ b/examples/namespaced/mssql/config.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: mssql.sql.m.crossplane.io/v1alpha1 +kind: ProviderConfig +metadata: + name: default + namespace: default +spec: + credentials: + source: MSSQLConnectionSecret + connectionSecretRef: + name: db-conn diff --git a/examples/namespaced/mssql/database.yaml b/examples/namespaced/mssql/database.yaml new file mode 100644 index 00000000..e8959c50 --- /dev/null +++ b/examples/namespaced/mssql/database.yaml @@ -0,0 +1,6 @@ +apiVersion: mssql.sql.m.crossplane.io/v1alpha1 +kind: Database +metadata: + name: example-db + namespace: default +spec: {} diff --git a/examples/namespaced/mssql/grant.yaml b/examples/namespaced/mssql/grant.yaml new file mode 100644 index 00000000..b5978ba5 --- /dev/null +++ b/examples/namespaced/mssql/grant.yaml @@ -0,0 +1,18 @@ +apiVersion: mssql.sql.m.crossplane.io/v1alpha1 +kind: Grant +metadata: + name: example-grant + namespace: default +spec: + forProvider: + permissions: + # CONNECT permission is added by default when user created. So, make sure + # to include it unless you are sure that you don't need it. + - CONNECT + - CREATE TABLE + - INSERT + - SELECT + userRef: + name: example-user + databaseRef: + name: example-db diff --git a/examples/namespaced/mssql/user.yaml b/examples/namespaced/mssql/user.yaml new file mode 100644 index 00000000..c7d83528 --- /dev/null +++ b/examples/namespaced/mssql/user.yaml @@ -0,0 +1,14 @@ +apiVersion: mssql.sql.m.crossplane.io/v1alpha1 +kind: User +metadata: + name: example-user + namespace: default +spec: + forProvider: + databaseRef: + name: example-db + passwordSecretRef: + name: example-pw + key: password + writeConnectionSecretToRef: + name: example-connection-secret diff --git a/examples/namespaced/mysql/config.yaml b/examples/namespaced/mysql/config.yaml new file mode 100644 index 00000000..917e8432 --- /dev/null +++ b/examples/namespaced/mysql/config.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: mysql.sql.m.crossplane.io/v1alpha1 +kind: ProviderConfig +metadata: + name: default + namespace: default +spec: + credentials: + source: MySQLConnectionSecret + connectionSecretRef: + name: db-conn + # tls one of preferred(default), skip-verify, true, or custom + tls: preferred diff --git a/examples/namespaced/mysql/config_tls.yaml b/examples/namespaced/mysql/config_tls.yaml new file mode 100644 index 00000000..9552abab --- /dev/null +++ b/examples/namespaced/mysql/config_tls.yaml @@ -0,0 +1,29 @@ +--- +apiVersion: mysql.sql.m.crossplane.io/v1alpha1 +kind: ProviderConfig +metadata: + name: default + namespace: default +spec: + credentials: + source: MySQLConnectionSecret + connectionSecretRef: + name: credentials + # setting tls to custom will configure mysql driver with custom tls configuration specified in tlsConfig + tls: custom + tlsConfig: + caCert: + secretRef: + namespace: default + name: tls-creds + key: ca-cert.pem + clientCert: + secretRef: + namespace: default + name: tls-creds + key: client-cert.pem + clientKey: + secretRef: + namespace: default + name: tls-creds + key: client-key.pem diff --git a/examples/namespaced/mysql/database.yaml b/examples/namespaced/mysql/database.yaml new file mode 100644 index 00000000..5da03965 --- /dev/null +++ b/examples/namespaced/mysql/database.yaml @@ -0,0 +1,10 @@ +apiVersion: mysql.sql.m.crossplane.io/v1alpha1 +kind: Database +metadata: + name: example-db + namespace: default +spec: + forProvider: {} + providerConfigRef: + kind: ProviderConfig + name: default diff --git a/examples/namespaced/mysql/grant_all_databases.yaml b/examples/namespaced/mysql/grant_all_databases.yaml new file mode 100644 index 00000000..efc95c63 --- /dev/null +++ b/examples/namespaced/mysql/grant_all_databases.yaml @@ -0,0 +1,13 @@ +apiVersion: mysql.sql.m.crossplane.io/v1alpha1 +kind: Grant +metadata: + name: example-grant-all-databases + namespace: default +spec: + forProvider: + privileges: + - DROP + - CREATE ROUTINE + - EVENT + userRef: + name: example-user diff --git a/examples/namespaced/mysql/grant_database.yaml b/examples/namespaced/mysql/grant_database.yaml new file mode 100644 index 00000000..219e42a6 --- /dev/null +++ b/examples/namespaced/mysql/grant_database.yaml @@ -0,0 +1,18 @@ +apiVersion: mysql.sql.m.crossplane.io/v1alpha1 +kind: Grant +metadata: + name: example-grant-database + namespace: default +spec: + forProvider: + privileges: + - DROP + - CREATE ROUTINE + - EVENT + userRef: + name: example-user + databaseRef: + name: example-db + providerConfigRef: + kind: ProviderConfig + name: default diff --git a/examples/namespaced/mysql/grant_table.yaml b/examples/namespaced/mysql/grant_table.yaml new file mode 100644 index 00000000..1563e2f0 --- /dev/null +++ b/examples/namespaced/mysql/grant_table.yaml @@ -0,0 +1,16 @@ +apiVersion: mysql.sql.m.crossplane.io/v1alpha1 +kind: Grant +metadata: + name: example-grant-table + namespace: default +spec: + forProvider: + privileges: + - DROP + - INSERT + - SELECT + table: example-table + userRef: + name: example-user + databaseRef: + name: example-db diff --git a/examples/namespaced/mysql/user.yaml b/examples/namespaced/mysql/user.yaml new file mode 100644 index 00000000..617dd7a2 --- /dev/null +++ b/examples/namespaced/mysql/user.yaml @@ -0,0 +1,20 @@ +apiVersion: mysql.sql.m.crossplane.io/v1alpha1 +kind: User +metadata: + name: example-user + namespace: default +spec: + forProvider: + passwordSecretRef: + name: example-pw + key: password + resourceOptions: + maxQueriesPerHour: 1000 + maxUpdatesPerHour: 1000 + maxConnectionsPerHour: 100 + maxUserConnections: 10 + writeConnectionSecretToRef: + name: example-connection-secret + providerConfigRef: + kind: ProviderConfig + name: default diff --git a/examples/namespaced/postgresql/config.yaml b/examples/namespaced/postgresql/config.yaml new file mode 100644 index 00000000..8d38e092 --- /dev/null +++ b/examples/namespaced/postgresql/config.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: postgresql.sql.m.crossplane.io/v1alpha1 +kind: ProviderConfig +metadata: + name: default + namespace: default +spec: + # defaultDatabase is the default database to be used in DSN. + # Similar to PGDATABASE environment variable and defaults to "postgres" + # if not set. + # defaultDatabase: postgres + # sslMode: disable + credentials: + source: PostgreSQLConnectionSecret + connectionSecretRef: + name: db-conn + +# docker run --rm -p 5432:5432 -e POSTGRES_PASSWORD=pass postgres +# --- +# apiVersion: v1 +# kind: Secret +# metadata: +# name: db-conn +# stringData: +# username: postgres +# password: pass +# endpoint: localhost +# port: "5432" diff --git a/examples/namespaced/postgresql/database.yaml b/examples/namespaced/postgresql/database.yaml new file mode 100644 index 00000000..21abda3b --- /dev/null +++ b/examples/namespaced/postgresql/database.yaml @@ -0,0 +1,36 @@ +apiVersion: postgresql.sql.m.crossplane.io/v1alpha1 +kind: Database +metadata: + name: example + namespace: default +spec: + forProvider: {} + providerConfigRef: + kind: ProviderConfig + name: default +--- +apiVersion: postgresql.sql.m.crossplane.io/v1alpha1 +kind: Database +metadata: + name: db1 + namespace: default +spec: + forProvider: + allowConnections: true + owner: "ownerrole" + providerConfigRef: + kind: ProviderConfig + name: default +--- +apiVersion: postgresql.sql.m.crossplane.io/v1alpha1 +kind: Database +metadata: + name: db-observe + namespace: default +spec: + managementPolicies: + - Observe + forProvider: {} + providerConfigRef: + kind: ProviderConfig + name: default diff --git a/examples/namespaced/postgresql/extension.yaml b/examples/namespaced/postgresql/extension.yaml new file mode 100644 index 00000000..e1a112bd --- /dev/null +++ b/examples/namespaced/postgresql/extension.yaml @@ -0,0 +1,29 @@ +apiVersion: postgresql.sql.m.crossplane.io/v1alpha1 +kind: Extension +metadata: + name: hstore-extension-db + namespace: default +spec: + forProvider: + extension: hstore + version: "1.4" + databaseRef: + name: example + providerConfigRef: + kind: ProviderConfig + name: default +--- +apiVersion: postgresql.sql.m.crossplane.io/v1alpha1 +kind: Extension +metadata: + name: ltree-extension-db + namespace: default +spec: + forProvider: + extension: ltree + version: "1.1" + databaseRef: + name: example + providerConfigRef: + kind: ProviderConfig + name: default diff --git a/examples/namespaced/postgresql/grant.yaml b/examples/namespaced/postgresql/grant.yaml new file mode 100644 index 00000000..e346b2b0 --- /dev/null +++ b/examples/namespaced/postgresql/grant.yaml @@ -0,0 +1,84 @@ +apiVersion: postgresql.sql.m.crossplane.io/v1alpha1 +kind: Grant +metadata: + name: example-grant-role-1-on-database + namespace: default +spec: + forProvider: + privileges: + - CREATE + withOption: GRANT + roleRef: + name: example-role + databaseRef: + name: example + providerConfigRef: + kind: ProviderConfig + name: default +--- +apiVersion: postgresql.sql.m.crossplane.io/v1alpha1 +kind: Grant +metadata: + name: example-grant-role-2-on-database + namespace: default +spec: + forProvider: + privileges: + - CONNECT + - TEMPORARY + roleRef: + name: example-role + databaseRef: + name: example + providerConfigRef: + kind: ProviderConfig + name: default +--- +apiVersion: postgresql.sql.m.crossplane.io/v1alpha1 +kind: Grant +metadata: + name: example-grant-role-membership + namespace: default +spec: + forProvider: + withOption: ADMIN + roleRef: + name: example-role + memberOfRef: + name: parent-role + providerConfigRef: + kind: ProviderConfig + name: default +--- +apiVersion: postgresql.sql.m.crossplane.io/v1alpha1 +kind: Grant +metadata: + name: grant-postgres-an-owner-role + namespace: default +spec: + forProvider: + role: "postgres" + memberOfRef: + name: "ownerrole" + providerConfigRef: + kind: ProviderConfig + name: default +--- +apiVersion: postgresql.sql.m.crossplane.io/v1alpha1 +kind: Grant +metadata: + name: grant-owner-role-privilege-connect + namespace: default +spec: + forProvider: + withOption: "GRANT" + privileges: + - CONNECT + roleRef: + name: "ownerrole" + databaseRef: + name: "db1" + revokePublicOnDb: true + providerConfigRef: + kind: ProviderConfig + name: default diff --git a/examples/namespaced/postgresql/role.yaml b/examples/namespaced/postgresql/role.yaml new file mode 100644 index 00000000..6f7057c7 --- /dev/null +++ b/examples/namespaced/postgresql/role.yaml @@ -0,0 +1,53 @@ +apiVersion: postgresql.sql.m.crossplane.io/v1alpha1 +kind: Role +metadata: + name: parent-role + namespace: default +spec: + forProvider: + connectionLimit: 10 + privileges: + login: true + configurationParameters: + - name: 'statement_timeout' + value: '123' + - name: 'search_path' + value: '"$user",public' + writeConnectionSecretToRef: + name: example-parent-role-secret + providerConfigRef: + kind: ProviderConfig + name: default +--- +apiVersion: postgresql.sql.m.crossplane.io/v1alpha1 +kind: Role +metadata: + name: example-role + namespace: default +spec: + forProvider: + privileges: + createDb: true + writeConnectionSecretToRef: + name: example-role-secret + providerConfigRef: + kind: ProviderConfig + name: default +--- +apiVersion: postgresql.sql.m.crossplane.io/v1alpha1 +kind: Role +metadata: + name: ownerrole + namespace: default +spec: + writeConnectionSecretToRef: + name: ownerrole-secret + forProvider: + privileges: + createDb: true + login: true + createRole: true + inherit: true + providerConfigRef: + kind: ProviderConfig + name: default diff --git a/examples/namespaced/postgresql/schema.yaml b/examples/namespaced/postgresql/schema.yaml new file mode 100644 index 00000000..172a9390 --- /dev/null +++ b/examples/namespaced/postgresql/schema.yaml @@ -0,0 +1,30 @@ +apiVersion: postgresql.sql.m.crossplane.io/v1alpha1 +kind: Schema +metadata: + name: my-schema + namespace: default +spec: + forProvider: + databaseRef: + name: example + roleRef: + name: example-role + providerConfigRef: + kind: ProviderConfig + name: default +--- +apiVersion: postgresql.sql.m.crossplane.io/v1alpha1 +kind: Schema +metadata: + name: public + namespace: default +spec: + forProvider: + revokePublicOnSchema: true + databaseRef: + name: db1 + roleRef: + name: ownerrole + providerConfigRef: + kind: ProviderConfig + name: default diff --git a/examples/namespaced/provider.yaml b/examples/namespaced/provider.yaml new file mode 100644 index 00000000..d4dfb5cc --- /dev/null +++ b/examples/namespaced/provider.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: provider-sql +spec: + package: xpkg.upbound.io/crossplane-contrib/provider-sql:v0.9.0 + runtimeConfigRef: # Optional + name: provider-sql # This is referencing the DeploymentRuntimeConfig in deploymentRuntimeConfig.yaml file diff --git a/apis/generate.go b/generate/generate.go similarity index 63% rename from apis/generate.go rename to generate/generate.go index 8dc4f86a..c3a104e0 100644 --- a/apis/generate.go +++ b/generate/generate.go @@ -23,16 +23,26 @@ limitations under the License. // Remove existing CRDs //go:generate rm -rf ../package/crds +// Remove generated Go files +//go:generate bash -c "find ../apis \\( -iname 'zz_generated.conversion_hubs.go' -o -iname 'zz_generated.conversion_spokes.go' -o -iname 'zz_generated.resolvers.go' \\) -delete" +//go:generate bash -c "find ../apis -type d -empty -delete" +//go:generate bash -c "find ../pkg/controller -iname 'zz_*' -delete" +//go:generate bash -c "find ../pkg/controller -type d -empty -delete" +//go:generate bash -c "find ../cmd/provider -name 'zz_*' -type f -delete" +//go:generate bash -c "find ../cmd/provider -type d -maxdepth 1 -mindepth 1 -empty -delete" + // 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:allowDangerousTypes=true,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 _ "github.com/crossplane/crossplane-tools/cmd/angryjet" //nolint:typecheck + + _ "github.com/crossplane/upjet/v2/cmd/scraper" ) diff --git a/go.mod b/go.mod index b10b2a16..bbd23640 100644 --- a/go.mod +++ b/go.mod @@ -1,94 +1,112 @@ module github.com/crossplane-contrib/provider-sql -go 1.23.9 +go 1.24.0 + +toolchain go1.24.5 require ( github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/alecthomas/kingpin/v2 v2.4.0 - github.com/crossplane/crossplane-runtime v1.16.0 - github.com/crossplane/crossplane-tools v0.0.0-20240522174801-1ad3d4c87f21 + github.com/crossplane/crossplane-runtime/v2 v2.0.0 + github.com/crossplane/crossplane-tools v0.0.0-20250731192036-00d407d8b7ec + github.com/crossplane/upjet/v2 v2.0.0 github.com/denisenkom/go-mssqldb v0.11.0 github.com/go-sql-driver/mysql v1.5.0 github.com/google/go-cmp v0.7.0 github.com/lib/pq v1.10.9 github.com/pkg/errors v0.9.1 - k8s.io/api v0.29.1 - k8s.io/apimachinery v0.29.1 - k8s.io/utils v0.0.0-20230726121419-3b25d923346b - sigs.k8s.io/controller-runtime v0.17.0 - sigs.k8s.io/controller-tools v0.14.0 + k8s.io/api v0.33.0 + k8s.io/apimachinery v0.33.0 + k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e + sigs.k8s.io/controller-runtime v0.19.0 + sigs.k8s.io/controller-tools v0.18.0 ) require ( - dario.cat/mergo v1.0.0 // indirect - github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect + dario.cat/mergo v1.0.2 // indirect + github.com/agext/levenshtein v1.2.3 // indirect + github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect + github.com/antchfx/htmlquery v1.2.4 // indirect + github.com/antchfx/xpath v1.2.0 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/dave/jennifer v1.7.0 // indirect - github.com/davecgh/go-spew v1.1.1 // 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/fsnotify/fsnotify v1.7.0 // indirect - github.com/go-logr/logr v1.4.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.12.1 // 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/fsnotify/fsnotify v1.8.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // 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/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // 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-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/google/gnostic-models v0.6.8 // indirect - github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.4.0 // indirect - github.com/imdario/mergo v0.3.16 // indirect + github.com/google/gnostic-models v0.6.9 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/hcl/v2 v2.23.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect 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-wordwrap v1.0.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/afero v1.11.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/afero v1.12.0 // indirect + github.com/spf13/cobra v1.9.1 // indirect + github.com/spf13/pflag v1.0.6 // indirect + github.com/tmccombs/hcl2json v0.3.3 // indirect + github.com/x448/float16 v0.8.4 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect + github.com/yuin/goldmark v1.4.13 // indirect + github.com/zclconf/go-cty v1.16.2 // indirect + go.opentelemetry.io/otel v1.35.0 // indirect + go.opentelemetry.io/otel/trace v1.35.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.26.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/oauth2 v0.15.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 + go.uber.org/zap v1.27.0 // indirect + golang.org/x/crypto v0.41.0 // indirect + golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect + golang.org/x/mod v0.27.0 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/oauth2 v0.29.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/term v0.34.0 // indirect + golang.org/x/text v0.28.0 // indirect + golang.org/x/time v0.11.0 // indirect + golang.org/x/tools v0.36.0 // indirect + golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect + google.golang.org/grpc v1.72.1 // indirect + google.golang.org/protobuf v1.36.6 // 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/apiextensions-apiserver v0.29.1 // indirect - k8s.io/client-go v0.29.1 // indirect - k8s.io/component-base v0.29.1 // indirect - k8s.io/klog/v2 v2.110.1 // indirect - k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect - sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + k8s.io/apiextensions-apiserver v0.33.0 // indirect + k8s.io/client-go v0.33.0 // 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 + 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 8d25a362..9d17c8ed 100644 --- a/go.sum +++ b/go.sum @@ -1,82 +1,109 @@ -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.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= +github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= +github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= 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-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +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/antchfx/htmlquery v1.2.4 h1:qLteofCMe/KGovBI6SQgmou2QNyedFUW+pE+BpeZ494= +github.com/antchfx/htmlquery v1.2.4/go.mod h1:2xO6iu3EVWs7R2JYqBbp8YzG50gj/ofqs5/0VZoDZLc= +github.com/antchfx/xpath v1.2.0 h1:mbwv7co+x0RwgeGAOHdrKy89GvHaGvxxBtPK0uF9Zr8= +github.com/antchfx/xpath v1.2.0/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= +github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= +github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= +github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -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/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +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/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-20240522174801-1ad3d4c87f21 h1:8wb7/zCbVPkeX68WbVESWJmSWQE5SZKzz0g9X4FlXRw= -github.com/crossplane/crossplane-tools v0.0.0-20240522174801-1ad3d4c87f21/go.mod h1:cN0Y7PFGQMM8mcagXVCbeQoKtipmFWQTPZYyziCPBUI= -github.com/dave/jennifer v1.7.0 h1:uRbSBH9UTS64yXbh4FrMHfgfY762RD+C7bUPKODpSJE= -github.com/dave/jennifer v1.7.0/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= +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/crossplane/upjet/v2 v2.0.0 h1:8yC2pFTD3UiYjp+3Cdp8RTXFpM+inznVZm0hSb95mkU= +github.com/crossplane/upjet/v2 v2.0.0/go.mod h1:nXboF68y9ZQRu39kZirQQiPL/BA4Afic17rEdHE+VQo= +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/denisenkom/go-mssqldb v0.11.0 h1:9rHa233rhdOyrz2GcP9NM+gi2psgJZ4GWDpL/7ND8HI= github.com/denisenkom/go-mssqldb v0.11.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -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/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= -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/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/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +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.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +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/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/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.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +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-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -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/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M= +github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8= +github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4= +github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -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/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +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.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -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/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -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/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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/hcl/v2 v2.9.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg= +github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos= +github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -86,6 +113,9 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= +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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -93,6 +123,9 @@ 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 v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= +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/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -102,8 +135,9 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -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/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -115,161 +149,205 @@ 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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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_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/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/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.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/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/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -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/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +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.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +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= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -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/tmccombs/hcl2json v0.3.3 h1:+DLNYqpWE0CsOQiEZu+OZm5ZBImake3wtITYxQ8uLFQ= +github.com/tmccombs/hcl2json v0.3.3/go.mod h1:Y2chtz2x9bAeRTvSibVRVgbLJhLJXKlUeIvjeVdnm4w= +github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +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.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= +github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= +github.com/zclconf/go-cty v1.8.1/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= +github.com/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70= +github.com/zclconf/go-cty v1.16.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= +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/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -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/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/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -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.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= -golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= +golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -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.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -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/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/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -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.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools/go/expect v0.1.0-deprecated h1:jY2C5HGYR5lqex3gEniOQL0r7Dq5+VGVgY1nudX5lXY= +golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= +google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= +google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 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/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= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.29.1 h1:DAjwWX/9YT7NQD4INu49ROJuZAAAP/Ijki48GUPzxqw= -k8s.io/api v0.29.1/go.mod h1:7Kl10vBRUXhnQQI8YR/R327zXC8eJ7887/+Ybta+RoQ= -k8s.io/apiextensions-apiserver v0.29.1 h1:S9xOtyk9M3Sk1tIpQMu9wXHm5O2MX6Y1kIpPMimZBZw= -k8s.io/apiextensions-apiserver v0.29.1/go.mod h1:zZECpujY5yTW58co8V2EQR4BD6A9pktVgHhvc0uLfeU= -k8s.io/apimachinery v0.29.1 h1:KY4/E6km/wLBguvCZv8cKTeOwwOBqFNjwJIdMkMbbRc= -k8s.io/apimachinery v0.29.1/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= -k8s.io/client-go v0.29.1 h1:19B/+2NGEwnFLzt0uB5kNJnfTsbV8w6TgQRz9l7ti7A= -k8s.io/client-go v0.29.1/go.mod h1:TDG/psL9hdet0TI9mGyHJSgRkW3H9JZk2dNEUS7bRks= -k8s.io/component-base v0.29.1 h1:MUimqJPCRnnHsskTTjKD+IC1EHBbRCVyi37IoFBrkYw= -k8s.io/component-base v0.29.1/go.mod h1:fP9GFjxYrLERq1GcWWZAE3bqbNcDKDytn2srWuHTtKc= -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-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.17.0 h1:fjJQf8Ukya+VjogLO6/bNX9HE6Y2xpsO5+fyS26ur/s= -sigs.k8s.io/controller-runtime v0.17.0/go.mod h1:+MngTvIQQQhfXtwfdGw/UOQ/aIaqsYywfCINOtwMO/s= -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= +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= +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/hack/boilerplate.go.txt b/hack/boilerplate.go.txt index 385ea2fb..79d7790b 100644 --- a/hack/boilerplate.go.txt +++ b/hack/boilerplate.go.txt @@ -1,15 +1,3 @@ -/* -Copyright 2021 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. -*/ \ No newline at end of file +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 diff --git a/package/crds/mssql.sql.crossplane.io_databases.yaml b/package/crds/mssql.sql.crossplane.io_databases.yaml index aeec2e59..e32e7ef9 100644 --- a/package/crds/mssql.sql.crossplane.io_databases.yaml +++ b/package/crds/mssql.sql.crossplane.io_databases.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: databases.mssql.sql.crossplane.io spec: group: mssql.sql.crossplane.io @@ -133,93 +133,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/crds/mssql.sql.crossplane.io_grants.yaml b/package/crds/mssql.sql.crossplane.io_grants.yaml index 2c70d591..f8c0c2f7 100644 --- a/package/crds/mssql.sql.crossplane.io_grants.yaml +++ b/package/crds/mssql.sql.crossplane.io_grants.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: grants.mssql.sql.crossplane.io spec: group: mssql.sql.crossplane.io @@ -324,93 +324,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/crds/mssql.sql.crossplane.io_providerconfigs.yaml b/package/crds/mssql.sql.crossplane.io_providerconfigs.yaml index e619aa91..b0d26ace 100644 --- a/package/crds/mssql.sql.crossplane.io_providerconfigs.yaml +++ b/package/crds/mssql.sql.crossplane.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.mssql.sql.crossplane.io spec: group: mssql.sql.crossplane.io diff --git a/package/crds/mssql.sql.crossplane.io_providerconfigusages.yaml b/package/crds/mssql.sql.crossplane.io_providerconfigusages.yaml index 2df71441..09b9b8d6 100644 --- a/package/crds/mssql.sql.crossplane.io_providerconfigusages.yaml +++ b/package/crds/mssql.sql.crossplane.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.mssql.sql.crossplane.io spec: group: mssql.sql.crossplane.io diff --git a/package/crds/mssql.sql.crossplane.io_users.yaml b/package/crds/mssql.sql.crossplane.io_users.yaml index e90fcf9c..065ab745 100644 --- a/package/crds/mssql.sql.crossplane.io_users.yaml +++ b/package/crds/mssql.sql.crossplane.io_users.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: users.mssql.sql.crossplane.io spec: group: mssql.sql.crossplane.io @@ -320,93 +320,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/crds/mssql.sql.m.crossplane.io_clusterproviderconfigs.yaml b/package/crds/mssql.sql.m.crossplane.io_clusterproviderconfigs.yaml new file mode 100644 index 00000000..d58e50bd --- /dev/null +++ b/package/crds/mssql.sql.m.crossplane.io_clusterproviderconfigs.yaml @@ -0,0 +1,145 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: clusterproviderconfigs.mssql.sql.m.crossplane.io +spec: + group: mssql.sql.m.crossplane.io + names: + categories: + - crossplane + - provider + - sql + kind: ClusterProviderConfig + listKind: ClusterProviderConfigList + plural: clusterproviderconfigs + singular: clusterproviderconfig + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + - jsonPath: .spec.credentials.connectionSecretRef.name + name: SECRET-NAME + priority: 1 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: A ClusterProviderConfig configures a SQL 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 ClusterProviderConfigSpec defines the desired state of + a ClusterProviderConfig. + properties: + credentials: + description: Credentials required to authenticate to this provider. + properties: + connectionSecretRef: + description: |- + A CredentialsSecretRef is a reference to a MSSQL connection secret + that contains the credentials that must be used to connect to the + provider. + properties: + name: + description: Name of the secret. + type: string + namespace: + description: Namespace of the secret. + type: string + required: + - name + - namespace + type: object + source: + description: Source of the provider credentials. + enum: + - MSSQLConnectionSecret + type: string + required: + - source + type: object + required: + - credentials + 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/mssql.sql.m.crossplane.io_databases.yaml b/package/crds/mssql.sql.m.crossplane.io_databases.yaml new file mode 100644 index 00000000..adddbb42 --- /dev/null +++ b/package/crds/mssql.sql.m.crossplane.io_databases.yaml @@ -0,0 +1,175 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: databases.mssql.sql.m.crossplane.io +spec: + group: mssql.sql.m.crossplane.io + names: + categories: + - crossplane + - managed + - sql + kind: Database + listKind: DatabaseList + plural: databases + singular: database + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: READY + type: string + - jsonPath: .status.conditions[?(@.type=='Synced')].status + name: SYNCED + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: A Database represents the declarative state of a MSSQL database. + 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 DatabaseSpec defines the desired state of a Database. + properties: + 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 + type: object + status: + description: A DatabaseStatus represents the observed state of a Database. + 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 + 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/mssql.sql.m.crossplane.io_grants.yaml b/package/crds/mssql.sql.m.crossplane.io_grants.yaml new file mode 100644 index 00000000..5b2f8db8 --- /dev/null +++ b/package/crds/mssql.sql.m.crossplane.io_grants.yaml @@ -0,0 +1,380 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: grants.mssql.sql.m.crossplane.io +spec: + group: mssql.sql.m.crossplane.io + names: + categories: + - crossplane + - managed + - sql + kind: Grant + listKind: GrantList + plural: grants + singular: grant + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: READY + type: string + - jsonPath: .status.conditions[?(@.type=='Synced')].status + name: SYNCED + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + - jsonPath: .spec.forProvider.user + name: ROLE + type: string + - jsonPath: .spec.forProvider.database + name: DATABASE + type: string + - jsonPath: .spec.forProvider.schema + name: SCHEMA + type: string + - jsonPath: .spec.forProvider.permissions + name: PERMISSIONS + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: A Grant represents the declarative state of a MSSQL grant. + 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 GrantSpec defines the desired state of a Grant. + properties: + forProvider: + description: GrantParameters define the desired state of a MSSQL grant + instance. + properties: + database: + description: Database this grant is for. + type: string + databaseRef: + description: DatabaseRef references the database object this grant + it for. + properties: + name: + description: Name of the referenced object. + type: string + namespace: + description: Namespace 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 + databaseSelector: + description: DatabaseSelector selects a reference to a Database + this grant is for. + properties: + matchControllerRef: + description: |- + MatchControllerRef ensures an object with the same controller reference + as the selecting object is selected. + type: boolean + matchLabels: + additionalProperties: + type: string + description: MatchLabels ensures an object with matching labels + is selected. + type: object + namespace: + description: Namespace for the selector + type: string + policy: + description: Policies for selection. + 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 + type: object + permissions: + description: |- + Permissions to be granted. + See https://docs.microsoft.com/en-us/sql/t-sql/statements/grant-database-permissions-transact-sql?view=sql-server-ver15#remarks + for available privileges. + items: + description: GrantPermission represents a permission to be granted + pattern: ^[A-Z_ ]+$ + type: string + minItems: 1 + type: array + schema: + description: Schema for the permissions to be granted for. + type: string + user: + description: User this grant is for. + type: string + userRef: + description: UserRef references the user object this grant is + for. + properties: + name: + description: Name of the referenced object. + type: string + namespace: + description: Namespace 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 + userSelector: + description: UserSelector selects a reference to a User this grant + is for. + properties: + matchControllerRef: + description: |- + MatchControllerRef ensures an object with the same controller reference + as the selecting object is selected. + type: boolean + matchLabels: + additionalProperties: + type: string + description: MatchLabels ensures an object with matching labels + is selected. + type: object + namespace: + description: Namespace for the selector + type: string + policy: + description: Policies for selection. + 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 + type: object + required: + - permissions + 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 GrantStatus represents the observed state of a Grant. + 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 + 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/mssql.sql.m.crossplane.io_providerconfigs.yaml b/package/crds/mssql.sql.m.crossplane.io_providerconfigs.yaml new file mode 100644 index 00000000..b7373d48 --- /dev/null +++ b/package/crds/mssql.sql.m.crossplane.io_providerconfigs.yaml @@ -0,0 +1,140 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: providerconfigs.mssql.sql.m.crossplane.io +spec: + group: mssql.sql.m.crossplane.io + names: + categories: + - crossplane + - provider + - sql + kind: ProviderConfig + listKind: ProviderConfigList + plural: providerconfigs + singular: providerconfig + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + - jsonPath: .spec.credentialsSecretRef.name + name: SECRET-NAME + priority: 1 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: A ProviderConfig configures a SQL 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: + credentials: + description: Credentials required to authenticate to this provider. + properties: + connectionSecretRef: + description: |- + A CredentialsSecretRef is a reference to a MSSQL connection secret + that contains the credentials that must be used to connect to the + provider. + properties: + name: + description: Name of the secret. + type: string + required: + - name + type: object + source: + description: Source of the provider credentials. + enum: + - MSSQLConnectionSecret + type: string + required: + - source + type: object + required: + - credentials + 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/mssql.sql.m.crossplane.io_providerconfigusages.yaml b/package/crds/mssql.sql.m.crossplane.io_providerconfigusages.yaml new file mode 100644 index 00000000..97fa4bec --- /dev/null +++ b/package/crds/mssql.sql.m.crossplane.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.mssql.sql.m.crossplane.io +spec: + group: mssql.sql.m.crossplane.io + names: + categories: + - crossplane + - provider + - sql + 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: v1alpha1 + 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/mssql.sql.m.crossplane.io_users.yaml b/package/crds/mssql.sql.m.crossplane.io_users.yaml new file mode 100644 index 00000000..e9d06b04 --- /dev/null +++ b/package/crds/mssql.sql.m.crossplane.io_users.yaml @@ -0,0 +1,371 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: users.mssql.sql.m.crossplane.io +spec: + group: mssql.sql.m.crossplane.io + names: + kind: User + listKind: UserList + plural: users + singular: user + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: READY + type: string + - jsonPath: .status.conditions[?(@.type=='Synced')].status + name: SYNCED + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: A User represents the declarative state of a MSSQL user. + 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 UserSpec defines the desired state of a Database. + properties: + forProvider: + description: UserParameters define the desired state of a MSSQL user + instance. + properties: + database: + description: Database allows you to specify the name of the Database + the USER is created for. + type: string + databaseRef: + description: |- + DatabaseRef allows you to specify custom resource name of the Database the USER is created for. + to fill Database field. + properties: + name: + description: Name of the referenced object. + type: string + namespace: + description: Namespace 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 + databaseSelector: + description: DatabaseSelector allows you to use selector constraints + to select a Database the USER is created for. + properties: + matchControllerRef: + description: |- + MatchControllerRef ensures an object with the same controller reference + as the selecting object is selected. + type: boolean + matchLabels: + additionalProperties: + type: string + description: MatchLabels ensures an object with matching labels + is selected. + type: object + namespace: + description: Namespace for the selector + type: string + policy: + description: Policies for selection. + 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 + type: object + loginDatabase: + description: LoginDatabase allows you to specify the name of the + Database to be used to create the user LOGIN in (normally master). + type: string + loginDatabaseRef: + description: |- + DatabaseRef allows you to specify custom resource name of the Database to be used to create the user LOGIN in (normally master). + to fill Database field. + properties: + name: + description: Name of the referenced object. + type: string + namespace: + description: Namespace 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 + loginDatabaseSelector: + description: DatabaseSelector allows you to use selector constraints + to select a Database to be used to create the user LOGIN in + (normally master). + properties: + matchControllerRef: + description: |- + MatchControllerRef ensures an object with the same controller reference + as the selecting object is selected. + type: boolean + matchLabels: + additionalProperties: + type: string + description: MatchLabels ensures an object with matching labels + is selected. + type: object + namespace: + description: Namespace for the selector + type: string + policy: + description: Policies for selection. + 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 + type: object + passwordSecretRef: + description: |- + PasswordSecretRef references the secret that contains the password used + for this user. If no reference is given, a password will be auto-generated. + properties: + key: + type: string + name: + description: Name of the secret. + type: string + required: + - key + - name + type: object + 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 UserStatus represents the observed state of a User. + properties: + atProvider: + description: A UserObservation represents the observed state of a + MSSQL user. + 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/mysql.sql.crossplane.io_databases.yaml b/package/crds/mysql.sql.crossplane.io_databases.yaml index c868f537..3ce9941d 100644 --- a/package/crds/mysql.sql.crossplane.io_databases.yaml +++ b/package/crds/mysql.sql.crossplane.io_databases.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: databases.mysql.sql.crossplane.io spec: group: mysql.sql.crossplane.io @@ -143,93 +143,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/crds/mysql.sql.crossplane.io_grants.yaml b/package/crds/mysql.sql.crossplane.io_grants.yaml index 1112c8d1..f3e24e06 100644 --- a/package/crds/mysql.sql.crossplane.io_grants.yaml +++ b/package/crds/mysql.sql.crossplane.io_grants.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: grants.mysql.sql.crossplane.io spec: group: mysql.sql.crossplane.io @@ -325,93 +325,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/crds/mysql.sql.crossplane.io_providerconfigs.yaml b/package/crds/mysql.sql.crossplane.io_providerconfigs.yaml index 1e52c0ee..9b1a01c9 100644 --- a/package/crds/mysql.sql.crossplane.io_providerconfigs.yaml +++ b/package/crds/mysql.sql.crossplane.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.mysql.sql.crossplane.io spec: group: mysql.sql.crossplane.io diff --git a/package/crds/mysql.sql.crossplane.io_providerconfigusages.yaml b/package/crds/mysql.sql.crossplane.io_providerconfigusages.yaml index 20b0fd6d..52905092 100644 --- a/package/crds/mysql.sql.crossplane.io_providerconfigusages.yaml +++ b/package/crds/mysql.sql.crossplane.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.mysql.sql.crossplane.io spec: group: mysql.sql.crossplane.io diff --git a/package/crds/mysql.sql.crossplane.io_users.yaml b/package/crds/mysql.sql.crossplane.io_users.yaml index 6a481dd2..788540e1 100644 --- a/package/crds/mysql.sql.crossplane.io_users.yaml +++ b/package/crds/mysql.sql.crossplane.io_users.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: users.mysql.sql.crossplane.io spec: group: mysql.sql.crossplane.io @@ -184,93 +184,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/crds/mysql.sql.m.crossplane.io_clusterproviderconfigs.yaml b/package/crds/mysql.sql.m.crossplane.io_clusterproviderconfigs.yaml new file mode 100644 index 00000000..bd82a4cf --- /dev/null +++ b/package/crds/mysql.sql.m.crossplane.io_clusterproviderconfigs.yaml @@ -0,0 +1,239 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: clusterproviderconfigs.mysql.sql.m.crossplane.io +spec: + group: mysql.sql.m.crossplane.io + names: + categories: + - crossplane + - provider + - sql + kind: ClusterProviderConfig + listKind: ClusterProviderConfigList + plural: clusterproviderconfigs + singular: clusterproviderconfig + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + - jsonPath: .spec.credentials.connectionSecretRef.name + name: SECRET-NAME + priority: 1 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: A ClusterProviderConfig configures a Template 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 ClusterProviderConfigSpec defines the desired state of + a ClusterProviderConfig. + properties: + credentials: + description: Credentials required to authenticate to this provider. + properties: + connectionSecretRef: + description: |- + A CredentialsSecretRef is a reference to a MySQL connection secret + that contains the credentials that must be used to connect to the + provider. +optional + properties: + name: + description: Name of the secret. + type: string + namespace: + description: Namespace of the secret. + type: string + required: + - name + - namespace + type: object + source: + description: Source of the provider credentials. + enum: + - MySQLConnectionSecret + type: string + required: + - source + type: object + tls: + description: |- + tls=true enables TLS / SSL encrypted connection to the server. + Use skip-verify if you want to use a self-signed or invalid certificate (server side) + or use preferred to use TLS only when advertised by the server. This is similar + to skip-verify, but additionally allows a fallback to a connection which is + not encrypted. Neither skip-verify nor preferred add any reliable security. + Alternatively, set tls=custom and provide a custom TLS configuration via the tlsConfig field. + enum: + - "true" + - skip-verify + - preferred + - custom + type: string + tlsConfig: + description: Optional TLS configuration for sql driver. Setting this + field also requires the tls field to be set to custom. + properties: + caCert: + description: TLSSecret defines a reference to a K8s secret and + its specific internal key that contains the TLS cert/keys in + PEM format. + properties: + secretRef: + description: A SecretKeySelector is a reference to a secret + key in an arbitrary namespace. + 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 + type: object + clientCert: + description: TLSSecret defines a reference to a K8s secret and + its specific internal key that contains the TLS cert/keys in + PEM format. + properties: + secretRef: + description: A SecretKeySelector is a reference to a secret + key in an arbitrary namespace. + 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 + type: object + clientKey: + description: TLSSecret defines a reference to a K8s secret and + its specific internal key that contains the TLS cert/keys in + PEM format. + properties: + secretRef: + description: A SecretKeySelector is a reference to a secret + key in an arbitrary namespace. + 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 + type: object + insecureSkipVerify: + type: boolean + type: object + required: + - credentials + type: object + status: + description: A ClusterProviderConfigStatus reflects the observed state + of a ClusterProviderConfig. + 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/mysql.sql.m.crossplane.io_databases.yaml b/package/crds/mysql.sql.m.crossplane.io_databases.yaml new file mode 100644 index 00000000..a15a779b --- /dev/null +++ b/package/crds/mysql.sql.m.crossplane.io_databases.yaml @@ -0,0 +1,185 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: databases.mysql.sql.m.crossplane.io +spec: + group: mysql.sql.m.crossplane.io + names: + categories: + - crossplane + - managed + - sql + kind: Database + listKind: DatabaseList + plural: databases + singular: database + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: READY + type: string + - jsonPath: .status.conditions[?(@.type=='Synced')].status + name: SYNCED + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: A Database represents the declarative state of a MySQL database. + 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 DatabaseSpec defines the desired state of a Database. + properties: + forProvider: + description: DatabaseParameters define the desired state of a MySQL + database instance. + properties: + binlog: + description: BinLog defines whether the create, delete, update + operations of this database are propagated to replicas. Defaults + to true + type: boolean + 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 + type: object + status: + description: A DatabaseStatus represents the observed state of a Database. + 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 + 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/mysql.sql.m.crossplane.io_grants.yaml b/package/crds/mysql.sql.m.crossplane.io_grants.yaml new file mode 100644 index 00000000..ed4d99cf --- /dev/null +++ b/package/crds/mysql.sql.m.crossplane.io_grants.yaml @@ -0,0 +1,389 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: grants.mysql.sql.m.crossplane.io +spec: + group: mysql.sql.m.crossplane.io + names: + categories: + - crossplane + - managed + - sql + kind: Grant + listKind: GrantList + plural: grants + singular: grant + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: READY + type: string + - jsonPath: .status.conditions[?(@.type=='Synced')].status + name: SYNCED + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + - jsonPath: .spec.forProvider.user + name: ROLE + type: string + - jsonPath: .spec.forProvider.database + name: DATABASE + type: string + - jsonPath: .spec.forProvider.privileges + name: PRIVILEGES + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: A Grant represents the declarative state of a MySQL grant. + 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 GrantSpec defines the desired state of a Grant. + properties: + forProvider: + description: GrantParameters define the desired state of a MySQL grant + instance. + properties: + binlog: + description: BinLog defines whether the create, delete, update + operations of this grant are propagated to replicas. Defaults + to true + type: boolean + database: + description: Database this grant is for, default *. + type: string + databaseRef: + description: DatabaseRef references the database object this grant + it for. + properties: + name: + description: Name of the referenced object. + type: string + namespace: + description: Namespace 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 + databaseSelector: + description: DatabaseSelector selects a reference to a Database + this grant is for. + properties: + matchControllerRef: + description: |- + MatchControllerRef ensures an object with the same controller reference + as the selecting object is selected. + type: boolean + matchLabels: + additionalProperties: + type: string + description: MatchLabels ensures an object with matching labels + is selected. + type: object + namespace: + description: Namespace for the selector + type: string + policy: + description: Policies for selection. + 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 + type: object + privileges: + description: |- + Privileges to be granted. + See https://mariadb.com/kb/en/grant/#database-privileges for available privileges. + items: + description: GrantPrivilege represents a privilege to be granted + pattern: ^[A-Z_ ]+$ + type: string + minItems: 1 + type: array + table: + description: Tables this grant is for, default *. + type: string + user: + description: User this grant is for. + type: string + userRef: + description: UserRef references the user object this grant is + for. + properties: + name: + description: Name of the referenced object. + type: string + namespace: + description: Namespace 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 + userSelector: + description: UserSelector selects a reference to a User this grant + is for. + properties: + matchControllerRef: + description: |- + MatchControllerRef ensures an object with the same controller reference + as the selecting object is selected. + type: boolean + matchLabels: + additionalProperties: + type: string + description: MatchLabels ensures an object with matching labels + is selected. + type: object + namespace: + description: Namespace for the selector + type: string + policy: + description: Policies for selection. + 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 + type: object + required: + - privileges + 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 + type: object + status: + description: A GrantStatus represents the observed state of a Grant. + properties: + atProvider: + description: A GrantObservation represents the observed state of a + MySQL grant. + properties: + privileges: + description: Privileges represents the applied privileges + items: + type: string + type: array + 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/mysql.sql.m.crossplane.io_providerconfigs.yaml b/package/crds/mysql.sql.m.crossplane.io_providerconfigs.yaml new file mode 100644 index 00000000..d5d4b83b --- /dev/null +++ b/package/crds/mysql.sql.m.crossplane.io_providerconfigs.yaml @@ -0,0 +1,233 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: providerconfigs.mysql.sql.m.crossplane.io +spec: + group: mysql.sql.m.crossplane.io + names: + categories: + - crossplane + - provider + - sql + kind: ProviderConfig + listKind: ProviderConfigList + plural: providerconfigs + singular: providerconfig + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + - jsonPath: .spec.credentials.connectionSecretRef.name + name: SECRET-NAME + priority: 1 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: A ProviderConfig configures a Template 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: + credentials: + description: Credentials required to authenticate to this provider. + properties: + connectionSecretRef: + description: |- + A CredentialsSecretRef is a reference to a MySQL connection secret + that contains the credentials that must be used to connect to the + provider. +optional + properties: + name: + description: Name of the secret. + type: string + required: + - name + type: object + source: + description: Source of the provider credentials. + enum: + - MySQLConnectionSecret + type: string + required: + - source + type: object + tls: + description: |- + tls=true enables TLS / SSL encrypted connection to the server. + Use skip-verify if you want to use a self-signed or invalid certificate (server side) + or use preferred to use TLS only when advertised by the server. This is similar + to skip-verify, but additionally allows a fallback to a connection which is + not encrypted. Neither skip-verify nor preferred add any reliable security. + Alternatively, set tls=custom and provide a custom TLS configuration via the tlsConfig field. + enum: + - "true" + - skip-verify + - preferred + - custom + type: string + tlsConfig: + description: Optional TLS configuration for sql driver. Setting this + field also requires the tls field to be set to custom. + properties: + caCert: + description: TLSSecret defines a reference to a K8s secret and + its specific internal key that contains the TLS cert/keys in + PEM format. + properties: + secretRef: + description: A SecretKeySelector is a reference to a secret + key in an arbitrary namespace. + 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 + type: object + clientCert: + description: TLSSecret defines a reference to a K8s secret and + its specific internal key that contains the TLS cert/keys in + PEM format. + properties: + secretRef: + description: A SecretKeySelector is a reference to a secret + key in an arbitrary namespace. + 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 + type: object + clientKey: + description: TLSSecret defines a reference to a K8s secret and + its specific internal key that contains the TLS cert/keys in + PEM format. + properties: + secretRef: + description: A SecretKeySelector is a reference to a secret + key in an arbitrary namespace. + 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 + type: object + insecureSkipVerify: + type: boolean + type: object + required: + - credentials + 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/mysql.sql.m.crossplane.io_providerconfigusages.yaml b/package/crds/mysql.sql.m.crossplane.io_providerconfigusages.yaml new file mode 100644 index 00000000..745e33a3 --- /dev/null +++ b/package/crds/mysql.sql.m.crossplane.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.mysql.sql.m.crossplane.io +spec: + group: mysql.sql.m.crossplane.io + names: + categories: + - crossplane + - provider + - sql + 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: v1alpha1 + 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/mysql.sql.m.crossplane.io_users.yaml b/package/crds/mysql.sql.m.crossplane.io_users.yaml new file mode 100644 index 00000000..3aab6808 --- /dev/null +++ b/package/crds/mysql.sql.m.crossplane.io_users.yaml @@ -0,0 +1,234 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: users.mysql.sql.m.crossplane.io +spec: + group: mysql.sql.m.crossplane.io + names: + categories: + - crossplane + - managed + - sql + kind: User + listKind: UserList + plural: users + singular: user + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: READY + type: string + - jsonPath: .status.conditions[?(@.type=='Synced')].status + name: SYNCED + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: A User represents the declarative state of a MySQL user. + 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 UserSpec defines the desired state of a Database. + properties: + forProvider: + description: UserParameters define the desired state of a MySQL user + instance. + properties: + binlog: + description: BinLog defines whether the create, delete, update + operations of this user are propagated to replicas. Defaults + to true + type: boolean + passwordSecretRef: + description: |- + PasswordSecretRef references the secret that contains the password used + for this user. If no reference is given, a password will be auto-generated. + properties: + key: + type: string + name: + description: Name of the secret. + type: string + required: + - key + - name + type: object + resourceOptions: + description: |- + ResourceOptions sets account specific resource limits. + See https://dev.mysql.com/doc/refman/8.0/en/user-resources.html + properties: + maxConnectionsPerHour: + description: MaxConnectionsPerHour sets the number of times + an account can connect to the server per hour + type: integer + maxQueriesPerHour: + description: MaxQueriesPerHour sets the number of queries + an account can issue per hour + type: integer + maxUpdatesPerHour: + description: MaxUpdatesPerHour sets the number of updates + an account can issue per hour + type: integer + maxUserConnections: + description: MaxUserConnections sets The number of simultaneous + connections to the server by an account + type: integer + type: object + 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 UserStatus represents the observed state of a User. + properties: + atProvider: + description: A UserObservation represents the observed state of a + MySQL user. + properties: + resourceOptionsAsClauses: + description: ResourceOptionsAsClauses represents the applied resource + options + items: + type: string + type: array + 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/postgresql.sql.crossplane.io_databases.yaml b/package/crds/postgresql.sql.crossplane.io_databases.yaml index 5054ff4d..25c1364c 100644 --- a/package/crds/postgresql.sql.crossplane.io_databases.yaml +++ b/package/crds/postgresql.sql.crossplane.io_databases.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: databases.postgresql.sql.crossplane.io spec: group: postgresql.sql.crossplane.io @@ -196,93 +196,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/crds/postgresql.sql.crossplane.io_extensions.yaml b/package/crds/postgresql.sql.crossplane.io_extensions.yaml index 7385d655..0a14e1a6 100644 --- a/package/crds/postgresql.sql.crossplane.io_extensions.yaml +++ b/package/crds/postgresql.sql.crossplane.io_extensions.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: extensions.postgresql.sql.crossplane.io spec: group: postgresql.sql.crossplane.io @@ -238,93 +238,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/crds/postgresql.sql.crossplane.io_grants.yaml b/package/crds/postgresql.sql.crossplane.io_grants.yaml index f9849f71..45fbfcb4 100644 --- a/package/crds/postgresql.sql.crossplane.io_grants.yaml +++ b/package/crds/postgresql.sql.crossplane.io_grants.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: grants.postgresql.sql.crossplane.io spec: group: postgresql.sql.crossplane.io @@ -412,93 +412,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/crds/postgresql.sql.crossplane.io_providerconfigs.yaml b/package/crds/postgresql.sql.crossplane.io_providerconfigs.yaml index ea431526..5e0ca7e6 100644 --- a/package/crds/postgresql.sql.crossplane.io_providerconfigs.yaml +++ b/package/crds/postgresql.sql.crossplane.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.postgresql.sql.crossplane.io spec: group: postgresql.sql.crossplane.io diff --git a/package/crds/postgresql.sql.crossplane.io_providerconfigusages.yaml b/package/crds/postgresql.sql.crossplane.io_providerconfigusages.yaml index ff34f36c..091b5dce 100644 --- a/package/crds/postgresql.sql.crossplane.io_providerconfigusages.yaml +++ b/package/crds/postgresql.sql.crossplane.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.postgresql.sql.crossplane.io spec: group: postgresql.sql.crossplane.io diff --git a/package/crds/postgresql.sql.crossplane.io_roles.yaml b/package/crds/postgresql.sql.crossplane.io_roles.yaml index d06b8ce4..1a7a2729 100644 --- a/package/crds/postgresql.sql.crossplane.io_roles.yaml +++ b/package/crds/postgresql.sql.crossplane.io_roles.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: roles.postgresql.sql.crossplane.io spec: group: postgresql.sql.crossplane.io @@ -79,7 +79,6 @@ spec: ConfigurationParameters to be applied to the role. If specified, any other configuration parameters set on the role in the database will be reset. - See https://www.postgresql.org/docs/current/runtime-config-client.html for some available configuration parameters. items: description: RoleConfigurationParameter is a role configuration @@ -213,93 +212,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/crds/postgresql.sql.crossplane.io_schemas.yaml b/package/crds/postgresql.sql.crossplane.io_schemas.yaml index 8b70d7fd..11593c32 100644 --- a/package/crds/postgresql.sql.crossplane.io_schemas.yaml +++ b/package/crds/postgresql.sql.crossplane.io_schemas.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: schemas.postgresql.sql.crossplane.io spec: group: postgresql.sql.crossplane.io @@ -306,93 +306,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/crds/postgresql.sql.m.crossplane.io_clusterproviderconfigs.yaml b/package/crds/postgresql.sql.m.crossplane.io_clusterproviderconfigs.yaml new file mode 100644 index 00000000..e40b7923 --- /dev/null +++ b/package/crds/postgresql.sql.m.crossplane.io_clusterproviderconfigs.yaml @@ -0,0 +1,163 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: clusterproviderconfigs.postgresql.sql.m.crossplane.io +spec: + group: postgresql.sql.m.crossplane.io + names: + categories: + - crossplane + - provider + - sql + kind: ClusterProviderConfig + listKind: ClusterProviderConfigList + plural: clusterproviderconfigs + singular: clusterproviderconfig + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + - jsonPath: .spec.credentials.connectionSecretRef.name + name: SECRET-NAME + priority: 1 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: A ClusterProviderConfig configures a Template 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 ClusterProviderConfigSpec defines the desired state of + a ClusterProviderConfig. + properties: + credentials: + description: Credentials required to authenticate to this provider. + properties: + connectionSecretRef: + description: |- + A CredentialsSecretRef is a reference to a PostgreSQL connection secret + that contains the credentials that must be used to connect to the + provider. +optional + properties: + name: + description: Name of the secret. + type: string + namespace: + description: Namespace of the secret. + type: string + required: + - name + - namespace + type: object + source: + description: Source of the provider credentials. + enum: + - PostgreSQLConnectionSecret + type: string + required: + - source + type: object + defaultDatabase: + default: postgres + description: |- + Defines the database name used to set up a connection to the provided + PostgreSQL instance. Same as PGDATABASE environment variable. + type: string + sslMode: + default: verify-full + description: |- + Defines the SSL mode used to set up a connection to the provided + PostgreSQL instance + enum: + - disable + - require + - verify-ca + - verify-full + type: string + required: + - credentials + type: object + status: + description: A ClusterProviderConfigStatus reflects the observed state + of a ClusterProviderConfig. + 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/postgresql.sql.m.crossplane.io_databases.yaml b/package/crds/postgresql.sql.m.crossplane.io_databases.yaml new file mode 100644 index 00000000..a34551b9 --- /dev/null +++ b/package/crds/postgresql.sql.m.crossplane.io_databases.yaml @@ -0,0 +1,240 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: databases.postgresql.sql.m.crossplane.io +spec: + group: postgresql.sql.m.crossplane.io + names: + categories: + - crossplane + - managed + - sql + kind: Database + listKind: DatabaseList + plural: databases + singular: database + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: READY + type: string + - jsonPath: .status.conditions[?(@.type=='Synced')].status + name: SYNCED + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: A Database represents the declarative state of a PostgreSQL database. + 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 DatabaseSpec defines the desired state of a Database. + properties: + forProvider: + description: DatabaseParameters are the configurable fields of a Database. + properties: + allowConnections: + description: |- + If false then no one can connect to this database. The default is true, + allowing connections (except as restricted by other mechanisms, such as + GRANT/REVOKE CONNECT). + type: boolean + connectionLimit: + description: |- + How many concurrent connections can be made to this database. -1 (the + default) means no limit. + type: integer + encoding: + description: |- + Character set encoding to use in the new database. Specify a string + constant (e.g., 'SQL_ASCII'), or an integer encoding number, or DEFAULT + to use the default encoding (namely, the encoding of the template + database). The character sets supported by the PostgreSQL server are + described in Section 23.3.1. See below for additional restrictions. + type: string + isTemplate: + description: |- + If true, then this database can be cloned by any user with CREATEDB + privileges; if false (the default), then only superusers or the owner of + the database can clone it. + type: boolean + lcCType: + description: |- + Character classification (LC_CTYPE) to use in the new database. This + affects the categorization of characters, e.g. lower, upper and digit. + The default is to use the character classification of the template + database. See below for additional restrictions. + type: string + lcCollate: + description: |- + Collation order (LC_COLLATE) to use in the new database. This affects the + sort order applied to strings, e.g. in queries with ORDER BY, as well as + the order used in indexes on text columns. The default is to use the + collation order of the template database. See below for additional + restrictions. + type: string + owner: + description: |- + The role name of the user who will own the new database, or DEFAULT to + use the default (namely, the user executing the command). To create a + database owned by another role, you must be a direct or indirect member + of that role, or be a superuser. + type: string + tablespace: + description: |- + The name of the tablespace that will be associated with the new database, + or DEFAULT to use the template database's tablespace. This tablespace + will be the default tablespace used for objects created in this database. + See CREATE TABLESPACE for more information. + type: string + template: + description: |- + The name of the template from which to create the new database, or + DEFAULT to use the default template (template1). + type: string + 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 DatabaseStatus represents the observed state of a Database. + 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 + 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/postgresql.sql.m.crossplane.io_extensions.yaml b/package/crds/postgresql.sql.m.crossplane.io_extensions.yaml new file mode 100644 index 00000000..2825d5f6 --- /dev/null +++ b/package/crds/postgresql.sql.m.crossplane.io_extensions.yaml @@ -0,0 +1,282 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: extensions.postgresql.sql.m.crossplane.io +spec: + group: postgresql.sql.m.crossplane.io + names: + categories: + - crossplane + - managed + - sql + kind: Extension + listKind: ExtensionList + plural: extensions + singular: extension + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: READY + type: string + - jsonPath: .status.conditions[?(@.type=='Synced')].status + name: SYNCED + type: string + - jsonPath: .spec.forProvider.database + name: DATABASE + type: string + - jsonPath: .spec.forProvider.extension + name: EXTENSION + type: string + - jsonPath: .spec.forProvider.version + name: VERSION + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: An Extension represents the declarative state of a PostgreSQL + Extension. + 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: ExtensionSpec defines the desired state of an Extension. + properties: + forProvider: + description: ExtensionParameters are the configurable fields of a + Extension. + properties: + database: + description: Database for extension install. + type: string + databaseRef: + description: DatabaseRef references the database object this extension + is for. + 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 + databaseSelector: + description: DatabaseSelector selects a reference to a Database + this extension is for. + properties: + matchControllerRef: + description: |- + MatchControllerRef ensures an object with the same controller reference + as the selecting object is selected. + type: boolean + matchLabels: + additionalProperties: + type: string + description: MatchLabels ensures an object with matching labels + is selected. + type: object + policy: + description: Policies for selection. + 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 + type: object + extension: + description: Extension name to be installed. + type: string + schema: + description: Schema for extension install. + type: string + version: + description: Version of the extension to be installed. + type: string + required: + - extension + 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 ExtensionStatus represents the observed state of a Extension. + 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 + 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/postgresql.sql.m.crossplane.io_grants.yaml b/package/crds/postgresql.sql.m.crossplane.io_grants.yaml new file mode 100644 index 00000000..182444dc --- /dev/null +++ b/package/crds/postgresql.sql.m.crossplane.io_grants.yaml @@ -0,0 +1,474 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: grants.postgresql.sql.m.crossplane.io +spec: + group: postgresql.sql.m.crossplane.io + names: + categories: + - crossplane + - managed + - sql + kind: Grant + listKind: GrantList + plural: grants + singular: grant + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: READY + type: string + - jsonPath: .status.conditions[?(@.type=='Synced')].status + name: SYNCED + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + - jsonPath: .spec.forProvider.role + name: ROLE + type: string + - jsonPath: .spec.forProvider.memberOf + name: MEMBER OF + type: string + - jsonPath: .spec.forProvider.database + name: DATABASE + type: string + - jsonPath: .spec.forProvider.privileges + name: PRIVILEGES + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: A Grant represents the declarative state of a PostgreSQL grant. + 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 GrantSpec defines the desired state of a Grant. + properties: + forProvider: + description: GrantParameters define the desired state of a PostgreSQL + grant instance. + properties: + database: + description: Database this grant is for. + type: string + databaseRef: + description: DatabaseRef references the database object this grant + it for. + properties: + name: + description: Name of the referenced object. + type: string + namespace: + description: Namespace 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 + databaseSelector: + description: DatabaseSelector selects a reference to a Database + this grant is for. + properties: + matchControllerRef: + description: |- + MatchControllerRef ensures an object with the same controller reference + as the selecting object is selected. + type: boolean + matchLabels: + additionalProperties: + type: string + description: MatchLabels ensures an object with matching labels + is selected. + type: object + namespace: + description: Namespace for the selector + type: string + policy: + description: Policies for selection. + 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 + type: object + memberOf: + description: MemberOf is the Role that this grant makes Role a + member of. + type: string + memberOfRef: + description: MemberOfRef references the Role that this grant makes + Role a member of. + properties: + name: + description: Name of the referenced object. + type: string + namespace: + description: Namespace 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 + memberOfSelector: + description: MemberOfSelector selects a reference to a Role that + this grant makes Role a member of. + properties: + matchControllerRef: + description: |- + MatchControllerRef ensures an object with the same controller reference + as the selecting object is selected. + type: boolean + matchLabels: + additionalProperties: + type: string + description: MatchLabels ensures an object with matching labels + is selected. + type: object + namespace: + description: Namespace for the selector + type: string + policy: + description: Policies for selection. + 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 + type: object + privileges: + description: |- + Privileges to be granted. + See https://www.postgresql.org/docs/current/sql-grant.html for available privileges. + items: + description: GrantPrivilege represents a privilege to be granted + pattern: ^[A-Z]+$ + type: string + minItems: 1 + type: array + revokePublicOnDb: + description: RevokePublicOnDb apply the statement "REVOKE ALL + ON DATABASE %s FROM PUBLIC" to make database unreachable from + public + type: boolean + role: + description: Role this grant is for. + type: string + roleRef: + description: RoleRef references the role object this grant is + for. + properties: + name: + description: Name of the referenced object. + type: string + namespace: + description: Namespace 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 + roleSelector: + description: RoleSelector selects a reference to a Role this grant + is for. + properties: + matchControllerRef: + description: |- + MatchControllerRef ensures an object with the same controller reference + as the selecting object is selected. + type: boolean + matchLabels: + additionalProperties: + type: string + description: MatchLabels ensures an object with matching labels + is selected. + type: object + namespace: + description: Namespace for the selector + type: string + policy: + description: Policies for selection. + 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 + type: object + withOption: + description: |- + WithOption allows an option to be set on the grant. + See https://www.postgresql.org/docs/current/sql-grant.html for available + options for each grant type, and the effects of applying the option. + enum: + - ADMIN + - GRANT + type: string + 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 GrantStatus represents the observed state of a Grant. + 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 + 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/postgresql.sql.m.crossplane.io_providerconfigs.yaml b/package/crds/postgresql.sql.m.crossplane.io_providerconfigs.yaml new file mode 100644 index 00000000..908f0e7d --- /dev/null +++ b/package/crds/postgresql.sql.m.crossplane.io_providerconfigs.yaml @@ -0,0 +1,157 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: providerconfigs.postgresql.sql.m.crossplane.io +spec: + group: postgresql.sql.m.crossplane.io + names: + categories: + - crossplane + - provider + - sql + kind: ProviderConfig + listKind: ProviderConfigList + plural: providerconfigs + singular: providerconfig + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + - jsonPath: .spec.credentials.connectionSecretRef.name + name: SECRET-NAME + priority: 1 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: A ProviderConfig configures a Template 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: + credentials: + description: Credentials required to authenticate to this provider. + properties: + connectionSecretRef: + description: |- + A CredentialsSecretRef is a reference to a PostgreSQL connection secret + that contains the credentials that must be used to connect to the + provider. +optional + properties: + name: + description: Name of the secret. + type: string + required: + - name + type: object + source: + description: Source of the provider credentials. + enum: + - PostgreSQLConnectionSecret + type: string + required: + - source + type: object + defaultDatabase: + default: postgres + description: |- + Defines the database name used to set up a connection to the provided + PostgreSQL instance. Same as PGDATABASE environment variable. + type: string + sslMode: + default: verify-full + description: |- + Defines the SSL mode used to set up a connection to the provided + PostgreSQL instance + enum: + - disable + - require + - verify-ca + - verify-full + type: string + required: + - credentials + 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/postgresql.sql.m.crossplane.io_providerconfigusages.yaml b/package/crds/postgresql.sql.m.crossplane.io_providerconfigusages.yaml new file mode 100644 index 00000000..d52d7cb9 --- /dev/null +++ b/package/crds/postgresql.sql.m.crossplane.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.postgresql.sql.m.crossplane.io +spec: + group: postgresql.sql.m.crossplane.io + names: + categories: + - crossplane + - provider + - sql + 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: v1alpha1 + 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/postgresql.sql.m.crossplane.io_roles.yaml b/package/crds/postgresql.sql.m.crossplane.io_roles.yaml new file mode 100644 index 00000000..2f4209f8 --- /dev/null +++ b/package/crds/postgresql.sql.m.crossplane.io_roles.yaml @@ -0,0 +1,276 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: roles.postgresql.sql.m.crossplane.io +spec: + group: postgresql.sql.m.crossplane.io + names: + categories: + - crossplane + - managed + - sql + kind: Role + listKind: RoleList + plural: roles + singular: role + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: READY + type: string + - jsonPath: .status.conditions[?(@.type=='Synced')].status + name: SYNCED + type: string + - jsonPath: .spec.forProvider.connectionLimit + name: CONN LIMIT + type: integer + - jsonPath: .status.atProvider.privilegesAsClauses + name: PRIVILEGES + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: A Role represents the declarative state of a PostgreSQL role. + 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 RoleSpec defines the desired state of a Role. + properties: + forProvider: + description: RoleParameters define the desired state of a PostgreSQL + role instance. + properties: + configurationParameters: + description: |- + ConfigurationParameters to be applied to the role. If specified, any other configuration parameters set on the + role in the database will be reset. + + See https://www.postgresql.org/docs/current/runtime-config-client.html for some available configuration parameters. + items: + description: RoleConfigurationParameter is a role configuration + parameter. + properties: + name: + type: string + value: + type: string + type: object + type: array + connectionLimit: + description: ConnectionLimit to be applied to the role. + format: int32 + type: integer + passwordSecretRef: + description: |- + PasswordSecretRef references the secret that contains the password used + for this role. If no reference is given, a password will be auto-generated. + properties: + key: + type: string + name: + description: Name of the secret. + type: string + required: + - key + - name + type: object + privileges: + description: Privileges to be granted. + properties: + bypassRls: + description: BypassRls grants BYPASSRLS when true, allowing + the role to bypass row-level security policies. + type: boolean + createDb: + description: CreateDb grants CREATEDB when true, allowing + the role to create databases. + type: boolean + createRole: + description: CreateRole grants CREATEROLE when true, allowing + this role to create other roles. + type: boolean + inherit: + description: |- + Inherit grants INHERIT when true, allowing the role to inherit permissions + from other roles it is a member of. + type: boolean + login: + description: Login grants LOGIN when true, allowing the role + to login to the server. + type: boolean + replication: + description: Replication grants REPLICATION when true, allowing + the role to connect in replication mode. + type: boolean + superUser: + description: SuperUser grants SUPERUSER privilege when true. + type: boolean + type: object + 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 RoleStatus represents the observed state of a Role. + properties: + atProvider: + description: A RoleObservation represents the observed state of a + PostgreSQL role. + properties: + configurationParameters: + description: ConfigurationParameters represents the applied configuration + parameters for the PostgreSQL role. + items: + description: RoleConfigurationParameter is a role configuration + parameter. + properties: + name: + type: string + value: + type: string + type: object + type: array + privilegesAsClauses: + description: |- + PrivilegesAsClauses represents the applied privileges state, taking into account + any defaults applied by Postgres, and expressed as a list of ROLE PRIVILEGE clauses. + items: + type: string + type: array + 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/postgresql.sql.m.crossplane.io_schemas.yaml b/package/crds/postgresql.sql.m.crossplane.io_schemas.yaml new file mode 100644 index 00000000..943dffa2 --- /dev/null +++ b/package/crds/postgresql.sql.m.crossplane.io_schemas.yaml @@ -0,0 +1,362 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: schemas.postgresql.sql.m.crossplane.io +spec: + group: postgresql.sql.m.crossplane.io + names: + categories: + - crossplane + - managed + - sql + kind: Schema + listKind: SchemaList + plural: schemas + singular: schema + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: READY + type: string + - jsonPath: .status.conditions[?(@.type=='Synced')].status + name: SYNCED + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + - jsonPath: .spec.forProvider.role + name: ROLE + type: string + - jsonPath: .spec.forProvider.database + name: DATABASE + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: A Schema represents the declarative state of a PostgreSQL schema. + 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 SchemaSpec defines the desired state of a Schema. + properties: + forProvider: + description: SchemaParameters define the desired state of a PostgreSQL + schema. + properties: + database: + description: Database this schema is for. + type: string + databaseRef: + description: DatabaseRef references the database object this schema + is for. + properties: + name: + description: Name of the referenced object. + type: string + namespace: + description: Namespace 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 + databaseSelector: + description: DatabaseSelector selects a reference to a Database + this schema is for. + properties: + matchControllerRef: + description: |- + MatchControllerRef ensures an object with the same controller reference + as the selecting object is selected. + type: boolean + matchLabels: + additionalProperties: + type: string + description: MatchLabels ensures an object with matching labels + is selected. + type: object + namespace: + description: Namespace for the selector + type: string + policy: + description: Policies for selection. + 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 + type: object + revokePublicOnSchema: + description: RevokePublicOnSchema apply a "REVOKE ALL ON SCHEMA + public FROM public" statement + type: boolean + role: + description: Role for ownership of this schema. + type: string + roleRef: + description: RoleRef references the role object this schema is + for. + properties: + name: + description: Name of the referenced object. + type: string + namespace: + description: Namespace 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 + roleSelector: + description: RoleSelector selects a reference to a Role this schema + is for. + properties: + matchControllerRef: + description: |- + MatchControllerRef ensures an object with the same controller reference + as the selecting object is selected. + type: boolean + matchLabels: + additionalProperties: + type: string + description: MatchLabels ensures an object with matching labels + is selected. + type: object + namespace: + description: Namespace for the selector + type: string + policy: + description: Policies for selection. + 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 + type: object + 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 SchemaStatus represents the observed state of a Schema. + 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 + 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/pkg/apis/scheme.go b/pkg/apis/scheme.go new file mode 100644 index 00000000..d64f7ba0 --- /dev/null +++ b/pkg/apis/scheme.go @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +package apis + +import ( + xpresource "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var s = runtime.NewScheme() + +func GetManagedResource(group, version, kind, listKind string) (xpresource.Managed, xpresource.ManagedList, error) { + gv := schema.GroupVersion{ + Group: group, + Version: version, + } + kingGVK := gv.WithKind(kind) + m, err := s.New(kingGVK) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to get a new API object of GVK %q from the runtime scheme", kingGVK) + } + + listGVK := gv.WithKind(listKind) + l, err := s.New(listGVK) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to get a new API object list of GVK %q from the runtime scheme", listGVK) + } + return m.(xpresource.Managed), l.(xpresource.ManagedList), nil +} + +func BuildScheme(sb runtime.SchemeBuilder) error { + return errors.Wrap(sb.AddToScheme(s), "failed to register the GVKs with the runtime scheme") +} diff --git a/pkg/clients/mssql/mssql.go b/pkg/clients/mssql/mssql.go index 89189140..ff6cebba 100644 --- a/pkg/clients/mssql/mssql.go +++ b/pkg/clients/mssql/mssql.go @@ -25,8 +25,8 @@ import ( "github.com/pkg/errors" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" ) diff --git a/pkg/clients/mysql/mysql.go b/pkg/clients/mysql/mysql.go index 3a9e02d9..a6ea5306 100644 --- a/pkg/clients/mysql/mysql.go +++ b/pkg/clients/mysql/mysql.go @@ -10,8 +10,8 @@ import ( "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" "github.com/pkg/errors" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" ) const ( diff --git a/pkg/clients/postgresql/postgresql.go b/pkg/clients/postgresql/postgresql.go index 270bb92c..a55d6ae2 100644 --- a/pkg/clients/postgresql/postgresql.go +++ b/pkg/clients/postgresql/postgresql.go @@ -9,8 +9,8 @@ import ( "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" "github.com/lib/pq" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" ) const ( diff --git a/pkg/clients/xsql/common.go b/pkg/clients/xsql/common.go index 96bce74a..9ed958c6 100644 --- a/pkg/clients/xsql/common.go +++ b/pkg/clients/xsql/common.go @@ -6,7 +6,7 @@ import ( "database/sql" - "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" ) // A Query that may be run against a DB. diff --git a/pkg/controller/mssql/config/config.go b/pkg/controller/cluster/mssql/config/config.go similarity index 78% rename from pkg/controller/mssql/config/config.go rename to pkg/controller/cluster/mssql/config/config.go index 3fb887a9..fb3e12d1 100644 --- a/pkg/controller/mssql/config/config.go +++ b/pkg/controller/cluster/mssql/config/config.go @@ -19,12 +19,12 @@ package config 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/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/reconciler/providerconfig" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" - "github.com/crossplane-contrib/provider-sql/apis/mssql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/apis/cluster/mssql/v1alpha1" ) // Setup adds a controller that reconciles ProviderConfigs by accounting for @@ -34,6 +34,7 @@ func Setup(mgr ctrl.Manager, o controller.Options) error { of := resource.ProviderConfigKinds{ Config: v1alpha1.ProviderConfigGroupVersionKind, + Usage: v1alpha1.ProviderConfigUsageGroupVersionKind, UsageList: v1alpha1.ProviderConfigUsageListGroupVersionKind, } diff --git a/pkg/controller/mssql/database/reconciler.go b/pkg/controller/cluster/mssql/database/reconciler.go similarity index 65% rename from pkg/controller/mssql/database/reconciler.go rename to pkg/controller/cluster/mssql/database/reconciler.go index e64c6938..0b4739b2 100644 --- a/pkg/controller/mssql/database/reconciler.go +++ b/pkg/controller/cluster/mssql/database/reconciler.go @@ -26,24 +26,27 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - xpcontroller "github.com/crossplane/crossplane-runtime/pkg/controller" - "github.com/crossplane/crossplane-runtime/pkg/event" - "github.com/crossplane/crossplane-runtime/pkg/feature" - "github.com/crossplane/crossplane-runtime/pkg/meta" - "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" - "github.com/crossplane/crossplane-runtime/pkg/resource" - - "github.com/crossplane-contrib/provider-sql/apis/mssql/v1alpha1" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/feature" + "github.com/crossplane/crossplane-runtime/v2/pkg/meta" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + clusterv1alpha1 "github.com/crossplane-contrib/provider-sql/apis/cluster/mssql/v1alpha1" "github.com/crossplane-contrib/provider-sql/pkg/clients/mssql" "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" ) const ( - errTrackPCUsage = "cannot track ProviderConfig usage" - errGetPC = "cannot get ProviderConfig" - errNoSecretRef = "ProviderConfig does not reference a credentials Secret" - errGetSecret = "cannot get credentials Secret" + errNoProviderConfig = "no providerConfigRef provided" + errGetProviderConfig = "cannot get referenced ProviderConfig" + errTrackUsage = "cannot track ProviderConfig usage" + errTrackPCUsage = "cannot track ProviderConfig usage" + errGetPC = "cannot get ProviderConfig" + errNoSecretRef = "ProviderConfig does not reference a credentials Secret" + errGetSecret = "cannot get credentials Secret" errNotDatabase = "managed resource is not a Database custom resource" errSelectDB = "cannot select database" @@ -53,13 +56,27 @@ const ( maxConcurrency = 5 ) +// TODO(nateinaction): This looks wrong, can tracker creation be improved? +type tracker struct { + tracker *resource.LegacyProviderConfigUsageTracker +} + +var _ resource.Tracker = &tracker{} + +func (t *tracker) Track(ctx context.Context, mg resource.Managed) error { + return t.tracker.Track(ctx, mg.(resource.LegacyManaged)) +} + // Setup adds a controller that reconciles Database managed resources. func Setup(mgr ctrl.Manager, o xpcontroller.Options) error { - name := managed.ControllerName(v1alpha1.DatabaseGroupKind) + name := managed.ControllerName(clusterv1alpha1.DatabaseGroupKind) + + // This can only be a legacy tracker + t := resource.NewLegacyProviderConfigUsageTracker(mgr.GetClient(), &clusterv1alpha1.ProviderConfigUsage{}) + trk := &tracker{tracker: t} - t := resource.NewProviderConfigUsageTracker(mgr.GetClient(), &v1alpha1.ProviderConfigUsage{}) reconcilerOptions := []managed.ReconcilerOption{ - managed.WithExternalConnecter(&connector{kube: mgr.GetClient(), usage: t, newClient: mssql.New}), + managed.WithTypedExternalConnector(&connector{kube: mgr.GetClient(), usage: trk, newClient: mssql.New}), managed.WithLogger(o.Logger.WithValues("controller", name)), managed.WithPollInterval(o.PollInterval), managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), @@ -68,12 +85,12 @@ func Setup(mgr ctrl.Manager, o xpcontroller.Options) error { reconcilerOptions = append(reconcilerOptions, managed.WithManagementPolicies()) } r := managed.NewReconciler(mgr, - resource.ManagedKind(v1alpha1.DatabaseGroupVersionKind), + resource.ManagedKind(clusterv1alpha1.DatabaseGroupVersionKind), reconcilerOptions..., ) return ctrl.NewControllerManagedBy(mgr). Named(name). - For(&v1alpha1.Database{}). + For(&clusterv1alpha1.Database{}). WithOptions(controller.Options{ MaxConcurrentReconciles: maxConcurrency, }). @@ -86,8 +103,10 @@ type connector struct { newClient func(creds map[string][]byte, database string) xsql.DB } -func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.ExternalClient, error) { - cr, ok := mg.(*v1alpha1.Database) +var _ managed.TypedExternalConnector[resource.Managed] = &connector{} + +func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.TypedExternalClient[resource.Managed], error) { + cr, ok := mg.(*clusterv1alpha1.Database) if !ok { return nil, errors.New(errNotDatabase) } @@ -98,7 +117,7 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E // ProviderConfigReference could theoretically be nil, but in practice the // DefaultProviderConfig initializer will set it before we get here. - pc := &v1alpha1.ProviderConfig{} + pc := &clusterv1alpha1.ProviderConfig{} if err := c.kube.Get(ctx, types.NamespacedName{Name: cr.GetProviderConfigReference().Name}, pc); err != nil { return nil, errors.Wrap(err, errGetPC) } @@ -121,8 +140,16 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E type external struct{ db xsql.DB } +var _ managed.TypedExternalClient[resource.Managed] = &external{} + +func (c *external) Disconnect(ctx context.Context) error { + // Do we need to implement this? Clean up any db connections? + // The xsql.DB interface does not have a Disconnect method. + return nil +} + func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { - cr, ok := mg.(*v1alpha1.Database) + cr, ok := mg.(*clusterv1alpha1.Database) if !ok { return managed.ExternalObservation{}, errors.New(errNotDatabase) } @@ -150,7 +177,7 @@ func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.Ex func (c *external) Create(ctx context.Context, mg resource.Managed) (managed.ExternalCreation, error) { - cr, ok := mg.(*v1alpha1.Database) + cr, ok := mg.(*clusterv1alpha1.Database) if !ok { return managed.ExternalCreation{}, errors.New(errNotDatabase) } @@ -164,12 +191,12 @@ func (c *external) Update(_ context.Context, _ resource.Managed) (managed.Extern return managed.ExternalUpdate{}, nil } -func (c *external) Delete(ctx context.Context, mg resource.Managed) error { - cr, ok := mg.(*v1alpha1.Database) +func (c *external) Delete(ctx context.Context, mg resource.Managed) (managed.ExternalDelete, error) { + cr, ok := mg.(*clusterv1alpha1.Database) if !ok { - return errors.New(errNotDatabase) + return managed.ExternalDelete{}, errors.New(errNotDatabase) } err := c.db.Exec(ctx, xsql.Query{String: "DROP DATABASE IF EXISTS " + mssql.QuoteIdentifier(meta.GetExternalName(cr))}) - return errors.Wrap(err, errDropDB) + return managed.ExternalDelete{}, errors.Wrap(err, errDropDB) } diff --git a/pkg/controller/mssql/database/reconciler_test.go b/pkg/controller/cluster/mssql/database/reconciler_test.go similarity index 96% rename from pkg/controller/mssql/database/reconciler_test.go rename to pkg/controller/cluster/mssql/database/reconciler_test.go index ed7c2fe7..dadeb7b0 100644 --- a/pkg/controller/mssql/database/reconciler_test.go +++ b/pkg/controller/cluster/mssql/database/reconciler_test.go @@ -21,16 +21,16 @@ import ( "database/sql" "testing" - "github.com/crossplane-contrib/provider-sql/apis/mssql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/apis/cluster/mssql/v1alpha1" "github.com/google/go-cmp/cmp" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" "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/crossplane-contrib/provider-sql/pkg/clients/xsql" ) @@ -390,7 +390,7 @@ func TestDelete(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { e := external{db: tc.fields.db} - 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/pkg/controller/mssql/grant/reconciler.go b/pkg/controller/cluster/mssql/grant/reconciler.go similarity index 83% rename from pkg/controller/mssql/grant/reconciler.go rename to pkg/controller/cluster/mssql/grant/reconciler.go index fb01ade5..7b1649ba 100644 --- a/pkg/controller/mssql/grant/reconciler.go +++ b/pkg/controller/cluster/mssql/grant/reconciler.go @@ -30,14 +30,14 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - xpcontroller "github.com/crossplane/crossplane-runtime/pkg/controller" - "github.com/crossplane/crossplane-runtime/pkg/event" - "github.com/crossplane/crossplane-runtime/pkg/feature" - "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" - "github.com/crossplane/crossplane-runtime/pkg/resource" - - "github.com/crossplane-contrib/provider-sql/apis/mssql/v1alpha1" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/feature" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + "github.com/crossplane-contrib/provider-sql/apis/cluster/mssql/v1alpha1" "github.com/crossplane-contrib/provider-sql/pkg/clients/mssql" "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" ) @@ -56,13 +56,27 @@ const ( maxConcurrency = 5 ) +// TODO(nateinaction): This looks wrong, can tracker creation be improved? +type tracker struct { + tracker *resource.LegacyProviderConfigUsageTracker +} + +var _ resource.Tracker = &tracker{} + +func (t *tracker) Track(ctx context.Context, mg resource.Managed) error { + return t.tracker.Track(ctx, mg.(resource.LegacyManaged)) +} + // Setup adds a controller that reconciles Grant managed resources. func Setup(mgr ctrl.Manager, o xpcontroller.Options) error { name := managed.ControllerName(v1alpha1.GrantGroupKind) - t := resource.NewProviderConfigUsageTracker(mgr.GetClient(), &v1alpha1.ProviderConfigUsage{}) + // This can only be a legacy tracker + t := resource.NewLegacyProviderConfigUsageTracker(mgr.GetClient(), &v1alpha1.ProviderConfigUsage{}) + trk := &tracker{tracker: t} + reconcilerOptions := []managed.ReconcilerOption{ - managed.WithExternalConnecter(&connector{kube: mgr.GetClient(), usage: t, newClient: mssql.New}), + managed.WithTypedExternalConnector(&connector{kube: mgr.GetClient(), usage: trk, newClient: mssql.New}), managed.WithReferenceResolver(managed.NewAPISimpleReferenceResolver(mgr.GetClient())), managed.WithLogger(o.Logger.WithValues("controller", name)), managed.WithPollInterval(o.PollInterval), @@ -90,7 +104,9 @@ type connector struct { newClient func(creds map[string][]byte, database string) xsql.DB } -func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.ExternalClient, error) { +var _ managed.TypedExternalConnector[resource.Managed] = &connector{} + +func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.TypedExternalClient[resource.Managed], error) { cr, ok := mg.(*v1alpha1.Grant) if !ok { return nil, errors.New(errNotGrant) @@ -120,16 +136,12 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E return nil, errors.Wrap(err, errGetSecret) } - return &external{ - db: c.newClient(s.Data, ptr.Deref(cr.Spec.ForProvider.Database, "")), - kube: c.kube, - }, nil + return &external{db: c.newClient(s.Data, ptr.Deref(cr.Spec.ForProvider.Database, ""))}, nil } -type external struct { - db xsql.DB - kube client.Client -} +type external struct{ db xsql.DB } + +var _ managed.TypedExternalClient[resource.Managed] = &external{} func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { cr, ok := mg.(*v1alpha1.Grant) @@ -199,10 +211,14 @@ func (c *external) Update(ctx context.Context, mg resource.Managed) (managed.Ext return managed.ExternalUpdate{}, nil } -func (c *external) Delete(ctx context.Context, mg resource.Managed) error { +func (c *external) Disconnect(ctx context.Context) error { + return nil +} + +func (c *external) Delete(ctx context.Context, mg resource.Managed) (managed.ExternalDelete, error) { cr, ok := mg.(*v1alpha1.Grant) if !ok { - return errors.New(errNotGrant) + return managed.ExternalDelete{}, errors.New(errNotGrant) } username := *cr.Spec.ForProvider.User @@ -212,7 +228,7 @@ func (c *external) Delete(ctx context.Context, mg resource.Managed) error { onSchemaQuery(cr), mssql.QuoteIdentifier(username), ) - return errors.Wrap(c.db.Exec(ctx, xsql.Query{String: query}), errRevoke) + return managed.ExternalDelete{}, errors.Wrap(c.db.Exec(ctx, xsql.Query{String: query}), errRevoke) } // TODO(turkenh/ulucinar): Possible performance improvement. We first diff --git a/pkg/controller/mssql/grant/reconciler_test.go b/pkg/controller/cluster/mssql/grant/reconciler_test.go similarity index 98% rename from pkg/controller/mssql/grant/reconciler_test.go rename to pkg/controller/cluster/mssql/grant/reconciler_test.go index c4b9d079..053116cf 100644 --- a/pkg/controller/mssql/grant/reconciler_test.go +++ b/pkg/controller/cluster/mssql/grant/reconciler_test.go @@ -30,12 +30,12 @@ import ( "k8s.io/utils/ptr" "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/crossplane-contrib/provider-sql/apis/mssql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/apis/cluster/mssql/v1alpha1" "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" ) @@ -780,7 +780,7 @@ func TestDelete(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { e := external{db: tc.fields.db} - 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/pkg/controller/mssql/mssql.go b/pkg/controller/cluster/mssql/mssql.go similarity index 72% rename from pkg/controller/mssql/mssql.go rename to pkg/controller/cluster/mssql/mssql.go index d583d726..32a66d02 100644 --- a/pkg/controller/mssql/mssql.go +++ b/pkg/controller/cluster/mssql/mssql.go @@ -19,12 +19,12 @@ package mssql import ( ctrl "sigs.k8s.io/controller-runtime" - "github.com/crossplane/crossplane-runtime/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/controller" - "github.com/crossplane-contrib/provider-sql/pkg/controller/mssql/config" - "github.com/crossplane-contrib/provider-sql/pkg/controller/mssql/database" - "github.com/crossplane-contrib/provider-sql/pkg/controller/mssql/grant" - "github.com/crossplane-contrib/provider-sql/pkg/controller/mssql/user" + "github.com/crossplane-contrib/provider-sql/pkg/controller/cluster/mssql/config" + "github.com/crossplane-contrib/provider-sql/pkg/controller/cluster/mssql/database" + "github.com/crossplane-contrib/provider-sql/pkg/controller/cluster/mssql/grant" + "github.com/crossplane-contrib/provider-sql/pkg/controller/cluster/mssql/user" ) // Setup creates all MSSQL controllers with the supplied logger and adds diff --git a/pkg/controller/mssql/user/reconciler.go b/pkg/controller/cluster/mssql/user/reconciler.go similarity index 79% rename from pkg/controller/mssql/user/reconciler.go rename to pkg/controller/cluster/mssql/user/reconciler.go index c8cf357f..3c18bfc7 100644 --- a/pkg/controller/mssql/user/reconciler.go +++ b/pkg/controller/cluster/mssql/user/reconciler.go @@ -28,16 +28,16 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - xpcontroller "github.com/crossplane/crossplane-runtime/pkg/controller" - "github.com/crossplane/crossplane-runtime/pkg/event" - "github.com/crossplane/crossplane-runtime/pkg/feature" - "github.com/crossplane/crossplane-runtime/pkg/meta" - "github.com/crossplane/crossplane-runtime/pkg/password" - "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" - "github.com/crossplane/crossplane-runtime/pkg/resource" - - "github.com/crossplane-contrib/provider-sql/apis/mssql/v1alpha1" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/feature" + "github.com/crossplane/crossplane-runtime/v2/pkg/meta" + "github.com/crossplane/crossplane-runtime/v2/pkg/password" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + "github.com/crossplane-contrib/provider-sql/apis/cluster/mssql/v1alpha1" "github.com/crossplane-contrib/provider-sql/pkg/clients/mssql" "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" ) @@ -63,13 +63,27 @@ const ( maxConcurrency = 5 ) +// TODO(nateinaction): This looks wrong, can tracker creation be improved? +type tracker struct { + tracker *resource.LegacyProviderConfigUsageTracker +} + +var _ resource.Tracker = &tracker{} + +func (t *tracker) Track(ctx context.Context, mg resource.Managed) error { + return t.tracker.Track(ctx, mg.(resource.LegacyManaged)) +} + // Setup adds a controller that reconciles User managed resources. func Setup(mgr ctrl.Manager, o xpcontroller.Options) error { name := managed.ControllerName(v1alpha1.UserGroupKind) - t := resource.NewProviderConfigUsageTracker(mgr.GetClient(), &v1alpha1.ProviderConfigUsage{}) + // This can only be a legacy tracker + t := resource.NewLegacyProviderConfigUsageTracker(mgr.GetClient(), &v1alpha1.ProviderConfigUsage{}) + trk := &tracker{tracker: t} + reconcilerOptions := []managed.ReconcilerOption{ - managed.WithExternalConnecter(&connector{kube: mgr.GetClient(), usage: t, newClient: mssql.New}), + managed.WithTypedExternalConnector(&connector{kube: mgr.GetClient(), usage: trk, newClient: mssql.New}), managed.WithLogger(o.Logger.WithValues("controller", name)), managed.WithPollInterval(o.PollInterval), managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), @@ -96,7 +110,9 @@ type connector struct { newClient func(creds map[string][]byte, database string) xsql.DB } -func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.ExternalClient, error) { +var _ managed.TypedExternalConnector[resource.Managed] = &connector{} + +func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.TypedExternalClient[resource.Managed], error) { cr, ok := mg.(*v1alpha1.User) if !ok { return nil, errors.New(errNotUser) @@ -145,6 +161,8 @@ type external struct { kube client.Client } +var _ managed.TypedExternalClient[resource.Managed] = &external{} + func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { cr, ok := mg.(*v1alpha1.User) if !ok { @@ -241,43 +259,47 @@ func (c *external) Update(ctx context.Context, mg resource.Managed) (managed.Ext return managed.ExternalUpdate{}, nil } -func (c *external) Delete(ctx context.Context, mg resource.Managed) error { +func (c *external) Disconnect(ctx context.Context) error { + return nil +} + +func (c *external) Delete(ctx context.Context, mg resource.Managed) (managed.ExternalDelete, error) { cr, ok := mg.(*v1alpha1.User) if !ok { - return errors.New(errNotUser) + return managed.ExternalDelete{}, errors.New(errNotUser) } query := fmt.Sprintf("SELECT session_id FROM sys.dm_exec_sessions WHERE login_name = %s", mssql.QuoteValue(meta.GetExternalName(cr))) rows, err := c.userDB.Query(ctx, xsql.Query{String: query}) if err != nil { - return errors.Wrap(err, errCannotGetLogins) + return managed.ExternalDelete{}, errors.Wrap(err, errCannotGetLogins) } defer rows.Close() //nolint:errcheck for rows.Next() { var sessionID int if err := rows.Scan(&sessionID); err != nil { - return errors.Wrap(err, errCannotGetLogins) + return managed.ExternalDelete{}, errors.Wrap(err, errCannotGetLogins) } if err := c.userDB.Exec(ctx, xsql.Query{String: fmt.Sprintf("KILL %d", sessionID)}); err != nil { - return errors.Wrapf(err, errCannotKillLoginSession, sessionID, meta.GetExternalName(cr)) + return managed.ExternalDelete{}, errors.Wrapf(err, errCannotKillLoginSession, sessionID, meta.GetExternalName(cr)) } } if err := rows.Err(); err != nil { - return errors.Wrap(err, errCannotGetLogins) + return managed.ExternalDelete{}, errors.Wrap(err, errCannotGetLogins) } if err := c.userDB.Exec(ctx, xsql.Query{ String: fmt.Sprintf("DROP USER IF EXISTS %s", mssql.QuoteIdentifier(meta.GetExternalName(cr))), }); err != nil { - return errors.Wrapf(err, errDropUser, meta.GetExternalName(cr)) + return managed.ExternalDelete{}, errors.Wrapf(err, errDropUser, meta.GetExternalName(cr)) } if err := c.loginDB.Exec(ctx, xsql.Query{ String: fmt.Sprintf("DROP LOGIN %s", mssql.QuoteIdentifier(meta.GetExternalName(cr))), }); err != nil { - return errors.Wrapf(err, errDropLogin, meta.GetExternalName(cr)) + return managed.ExternalDelete{}, errors.Wrapf(err, errDropLogin, meta.GetExternalName(cr)) } - return nil + return managed.ExternalDelete{}, nil } diff --git a/pkg/controller/mssql/user/reconciler_test.go b/pkg/controller/cluster/mssql/user/reconciler_test.go similarity index 98% rename from pkg/controller/mssql/user/reconciler_test.go rename to pkg/controller/cluster/mssql/user/reconciler_test.go index 022eceb9..478b86cd 100644 --- a/pkg/controller/mssql/user/reconciler_test.go +++ b/pkg/controller/cluster/mssql/user/reconciler_test.go @@ -22,7 +22,7 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/crossplane-contrib/provider-sql/apis/mssql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/apis/cluster/mssql/v1alpha1" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/pkg/errors" @@ -31,11 +31,11 @@ import ( "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - "github.com/crossplane/crossplane-runtime/pkg/meta" - "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/meta" + "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/crossplane-contrib/provider-sql/pkg/clients/xsql" ) @@ -884,7 +884,7 @@ func TestDelete(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { e := external{userDB: tc.fields.userDB, loginDB: tc.fields.loginDB} - 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/pkg/controller/mssql/user/utils.go b/pkg/controller/cluster/mssql/user/utils.go similarity index 90% rename from pkg/controller/mssql/user/utils.go rename to pkg/controller/cluster/mssql/user/utils.go index 15bac9b6..411ffd63 100644 --- a/pkg/controller/mssql/user/utils.go +++ b/pkg/controller/cluster/mssql/user/utils.go @@ -24,10 +24,10 @@ import ( "github.com/pkg/errors" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - "github.com/crossplane/crossplane-runtime/pkg/resource" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" - "github.com/crossplane-contrib/provider-sql/apis/mssql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/apis/cluster/mssql/v1alpha1" ) func (c *external) getPassword(ctx context.Context, user *v1alpha1.User) (newPwd string, changed bool, err error) { diff --git a/pkg/controller/postgresql/config/config.go b/pkg/controller/cluster/mysql/config/config.go similarity index 78% rename from pkg/controller/postgresql/config/config.go rename to pkg/controller/cluster/mysql/config/config.go index eae99a78..d572dd67 100644 --- a/pkg/controller/postgresql/config/config.go +++ b/pkg/controller/cluster/mysql/config/config.go @@ -19,12 +19,12 @@ package config 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/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/reconciler/providerconfig" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" - "github.com/crossplane-contrib/provider-sql/apis/postgresql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/apis/cluster/mysql/v1alpha1" ) // Setup adds a controller that reconciles ProviderConfigs by accounting for @@ -34,6 +34,7 @@ func Setup(mgr ctrl.Manager, o controller.Options) error { of := resource.ProviderConfigKinds{ Config: v1alpha1.ProviderConfigGroupVersionKind, + Usage: v1alpha1.ProviderConfigUsageGroupVersionKind, UsageList: v1alpha1.ProviderConfigUsageListGroupVersionKind, } diff --git a/pkg/controller/mysql/database/reconciler.go b/pkg/controller/cluster/mysql/database/reconciler.go similarity index 77% rename from pkg/controller/mysql/database/reconciler.go rename to pkg/controller/cluster/mysql/database/reconciler.go index d5d8c932..72d86444 100644 --- a/pkg/controller/mysql/database/reconciler.go +++ b/pkg/controller/cluster/mysql/database/reconciler.go @@ -26,18 +26,18 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - xpcontroller "github.com/crossplane/crossplane-runtime/pkg/controller" - "github.com/crossplane/crossplane-runtime/pkg/event" - "github.com/crossplane/crossplane-runtime/pkg/feature" - "github.com/crossplane/crossplane-runtime/pkg/meta" - "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" - "github.com/crossplane/crossplane-runtime/pkg/resource" - - "github.com/crossplane-contrib/provider-sql/apis/mysql/v1alpha1" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/feature" + "github.com/crossplane/crossplane-runtime/v2/pkg/meta" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + "github.com/crossplane-contrib/provider-sql/apis/cluster/mysql/v1alpha1" "github.com/crossplane-contrib/provider-sql/pkg/clients/mysql" "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" - "github.com/crossplane-contrib/provider-sql/pkg/controller/mysql/tls" + "github.com/crossplane-contrib/provider-sql/pkg/controller/cluster/mysql/tls" ) const ( @@ -55,13 +55,27 @@ const ( maxConcurrency = 5 ) +// TODO(nateinaction): This looks wrong, can tracker creation be improved? +type tracker struct { + tracker *resource.LegacyProviderConfigUsageTracker +} + +var _ resource.Tracker = &tracker{} + +func (t *tracker) Track(ctx context.Context, mg resource.Managed) error { + return t.tracker.Track(ctx, mg.(resource.LegacyManaged)) +} + // Setup adds a controller that reconciles Database managed resources. func Setup(mgr ctrl.Manager, o xpcontroller.Options) error { name := managed.ControllerName(v1alpha1.DatabaseGroupKind) - t := resource.NewProviderConfigUsageTracker(mgr.GetClient(), &v1alpha1.ProviderConfigUsage{}) + // This can only be a legacy tracker + t := resource.NewLegacyProviderConfigUsageTracker(mgr.GetClient(), &v1alpha1.ProviderConfigUsage{}) + trk := &tracker{tracker: t} + reconcilerOptions := []managed.ReconcilerOption{ - managed.WithExternalConnecter(&connector{kube: mgr.GetClient(), usage: t, newDB: mysql.New}), + managed.WithTypedExternalConnector(&connector{kube: mgr.GetClient(), usage: trk, newDB: mysql.New}), managed.WithLogger(o.Logger.WithValues("controller", name)), managed.WithPollInterval(o.PollInterval), managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), @@ -89,7 +103,9 @@ type connector struct { newDB func(creds map[string][]byte, tls *string, binlog *bool) xsql.DB } -func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.ExternalClient, error) { +var _ managed.TypedExternalConnector[resource.Managed] = &connector{} + +func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.TypedExternalClient[resource.Managed], error) { cr, ok := mg.(*v1alpha1.Database) if !ok { return nil, errors.New(errNotDatabase) @@ -130,6 +146,8 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E type external struct{ db xsql.DB } +var _ managed.TypedExternalClient[resource.Managed] = &external{} + func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { cr, ok := mg.(*v1alpha1.Database) if !ok { @@ -177,17 +195,21 @@ func (c *external) Update(ctx context.Context, mg resource.Managed) (managed.Ext return managed.ExternalUpdate{}, nil } -func (c *external) Delete(ctx context.Context, mg resource.Managed) error { +func (c *external) Disconnect(ctx context.Context) error { + return nil +} + +func (c *external) Delete(ctx context.Context, mg resource.Managed) (managed.ExternalDelete, error) { cr, ok := mg.(*v1alpha1.Database) if !ok { - return errors.New(errNotDatabase) + return managed.ExternalDelete{}, errors.New(errNotDatabase) } query := "DROP DATABASE IF EXISTS " + mysql.QuoteIdentifier(meta.GetExternalName(cr)) if err := mysql.ExecWrapper(ctx, c.db, mysql.ExecQuery{Query: query, ErrorValue: errDropDB}); err != nil { - return err + return managed.ExternalDelete{}, err } - return nil + return managed.ExternalDelete{}, nil } diff --git a/pkg/controller/mysql/database/reconciler_test.go b/pkg/controller/cluster/mysql/database/reconciler_test.go similarity index 96% rename from pkg/controller/mysql/database/reconciler_test.go rename to pkg/controller/cluster/mysql/database/reconciler_test.go index e1a6a59a..b3a6db0c 100644 --- a/pkg/controller/mysql/database/reconciler_test.go +++ b/pkg/controller/cluster/mysql/database/reconciler_test.go @@ -21,16 +21,16 @@ import ( "database/sql" "testing" - "github.com/crossplane-contrib/provider-sql/apis/mysql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/apis/cluster/mysql/v1alpha1" "github.com/google/go-cmp/cmp" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" "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/crossplane-contrib/provider-sql/pkg/clients/xsql" ) @@ -391,7 +391,7 @@ func TestDelete(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { e := external{db: tc.fields.db} - 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/pkg/controller/mysql/grant/reconciler.go b/pkg/controller/cluster/mysql/grant/reconciler.go similarity index 87% rename from pkg/controller/mysql/grant/reconciler.go rename to pkg/controller/cluster/mysql/grant/reconciler.go index 675ab401..3c8ac7c8 100644 --- a/pkg/controller/mysql/grant/reconciler.go +++ b/pkg/controller/cluster/mysql/grant/reconciler.go @@ -31,17 +31,17 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - xpcontroller "github.com/crossplane/crossplane-runtime/pkg/controller" - "github.com/crossplane/crossplane-runtime/pkg/event" - "github.com/crossplane/crossplane-runtime/pkg/feature" - "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" - "github.com/crossplane/crossplane-runtime/pkg/resource" - - "github.com/crossplane-contrib/provider-sql/apis/mysql/v1alpha1" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/feature" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + "github.com/crossplane-contrib/provider-sql/apis/cluster/mysql/v1alpha1" "github.com/crossplane-contrib/provider-sql/pkg/clients/mysql" "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" - "github.com/crossplane-contrib/provider-sql/pkg/controller/mysql/tls" + "github.com/crossplane-contrib/provider-sql/pkg/controller/cluster/mysql/tls" ) const ( @@ -65,13 +65,27 @@ var ( grantRegex = regexp.MustCompile(`^GRANT (.+) ON (\S+)\.(\S+) TO \S+@\S+?(\sWITH GRANT OPTION)?$`) ) +// TODO(nateinaction): This looks wrong, can tracker creation be improved? +type tracker struct { + tracker *resource.LegacyProviderConfigUsageTracker +} + +var _ resource.Tracker = &tracker{} + +func (t *tracker) Track(ctx context.Context, mg resource.Managed) error { + return t.tracker.Track(ctx, mg.(resource.LegacyManaged)) +} + // Setup adds a controller that reconciles Grant managed resources. func Setup(mgr ctrl.Manager, o xpcontroller.Options) error { name := managed.ControllerName(v1alpha1.GrantGroupKind) - t := resource.NewProviderConfigUsageTracker(mgr.GetClient(), &v1alpha1.ProviderConfigUsage{}) + // This can only be a legacy tracker + t := resource.NewLegacyProviderConfigUsageTracker(mgr.GetClient(), &v1alpha1.ProviderConfigUsage{}) + trk := &tracker{tracker: t} + reconcilerOptions := []managed.ReconcilerOption{ - managed.WithExternalConnecter(&connector{kube: mgr.GetClient(), usage: t, newDB: mysql.New}), + managed.WithTypedExternalConnector(&connector{kube: mgr.GetClient(), usage: trk, newDB: mysql.New}), managed.WithReferenceResolver(managed.NewAPISimpleReferenceResolver(mgr.GetClient())), managed.WithLogger(o.Logger.WithValues("controller", name)), managed.WithPollInterval(o.PollInterval), @@ -99,7 +113,9 @@ type connector struct { newDB func(creds map[string][]byte, tls *string, binlog *bool) xsql.DB } -func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.ExternalClient, error) { +var _ managed.TypedExternalConnector[resource.Managed] = &connector{} + +func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.TypedExternalClient[resource.Managed], error) { cr, ok := mg.(*v1alpha1.Grant) if !ok { return nil, errors.New(errNotGrant) @@ -135,16 +151,12 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E return nil, errors.Wrap(err, errTLSConfig) } - return &external{ - db: c.newDB(s.Data, tlsName, cr.Spec.ForProvider.BinLog), - kube: c.kube, - }, nil + return &external{db: c.newDB(s.Data, tlsName, cr.Spec.ForProvider.BinLog)}, nil } -type external struct { - db xsql.DB - kube client.Client -} +type external struct{ db xsql.DB } + +var _ managed.TypedExternalClient[resource.Managed] = &external{} func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { cr, ok := mg.(*v1alpha1.Grant) @@ -367,10 +379,14 @@ func createGrantQuery(privileges, dbname, username, host, table string, grantOpt return result } -func (c *external) Delete(ctx context.Context, mg resource.Managed) error { +func (c *external) Disconnect(ctx context.Context) error { + return nil +} + +func (c *external) Delete(ctx context.Context, mg resource.Managed) (managed.ExternalDelete, error) { cr, ok := mg.(*v1alpha1.Grant) if !ok { - return errors.New(errNotGrant) + return managed.ExternalDelete{}, errors.New(errNotGrant) } username, host := mysql.SplitUserHost(*cr.Spec.ForProvider.User) @@ -384,13 +400,13 @@ func (c *external) Delete(ctx context.Context, mg resource.Managed) error { var myErr *mysqldriver.MySQLError if errors.As(err, &myErr) && myErr.Number == errCodeNoSuchGrant { // MySQL automatically deletes related grants if the user has been deleted - return nil + return managed.ExternalDelete{}, nil } - return err + return managed.ExternalDelete{}, err } - return nil + return managed.ExternalDelete{}, nil } func diffPermissions(desired, observed []string) ([]string, []string) { diff --git a/pkg/controller/mysql/grant/reconciler_test.go b/pkg/controller/cluster/mysql/grant/reconciler_test.go similarity index 98% rename from pkg/controller/mysql/grant/reconciler_test.go rename to pkg/controller/cluster/mysql/grant/reconciler_test.go index 49b2eaf4..54089134 100644 --- a/pkg/controller/mysql/grant/reconciler_test.go +++ b/pkg/controller/cluster/mysql/grant/reconciler_test.go @@ -23,7 +23,7 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/crossplane-contrib/provider-sql/apis/mysql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/apis/cluster/mysql/v1alpha1" "github.com/go-sql-driver/mysql" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" @@ -32,10 +32,10 @@ import ( "k8s.io/utils/ptr" "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/crossplane-contrib/provider-sql/pkg/clients/xsql" ) @@ -1131,7 +1131,7 @@ func TestDelete(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { e := external{db: tc.fields.db} - 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/pkg/controller/mysql/mysql.go b/pkg/controller/cluster/mysql/mysql.go similarity index 72% rename from pkg/controller/mysql/mysql.go rename to pkg/controller/cluster/mysql/mysql.go index abc8e226..55b1b3f5 100644 --- a/pkg/controller/mysql/mysql.go +++ b/pkg/controller/cluster/mysql/mysql.go @@ -19,12 +19,12 @@ package mysql import ( ctrl "sigs.k8s.io/controller-runtime" - "github.com/crossplane/crossplane-runtime/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/controller" - "github.com/crossplane-contrib/provider-sql/pkg/controller/mysql/config" - "github.com/crossplane-contrib/provider-sql/pkg/controller/mysql/database" - "github.com/crossplane-contrib/provider-sql/pkg/controller/mysql/grant" - "github.com/crossplane-contrib/provider-sql/pkg/controller/mysql/user" + "github.com/crossplane-contrib/provider-sql/pkg/controller/cluster/mysql/config" + "github.com/crossplane-contrib/provider-sql/pkg/controller/cluster/mysql/database" + "github.com/crossplane-contrib/provider-sql/pkg/controller/cluster/mysql/grant" + "github.com/crossplane-contrib/provider-sql/pkg/controller/cluster/mysql/user" ) // Setup creates all MySQL controllers with the supplied logger and adds diff --git a/pkg/controller/mysql/tls/tls.go b/pkg/controller/cluster/mysql/tls/tls.go similarity index 95% rename from pkg/controller/mysql/tls/tls.go rename to pkg/controller/cluster/mysql/tls/tls.go index f4a6315a..8985e064 100644 --- a/pkg/controller/mysql/tls/tls.go +++ b/pkg/controller/cluster/mysql/tls/tls.go @@ -6,8 +6,8 @@ import ( "crypto/x509" "fmt" - "github.com/crossplane-contrib/provider-sql/apis/mysql/v1alpha1" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + "github.com/crossplane-contrib/provider-sql/apis/cluster/mysql/v1alpha1" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" "github.com/go-sql-driver/mysql" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" diff --git a/pkg/controller/mysql/user/reconciler.go b/pkg/controller/cluster/mysql/user/reconciler.go similarity index 87% rename from pkg/controller/mysql/user/reconciler.go rename to pkg/controller/cluster/mysql/user/reconciler.go index 07de47d7..c3ffdd87 100644 --- a/pkg/controller/mysql/user/reconciler.go +++ b/pkg/controller/cluster/mysql/user/reconciler.go @@ -28,19 +28,19 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - xpcontroller "github.com/crossplane/crossplane-runtime/pkg/controller" - "github.com/crossplane/crossplane-runtime/pkg/event" - "github.com/crossplane/crossplane-runtime/pkg/feature" - "github.com/crossplane/crossplane-runtime/pkg/meta" - "github.com/crossplane/crossplane-runtime/pkg/password" - "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" - "github.com/crossplane/crossplane-runtime/pkg/resource" - - "github.com/crossplane-contrib/provider-sql/apis/mysql/v1alpha1" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/feature" + "github.com/crossplane/crossplane-runtime/v2/pkg/meta" + "github.com/crossplane/crossplane-runtime/v2/pkg/password" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + "github.com/crossplane-contrib/provider-sql/apis/cluster/mysql/v1alpha1" "github.com/crossplane-contrib/provider-sql/pkg/clients/mysql" "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" - "github.com/crossplane-contrib/provider-sql/pkg/controller/mysql/tls" + "github.com/crossplane-contrib/provider-sql/pkg/controller/cluster/mysql/tls" ) const ( @@ -61,13 +61,27 @@ const ( maxConcurrency = 5 ) +// TODO(nateinaction): This looks wrong, can tracker creation be improved? +type tracker struct { + tracker *resource.LegacyProviderConfigUsageTracker +} + +var _ resource.Tracker = &tracker{} + +func (t *tracker) Track(ctx context.Context, mg resource.Managed) error { + return t.tracker.Track(ctx, mg.(resource.LegacyManaged)) +} + // Setup adds a controller that reconciles User managed resources. func Setup(mgr ctrl.Manager, o xpcontroller.Options) error { name := managed.ControllerName(v1alpha1.UserGroupKind) - t := resource.NewProviderConfigUsageTracker(mgr.GetClient(), &v1alpha1.ProviderConfigUsage{}) + // This can only be a legacy tracker + t := resource.NewLegacyProviderConfigUsageTracker(mgr.GetClient(), &v1alpha1.ProviderConfigUsage{}) + trk := &tracker{tracker: t} + reconcilerOptions := []managed.ReconcilerOption{ - managed.WithExternalConnecter(&connector{kube: mgr.GetClient(), usage: t, newDB: mysql.New}), + managed.WithTypedExternalConnector(&connector{kube: mgr.GetClient(), usage: trk, newDB: mysql.New}), managed.WithLogger(o.Logger.WithValues("controller", name)), managed.WithPollInterval(o.PollInterval), managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), @@ -94,7 +108,9 @@ type connector struct { newDB func(creds map[string][]byte, tls *string, binlog *bool) xsql.DB } -func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.ExternalClient, error) { +var _ managed.TypedExternalConnector[resource.Managed] = &connector{} + +func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.TypedExternalClient[resource.Managed], error) { cr, ok := mg.(*v1alpha1.User) if !ok { return nil, errors.New(errNotUser) @@ -141,6 +157,8 @@ type external struct { kube client.Client } +var _ managed.TypedExternalClient[resource.Managed] = &external{} + func handleClause(clause string, value *int, out *[]string) { // If clause is not set (nil pointer), do not push a setting. // This means the default is applied. @@ -360,10 +378,14 @@ func (c *external) UpdatePassword(ctx context.Context, cr *v1alpha1.User, userna return managed.ConnectionDetails{}, nil } -func (c *external) Delete(ctx context.Context, mg resource.Managed) error { +func (c *external) Disconnect(ctx context.Context) error { + return nil +} + +func (c *external) Delete(ctx context.Context, mg resource.Managed) (managed.ExternalDelete, error) { cr, ok := mg.(*v1alpha1.User) if !ok { - return errors.New(errNotUser) + return managed.ExternalDelete{}, errors.New(errNotUser) } cr.SetConditions(xpv1.Deleting()) @@ -372,10 +394,10 @@ func (c *external) Delete(ctx context.Context, mg resource.Managed) error { query := fmt.Sprintf("DROP USER IF EXISTS %s@%s", mysql.QuoteValue(username), mysql.QuoteValue(host)) if err := mysql.ExecWrapper(ctx, c.db, mysql.ExecQuery{Query: query, ErrorValue: errDropUser}); err != nil { - return err + return managed.ExternalDelete{}, err } - return nil + return managed.ExternalDelete{}, nil } func upToDate(observed *v1alpha1.UserParameters, desired *v1alpha1.UserParameters) bool { diff --git a/pkg/controller/mysql/user/reconciler_test.go b/pkg/controller/cluster/mysql/user/reconciler_test.go similarity index 98% rename from pkg/controller/mysql/user/reconciler_test.go rename to pkg/controller/cluster/mysql/user/reconciler_test.go index 855fe457..23420075 100644 --- a/pkg/controller/mysql/user/reconciler_test.go +++ b/pkg/controller/cluster/mysql/user/reconciler_test.go @@ -22,7 +22,7 @@ import ( "strings" "testing" - "github.com/crossplane-contrib/provider-sql/apis/mysql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/apis/cluster/mysql/v1alpha1" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/pkg/errors" @@ -30,11 +30,11 @@ import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - "github.com/crossplane/crossplane-runtime/pkg/meta" - "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/meta" + "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/crossplane-contrib/provider-sql/pkg/clients/xsql" ) @@ -871,7 +871,7 @@ func TestDelete(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { e := external{db: tc.fields.db} - 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/pkg/controller/mysql/user/utils.go b/pkg/controller/cluster/mysql/user/utils.go similarity index 90% rename from pkg/controller/mysql/user/utils.go rename to pkg/controller/cluster/mysql/user/utils.go index 716bffb1..13dd5050 100644 --- a/pkg/controller/mysql/user/utils.go +++ b/pkg/controller/cluster/mysql/user/utils.go @@ -24,10 +24,10 @@ import ( "github.com/pkg/errors" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - "github.com/crossplane/crossplane-runtime/pkg/resource" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" - "github.com/crossplane-contrib/provider-sql/apis/mysql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/apis/cluster/mysql/v1alpha1" ) func (c *external) getPassword(ctx context.Context, user *v1alpha1.User) (newPwd string, changed bool, err error) { diff --git a/pkg/controller/mysql/config/config.go b/pkg/controller/cluster/postgresql/config/config.go similarity index 78% rename from pkg/controller/mysql/config/config.go rename to pkg/controller/cluster/postgresql/config/config.go index 7224df62..67534029 100644 --- a/pkg/controller/mysql/config/config.go +++ b/pkg/controller/cluster/postgresql/config/config.go @@ -19,12 +19,12 @@ package config 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/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/reconciler/providerconfig" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" - "github.com/crossplane-contrib/provider-sql/apis/mysql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/apis/cluster/postgresql/v1alpha1" ) // Setup adds a controller that reconciles ProviderConfigs by accounting for @@ -34,6 +34,7 @@ func Setup(mgr ctrl.Manager, o controller.Options) error { of := resource.ProviderConfigKinds{ Config: v1alpha1.ProviderConfigGroupVersionKind, + Usage: v1alpha1.ProviderConfigUsageGroupVersionKind, UsageList: v1alpha1.ProviderConfigUsageListGroupVersionKind, } diff --git a/pkg/controller/postgresql/database/reconciler.go b/pkg/controller/cluster/postgresql/database/reconciler.go similarity index 87% rename from pkg/controller/postgresql/database/reconciler.go rename to pkg/controller/cluster/postgresql/database/reconciler.go index 9990a5f7..ddba2447 100644 --- a/pkg/controller/postgresql/database/reconciler.go +++ b/pkg/controller/cluster/postgresql/database/reconciler.go @@ -31,15 +31,15 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - xpcontroller "github.com/crossplane/crossplane-runtime/pkg/controller" - "github.com/crossplane/crossplane-runtime/pkg/event" - "github.com/crossplane/crossplane-runtime/pkg/feature" - "github.com/crossplane/crossplane-runtime/pkg/meta" - "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" - "github.com/crossplane/crossplane-runtime/pkg/resource" - - "github.com/crossplane-contrib/provider-sql/apis/postgresql/v1alpha1" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/feature" + "github.com/crossplane/crossplane-runtime/v2/pkg/meta" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + "github.com/crossplane-contrib/provider-sql/apis/cluster/postgresql/v1alpha1" "github.com/crossplane-contrib/provider-sql/pkg/clients" "github.com/crossplane-contrib/provider-sql/pkg/clients/postgresql" "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" @@ -63,13 +63,27 @@ const ( maxConcurrency = 5 ) +// TODO(nateinaction): This looks wrong, can tracker creation be improved? +type tracker struct { + tracker *resource.LegacyProviderConfigUsageTracker +} + +var _ resource.Tracker = &tracker{} + +func (t *tracker) Track(ctx context.Context, mg resource.Managed) error { + return t.tracker.Track(ctx, mg.(resource.LegacyManaged)) +} + // Setup adds a controller that reconciles Database managed resources. func Setup(mgr ctrl.Manager, o xpcontroller.Options) error { name := managed.ControllerName(v1alpha1.DatabaseGroupKind) - t := resource.NewProviderConfigUsageTracker(mgr.GetClient(), &v1alpha1.ProviderConfigUsage{}) + // This can only be a legacy tracker + t := resource.NewLegacyProviderConfigUsageTracker(mgr.GetClient(), &v1alpha1.ProviderConfigUsage{}) + trk := &tracker{tracker: t} + reconcilerOptions := []managed.ReconcilerOption{ - managed.WithExternalConnecter(&connector{kube: mgr.GetClient(), usage: t, newDB: postgresql.New}), + managed.WithTypedExternalConnector(&connector{kube: mgr.GetClient(), usage: trk, newDB: postgresql.New}), managed.WithLogger(o.Logger.WithValues("controller", name)), managed.WithPollInterval(o.PollInterval), managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), @@ -96,7 +110,9 @@ type connector struct { newDB func(creds map[string][]byte, database string, sslmode string) xsql.DB } -func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.ExternalClient, error) { +var _ managed.TypedExternalConnector[resource.Managed] = &connector{} + +func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.TypedExternalClient[resource.Managed], error) { cr, ok := mg.(*v1alpha1.Database) if !ok { return nil, errors.New(errNotDatabase) @@ -131,6 +147,8 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E type external struct{ db xsql.DB } +var _ managed.TypedExternalClient[resource.Managed] = &external{} + func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { cr, ok := mg.(*v1alpha1.Database) if !ok { @@ -289,14 +307,18 @@ func (c *external) Update(ctx context.Context, mg resource.Managed) (managed.Ext return managed.ExternalUpdate{}, nil } -func (c *external) Delete(ctx context.Context, mg resource.Managed) error { +func (c *external) Disconnect(ctx context.Context) error { + return nil +} + +func (c *external) Delete(ctx context.Context, mg resource.Managed) (managed.ExternalDelete, error) { cr, ok := mg.(*v1alpha1.Database) if !ok { - return errors.New(errNotDatabase) + return managed.ExternalDelete{}, errors.New(errNotDatabase) } err := c.db.Exec(ctx, xsql.Query{String: "DROP DATABASE IF EXISTS " + pq.QuoteIdentifier(meta.GetExternalName(cr))}) - return errors.Wrap(err, errDropDB) + return managed.ExternalDelete{}, errors.Wrap(err, errDropDB) } func upToDate(observed, desired v1alpha1.DatabaseParameters) bool { diff --git a/pkg/controller/postgresql/database/reconciler_test.go b/pkg/controller/cluster/postgresql/database/reconciler_test.go similarity index 97% rename from pkg/controller/postgresql/database/reconciler_test.go rename to pkg/controller/cluster/postgresql/database/reconciler_test.go index 98287ceb..3c8a512e 100644 --- a/pkg/controller/postgresql/database/reconciler_test.go +++ b/pkg/controller/cluster/postgresql/database/reconciler_test.go @@ -21,16 +21,16 @@ import ( "database/sql" "testing" - "github.com/crossplane-contrib/provider-sql/apis/postgresql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/apis/cluster/postgresql/v1alpha1" "github.com/google/go-cmp/cmp" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" "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/crossplane-contrib/provider-sql/pkg/clients/xsql" ) @@ -564,7 +564,7 @@ func TestDelete(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { e := external{db: tc.fields.db} - 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/pkg/controller/postgresql/extension/reconciler.go b/pkg/controller/cluster/postgresql/extension/reconciler.go similarity index 81% rename from pkg/controller/postgresql/extension/reconciler.go rename to pkg/controller/cluster/postgresql/extension/reconciler.go index ac2ae11d..0a99bb13 100644 --- a/pkg/controller/postgresql/extension/reconciler.go +++ b/pkg/controller/cluster/postgresql/extension/reconciler.go @@ -28,14 +28,14 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - xpcontroller "github.com/crossplane/crossplane-runtime/pkg/controller" - "github.com/crossplane/crossplane-runtime/pkg/event" - "github.com/crossplane/crossplane-runtime/pkg/feature" - "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" - "github.com/crossplane/crossplane-runtime/pkg/resource" - - "github.com/crossplane-contrib/provider-sql/apis/postgresql/v1alpha1" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/feature" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + "github.com/crossplane-contrib/provider-sql/apis/cluster/postgresql/v1alpha1" "github.com/crossplane-contrib/provider-sql/pkg/clients" "github.com/crossplane-contrib/provider-sql/pkg/clients/postgresql" "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" @@ -55,13 +55,27 @@ const ( maxConcurrency = 5 ) +// TODO(nateinaction): This looks wrong, can tracker creation be improved? +type tracker struct { + tracker *resource.LegacyProviderConfigUsageTracker +} + +var _ resource.Tracker = &tracker{} + +func (t *tracker) Track(ctx context.Context, mg resource.Managed) error { + return t.tracker.Track(ctx, mg.(resource.LegacyManaged)) +} + // Setup adds a controller that reconciles Extension managed resources. func Setup(mgr ctrl.Manager, o xpcontroller.Options) error { name := managed.ControllerName(v1alpha1.ExtensionGroupKind) - t := resource.NewProviderConfigUsageTracker(mgr.GetClient(), &v1alpha1.ProviderConfigUsage{}) + // This can only be a legacy tracker + t := resource.NewLegacyProviderConfigUsageTracker(mgr.GetClient(), &v1alpha1.ProviderConfigUsage{}) + trk := &tracker{tracker: t} + reconcilerOptions := []managed.ReconcilerOption{ - managed.WithExternalConnecter(&connector{kube: mgr.GetClient(), usage: t, newDB: postgresql.New}), + managed.WithTypedExternalConnector(&connector{kube: mgr.GetClient(), usage: trk, newDB: postgresql.New}), managed.WithLogger(o.Logger.WithValues("controller", name)), managed.WithPollInterval(o.PollInterval), managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), @@ -88,7 +102,9 @@ type connector struct { newDB func(creds map[string][]byte, database string, sslmode string) xsql.DB } -func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.ExternalClient, error) { +var _ managed.TypedExternalConnector[resource.Managed] = &connector{} + +func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.TypedExternalClient[resource.Managed], error) { cr, ok := mg.(*v1alpha1.Extension) if !ok { return nil, errors.New(errNotExtension) @@ -129,6 +145,8 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E type external struct{ db xsql.DB } +var _ managed.TypedExternalClient[resource.Managed] = &external{} + func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { cr, ok := mg.(*v1alpha1.Extension) if !ok { @@ -197,14 +215,18 @@ func (c *external) Update(_ context.Context, mg resource.Managed) (managed.Exter return managed.ExternalUpdate{}, nil } -func (c *external) Delete(ctx context.Context, mg resource.Managed) error { +func (c *external) Disconnect(ctx context.Context) error { + return nil +} + +func (c *external) Delete(ctx context.Context, mg resource.Managed) (managed.ExternalDelete, error) { cr, ok := mg.(*v1alpha1.Extension) if !ok { - return errors.New(errNotExtension) + return managed.ExternalDelete{}, errors.New(errNotExtension) } err := c.db.Exec(ctx, xsql.Query{String: "DROP EXTENSION IF EXISTS " + pq.QuoteIdentifier(cr.Spec.ForProvider.Extension)}) - return errors.Wrap(err, errDropExtension) + return managed.ExternalDelete{}, errors.Wrap(err, errDropExtension) } func upToDate(observed, desired v1alpha1.ExtensionParameters) bool { diff --git a/pkg/controller/postgresql/extension/reconciler_test.go b/pkg/controller/cluster/postgresql/extension/reconciler_test.go similarity index 97% rename from pkg/controller/postgresql/extension/reconciler_test.go rename to pkg/controller/cluster/postgresql/extension/reconciler_test.go index d675f016..a689cad5 100644 --- a/pkg/controller/postgresql/extension/reconciler_test.go +++ b/pkg/controller/cluster/postgresql/extension/reconciler_test.go @@ -21,16 +21,16 @@ import ( "database/sql" "testing" - "github.com/crossplane-contrib/provider-sql/apis/postgresql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/apis/cluster/postgresql/v1alpha1" "github.com/google/go-cmp/cmp" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" "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/crossplane-contrib/provider-sql/pkg/clients/xsql" ) @@ -501,7 +501,7 @@ func TestDelete(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { e := external{db: tc.fields.db} - 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/pkg/controller/postgresql/grant/reconciler.go b/pkg/controller/cluster/postgresql/grant/reconciler.go similarity index 87% rename from pkg/controller/postgresql/grant/reconciler.go rename to pkg/controller/cluster/postgresql/grant/reconciler.go index 7a236c09..bf8e4c84 100644 --- a/pkg/controller/postgresql/grant/reconciler.go +++ b/pkg/controller/cluster/postgresql/grant/reconciler.go @@ -29,14 +29,14 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - xpcontroller "github.com/crossplane/crossplane-runtime/pkg/controller" - "github.com/crossplane/crossplane-runtime/pkg/event" - "github.com/crossplane/crossplane-runtime/pkg/feature" - "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" - "github.com/crossplane/crossplane-runtime/pkg/resource" - - "github.com/crossplane-contrib/provider-sql/apis/postgresql/v1alpha1" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/feature" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + "github.com/crossplane-contrib/provider-sql/apis/cluster/postgresql/v1alpha1" "github.com/crossplane-contrib/provider-sql/pkg/clients" "github.com/crossplane-contrib/provider-sql/pkg/clients/postgresql" "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" @@ -64,13 +64,27 @@ const ( maxConcurrency = 5 ) +// TODO(nateinaction): This looks wrong, can tracker creation be improved? +type tracker struct { + tracker *resource.LegacyProviderConfigUsageTracker +} + +var _ resource.Tracker = &tracker{} + +func (t *tracker) Track(ctx context.Context, mg resource.Managed) error { + return t.tracker.Track(ctx, mg.(resource.LegacyManaged)) +} + // Setup adds a controller that reconciles Grant managed resources. func Setup(mgr ctrl.Manager, o xpcontroller.Options) error { name := managed.ControllerName(v1alpha1.GrantGroupKind) - t := resource.NewProviderConfigUsageTracker(mgr.GetClient(), &v1alpha1.ProviderConfigUsage{}) + // This can only be a legacy tracker + t := resource.NewLegacyProviderConfigUsageTracker(mgr.GetClient(), &v1alpha1.ProviderConfigUsage{}) + trk := &tracker{tracker: t} + reconcilerOptions := []managed.ReconcilerOption{ - managed.WithExternalConnecter(&connector{kube: mgr.GetClient(), usage: t, newDB: postgresql.New}), + managed.WithTypedExternalConnector(&connector{kube: mgr.GetClient(), usage: trk, newDB: postgresql.New}), managed.WithLogger(o.Logger.WithValues("controller", name)), managed.WithPollInterval(o.PollInterval), managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), @@ -97,7 +111,9 @@ type connector struct { newDB func(creds map[string][]byte, database string, sslmode string) xsql.DB } -func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.ExternalClient, error) { +var _ managed.TypedExternalConnector[resource.Managed] = &connector{} + +func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.TypedExternalClient[resource.Managed], error) { cr, ok := mg.(*v1alpha1.Grant) if !ok { return nil, errors.New(errNotGrant) @@ -137,6 +153,8 @@ type external struct { kube client.Client } +var _ managed.TypedExternalClient[resource.Managed] = &external{} + type grantType string const ( @@ -382,10 +400,14 @@ func (c *external) Update(ctx context.Context, mg resource.Managed) (managed.Ext return managed.ExternalUpdate{}, nil } -func (c *external) Delete(ctx context.Context, mg resource.Managed) error { +func (c *external) Disconnect(ctx context.Context) error { + return nil +} + +func (c *external) Delete(ctx context.Context, mg resource.Managed) (managed.ExternalDelete, error) { cr, ok := mg.(*v1alpha1.Grant) if !ok { - return errors.New(errNotGrant) + return managed.ExternalDelete{}, errors.New(errNotGrant) } var query xsql.Query @@ -393,8 +415,8 @@ func (c *external) Delete(ctx context.Context, mg resource.Managed) error { err := deleteGrantQuery(cr.Spec.ForProvider, &query) if err != nil { - return errors.Wrap(err, errRevokeGrant) + return managed.ExternalDelete{}, errors.Wrap(err, errRevokeGrant) } - return errors.Wrap(c.db.Exec(ctx, query), errRevokeGrant) + return managed.ExternalDelete{}, errors.Wrap(c.db.Exec(ctx, query), errRevokeGrant) } diff --git a/pkg/controller/postgresql/grant/reconciler_test.go b/pkg/controller/cluster/postgresql/grant/reconciler_test.go similarity index 97% rename from pkg/controller/postgresql/grant/reconciler_test.go rename to pkg/controller/cluster/postgresql/grant/reconciler_test.go index 8ab046db..071c2445 100644 --- a/pkg/controller/postgresql/grant/reconciler_test.go +++ b/pkg/controller/cluster/postgresql/grant/reconciler_test.go @@ -23,7 +23,7 @@ import ( "sort" "testing" - "github.com/crossplane-contrib/provider-sql/apis/postgresql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/apis/cluster/postgresql/v1alpha1" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/lib/pq" @@ -32,10 +32,10 @@ import ( "k8s.io/utils/ptr" "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/crossplane-contrib/provider-sql/pkg/clients/xsql" ) @@ -618,7 +618,7 @@ func TestDelete(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { e := external{db: tc.fields.db} - 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/pkg/controller/postgresql/postgresql.go b/pkg/controller/cluster/postgresql/postgresql.go similarity index 64% rename from pkg/controller/postgresql/postgresql.go rename to pkg/controller/cluster/postgresql/postgresql.go index 5af9b800..46a41f74 100644 --- a/pkg/controller/postgresql/postgresql.go +++ b/pkg/controller/cluster/postgresql/postgresql.go @@ -19,14 +19,14 @@ package postgresql import ( ctrl "sigs.k8s.io/controller-runtime" - "github.com/crossplane/crossplane-runtime/pkg/controller" - - "github.com/crossplane-contrib/provider-sql/pkg/controller/postgresql/config" - "github.com/crossplane-contrib/provider-sql/pkg/controller/postgresql/database" - "github.com/crossplane-contrib/provider-sql/pkg/controller/postgresql/extension" - "github.com/crossplane-contrib/provider-sql/pkg/controller/postgresql/grant" - "github.com/crossplane-contrib/provider-sql/pkg/controller/postgresql/role" - "github.com/crossplane-contrib/provider-sql/pkg/controller/postgresql/schema" + "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + + "github.com/crossplane-contrib/provider-sql/pkg/controller/cluster/postgresql/config" + "github.com/crossplane-contrib/provider-sql/pkg/controller/cluster/postgresql/database" + "github.com/crossplane-contrib/provider-sql/pkg/controller/cluster/postgresql/extension" + "github.com/crossplane-contrib/provider-sql/pkg/controller/cluster/postgresql/grant" + "github.com/crossplane-contrib/provider-sql/pkg/controller/cluster/postgresql/role" + "github.com/crossplane-contrib/provider-sql/pkg/controller/cluster/postgresql/schema" ) // Setup creates all PostgreSQL controllers with the supplied logger and adds diff --git a/pkg/controller/postgresql/role/reconciler.go b/pkg/controller/cluster/postgresql/role/reconciler.go similarity index 90% rename from pkg/controller/postgresql/role/reconciler.go rename to pkg/controller/cluster/postgresql/role/reconciler.go index 055e4b77..9e34d3e8 100644 --- a/pkg/controller/postgresql/role/reconciler.go +++ b/pkg/controller/cluster/postgresql/role/reconciler.go @@ -32,16 +32,16 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - xpcontroller "github.com/crossplane/crossplane-runtime/pkg/controller" - "github.com/crossplane/crossplane-runtime/pkg/event" - "github.com/crossplane/crossplane-runtime/pkg/feature" - "github.com/crossplane/crossplane-runtime/pkg/meta" - "github.com/crossplane/crossplane-runtime/pkg/password" - "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" - "github.com/crossplane/crossplane-runtime/pkg/resource" - - "github.com/crossplane-contrib/provider-sql/apis/postgresql/v1alpha1" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/feature" + "github.com/crossplane/crossplane-runtime/v2/pkg/meta" + "github.com/crossplane/crossplane-runtime/v2/pkg/password" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + "github.com/crossplane-contrib/provider-sql/apis/cluster/postgresql/v1alpha1" "github.com/crossplane-contrib/provider-sql/pkg/clients" "github.com/crossplane-contrib/provider-sql/pkg/clients/postgresql" "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" @@ -65,13 +65,27 @@ const ( maxConcurrency = 5 ) +// TODO(nateinaction): This looks wrong, can tracker creation be improved? +type tracker struct { + tracker *resource.LegacyProviderConfigUsageTracker +} + +var _ resource.Tracker = &tracker{} + +func (t *tracker) Track(ctx context.Context, mg resource.Managed) error { + return t.tracker.Track(ctx, mg.(resource.LegacyManaged)) +} + // Setup adds a controller that reconciles Role managed resources. func Setup(mgr ctrl.Manager, o xpcontroller.Options) error { name := managed.ControllerName(v1alpha1.RoleGroupKind) - t := resource.NewProviderConfigUsageTracker(mgr.GetClient(), &v1alpha1.ProviderConfigUsage{}) + // This can only be a legacy tracker + t := resource.NewLegacyProviderConfigUsageTracker(mgr.GetClient(), &v1alpha1.ProviderConfigUsage{}) + trk := &tracker{tracker: t} + reconcilerOptions := []managed.ReconcilerOption{ - managed.WithExternalConnecter(&connector{kube: mgr.GetClient(), usage: t, newDB: postgresql.New}), + managed.WithTypedExternalConnector(&connector{kube: mgr.GetClient(), usage: trk, newDB: postgresql.New}), managed.WithLogger(o.Logger.WithValues("controller", name)), managed.WithPollInterval(o.PollInterval), managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), @@ -98,7 +112,9 @@ type connector struct { newDB func(creds map[string][]byte, database string, sslmode string) xsql.DB } -func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.ExternalClient, error) { +var _ managed.TypedExternalConnector[resource.Managed] = &connector{} + +func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.TypedExternalClient[resource.Managed], error) { cr, ok := mg.(*v1alpha1.Role) if !ok { return nil, errors.New(errNotRole) @@ -139,6 +155,8 @@ type external struct { kube client.Client } +var _ managed.TypedExternalClient[resource.Managed] = &external{} + func negateClause(clause string, negate *bool, out *[]string) { // If clause boolean is not set (nil pointer), do not push a setting. // This means the postgres default is applied. @@ -423,16 +441,16 @@ func (c *external) Update(ctx context.Context, mg resource.Managed) (managed.Ext return managed.ExternalUpdate{}, 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.(*v1alpha1.Role) if !ok { - return errors.New(errNotRole) + return managed.ExternalDelete{}, errors.New(errNotRole) } cr.SetConditions(xpv1.Deleting()) err := c.db.Exec(ctx, xsql.Query{ String: "DROP ROLE IF EXISTS " + pq.QuoteIdentifier(meta.GetExternalName(cr)), }) - return errors.Wrap(err, errDropRole) + return managed.ExternalDelete{}, errors.Wrap(err, errDropRole) } func upToDate(observed *v1alpha1.RoleParameters, desired *v1alpha1.RoleParameters) bool { @@ -505,3 +523,7 @@ func lateInit(observed *v1alpha1.RoleParameters, desired *v1alpha1.RoleParameter return li } + +func (c *external) Disconnect(ctx context.Context) error { + return nil +} diff --git a/pkg/controller/postgresql/role/reconciler_test.go b/pkg/controller/cluster/postgresql/role/reconciler_test.go similarity index 98% rename from pkg/controller/postgresql/role/reconciler_test.go rename to pkg/controller/cluster/postgresql/role/reconciler_test.go index f151a4ee..d2cc358b 100644 --- a/pkg/controller/postgresql/role/reconciler_test.go +++ b/pkg/controller/cluster/postgresql/role/reconciler_test.go @@ -22,7 +22,7 @@ import ( "fmt" "testing" - "github.com/crossplane-contrib/provider-sql/apis/postgresql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/apis/cluster/postgresql/v1alpha1" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/lib/pq" @@ -32,11 +32,11 @@ import ( "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - "github.com/crossplane/crossplane-runtime/pkg/meta" - "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/meta" + "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/crossplane-contrib/provider-sql/pkg/clients/xsql" ) @@ -1133,7 +1133,7 @@ func TestDelete(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { e := external{db: tc.fields.db} - 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/pkg/controller/postgresql/role/utils.go b/pkg/controller/cluster/postgresql/role/utils.go similarity index 90% rename from pkg/controller/postgresql/role/utils.go rename to pkg/controller/cluster/postgresql/role/utils.go index 421fb7d1..94d89f05 100644 --- a/pkg/controller/postgresql/role/utils.go +++ b/pkg/controller/cluster/postgresql/role/utils.go @@ -24,10 +24,10 @@ import ( "github.com/pkg/errors" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - "github.com/crossplane/crossplane-runtime/pkg/resource" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" - "github.com/crossplane-contrib/provider-sql/apis/postgresql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/apis/cluster/postgresql/v1alpha1" ) func (c *external) getPassword(ctx context.Context, role *v1alpha1.Role) (newPwd string, changed bool, err error) { diff --git a/pkg/controller/postgresql/schema/reconciler.go b/pkg/controller/cluster/postgresql/schema/reconciler.go similarity index 83% rename from pkg/controller/postgresql/schema/reconciler.go rename to pkg/controller/cluster/postgresql/schema/reconciler.go index d60cbe13..ec54f92b 100644 --- a/pkg/controller/postgresql/schema/reconciler.go +++ b/pkg/controller/cluster/postgresql/schema/reconciler.go @@ -28,15 +28,15 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - xpcontroller "github.com/crossplane/crossplane-runtime/pkg/controller" - "github.com/crossplane/crossplane-runtime/pkg/event" - "github.com/crossplane/crossplane-runtime/pkg/feature" - "github.com/crossplane/crossplane-runtime/pkg/meta" - "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" - "github.com/crossplane/crossplane-runtime/pkg/resource" - - "github.com/crossplane-contrib/provider-sql/apis/postgresql/v1alpha1" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/feature" + "github.com/crossplane/crossplane-runtime/v2/pkg/meta" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + "github.com/crossplane-contrib/provider-sql/apis/cluster/postgresql/v1alpha1" "github.com/crossplane-contrib/provider-sql/pkg/clients" "github.com/crossplane-contrib/provider-sql/pkg/clients/postgresql" "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" @@ -58,13 +58,27 @@ const ( maxConcurrency = 5 ) +// TODO(nateinaction): This looks wrong, can tracker creation be improved? +type tracker struct { + tracker *resource.LegacyProviderConfigUsageTracker +} + +var _ resource.Tracker = &tracker{} + +func (t *tracker) Track(ctx context.Context, mg resource.Managed) error { + return t.tracker.Track(ctx, mg.(resource.LegacyManaged)) +} + // Setup adds a controller that reconciles Schema managed resources. func Setup(mgr ctrl.Manager, o xpcontroller.Options) error { name := managed.ControllerName(v1alpha1.SchemaGroupKind) - t := resource.NewProviderConfigUsageTracker(mgr.GetClient(), &v1alpha1.ProviderConfigUsage{}) + // This can only be a legacy tracker + t := resource.NewLegacyProviderConfigUsageTracker(mgr.GetClient(), &v1alpha1.ProviderConfigUsage{}) + trk := &tracker{tracker: t} + reconcilerOptions := []managed.ReconcilerOption{ - managed.WithExternalConnecter(&connector{kube: mgr.GetClient(), usage: t, newDB: postgresql.New}), + managed.WithTypedExternalConnector(&connector{kube: mgr.GetClient(), usage: trk, newDB: postgresql.New}), managed.WithLogger(o.Logger.WithValues("controller", name)), managed.WithPollInterval(o.PollInterval), managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), @@ -85,13 +99,15 @@ func Setup(mgr ctrl.Manager, o xpcontroller.Options) error { Complete(r) } +var _ managed.TypedExternalConnector[resource.Managed] = &connector{} + type connector struct { kube client.Client usage resource.Tracker newDB func(creds map[string][]byte, database string, sslmode string) xsql.DB } -func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.ExternalClient, error) { +func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.TypedExternalClient[resource.Managed], error) { cr, ok := mg.(*v1alpha1.Schema) if !ok { return nil, errors.New(errNotSchema) @@ -128,8 +144,14 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E return &external{db: c.newDB(s.Data, *cr.Spec.ForProvider.Database, clients.ToString(pc.Spec.SSLMode))}, nil } +var _ managed.TypedExternalClient[resource.Managed] = &external{} + type external struct{ db xsql.DB } +func (c *external) Disconnect(ctx context.Context) error { + return nil +} + func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { cr, ok := mg.(*v1alpha1.Schema) if !ok { @@ -201,14 +223,14 @@ func (c *external) Update(ctx context.Context, mg resource.Managed) (managed.Ext return managed.ExternalUpdate{}, errors.Wrap(err, errAlterSchema) } -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.(*v1alpha1.Schema) if !ok { - return errors.New(errNotSchema) + return managed.ExternalDelete{}, errors.New(errNotSchema) } err := c.db.Exec(ctx, xsql.Query{String: "DROP SCHEMA IF EXISTS " + pq.QuoteIdentifier(meta.GetExternalName(cr))}) - return errors.Wrap(err, errDropSchema) + return managed.ExternalDelete{}, errors.Wrap(err, errDropSchema) } func upToDate(observed, desired v1alpha1.SchemaParameters) bool { diff --git a/pkg/controller/postgresql/schema/reconciler_test.go b/pkg/controller/cluster/postgresql/schema/reconciler_test.go similarity index 96% rename from pkg/controller/postgresql/schema/reconciler_test.go rename to pkg/controller/cluster/postgresql/schema/reconciler_test.go index d2c493e5..2f467141 100644 --- a/pkg/controller/postgresql/schema/reconciler_test.go +++ b/pkg/controller/cluster/postgresql/schema/reconciler_test.go @@ -21,18 +21,18 @@ import ( "database/sql" "testing" - "github.com/crossplane-contrib/provider-sql/apis/postgresql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/apis/cluster/postgresql/v1alpha1" "github.com/google/go-cmp/cmp" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" - xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" - "github.com/crossplane/crossplane-runtime/pkg/meta" - "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/meta" + "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/crossplane-contrib/provider-sql/pkg/clients/xsql" ) @@ -544,7 +544,7 @@ func TestDelete(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { e := external{db: tc.fields.db} - 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/pkg/controller/namespaced/errors/errors.go b/pkg/controller/namespaced/errors/errors.go new file mode 100644 index 00000000..1d9dc7a3 --- /dev/null +++ b/pkg/controller/namespaced/errors/errors.go @@ -0,0 +1,57 @@ +package errors + +import ( + "fmt" +) + +const ( + errGetProviderConfig = "cannot get ProviderConfig: %s" + errGetClusterProviderConfig = "cannot get ClusterProviderConfig: %s" + errGetSecret = "cannot get credentials Secret: %s" + errInvalidProviderConfigKind = "invalid ProviderConfig kind: %s" + errNoSecretRef = "providerConfig does not reference a credentials Secret" +) + +func GetProviderConfigError(err error) error { return ErrGetProviderConfig{err} } + +type ErrGetProviderConfig struct{ error } + +func (e ErrGetProviderConfig) Error() string { + return fmt.Sprintf(errGetProviderConfig, e.error) +} + +func (e ErrGetProviderConfig) Unwrap() error { return e.error } + +func GetClusterProviderConfigError(err error) error { return ErrGetClusterProviderConfig{err} } + +type ErrGetClusterProviderConfig struct{ error } + +func (e ErrGetClusterProviderConfig) Error() string { + return fmt.Sprintf(errGetClusterProviderConfig, e.error) +} + +func (e ErrGetClusterProviderConfig) Unwrap() error { return e.error } + +func GetSecretError(err error) error { return ErrGetSecret{err} } + +type ErrGetSecret struct{ error } + +func (e ErrGetSecret) Error() string { + return fmt.Sprintf(errGetSecret, e.error) +} + +func (e ErrGetSecret) Unwrap() error { return e.error } + +func InvalidProviderConfigKindError(kind string) error { return ErrInvalidProviderConfigKind{kind} } + +type ErrInvalidProviderConfigKind struct{ kind string } + +func (e ErrInvalidProviderConfigKind) Error() string { + return fmt.Sprintf(errInvalidProviderConfigKind, e.kind) +} + +func MissingSecretRefError() error { return ErrNoSecretRef{} } + +type ErrNoSecretRef struct{} + +func (e ErrNoSecretRef) Error() string { return errNoSecretRef } diff --git a/pkg/controller/namespaced/mssql/config/config.go b/pkg/controller/namespaced/mssql/config/config.go new file mode 100644 index 00000000..772686c1 --- /dev/null +++ b/pkg/controller/namespaced/mssql/config/config.go @@ -0,0 +1,48 @@ +/* +Copyright 2021 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 ( + 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/reconciler/providerconfig" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + "github.com/crossplane-contrib/provider-sql/apis/namespaced/mssql/v1alpha1" +) + +// Setup adds a controller that reconciles ProviderConfigs by accounting for +// their current usage. +func Setup(mgr ctrl.Manager, o controller.Options) error { + name := providerconfig.ControllerName(v1alpha1.ProviderConfigGroupKind) + + of := resource.ProviderConfigKinds{ + Config: v1alpha1.ProviderConfigGroupVersionKind, + Usage: v1alpha1.ProviderConfigUsageGroupVersionKind, + UsageList: v1alpha1.ProviderConfigUsageListGroupVersionKind, + } + + return ctrl.NewControllerManagedBy(mgr). + Named(name). + For(&v1alpha1.ProviderConfig{}). + Watches(&v1alpha1.ProviderConfigUsage{}, &resource.EnqueueRequestForProviderConfig{}). + Complete(providerconfig.NewReconciler(mgr, of, + providerconfig.WithLogger(o.Logger.WithValues("controller", name)), + providerconfig.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))))) +} diff --git a/pkg/controller/namespaced/mssql/database/reconciler.go b/pkg/controller/namespaced/mssql/database/reconciler.go new file mode 100644 index 00000000..cf2d3fa6 --- /dev/null +++ b/pkg/controller/namespaced/mssql/database/reconciler.go @@ -0,0 +1,182 @@ +/* +Copyright 2021 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 database + +import ( + "context" + + "github.com/pkg/errors" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/feature" + "github.com/crossplane/crossplane-runtime/v2/pkg/meta" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + namespacedv1alpha1 "github.com/crossplane-contrib/provider-sql/apis/namespaced/mssql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/pkg/clients/mssql" + "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/mssql/provider" +) + +const ( + errTrackUsage = "cannot track ProviderConfig usage" + errTrackPCUsage = "cannot track ProviderConfig usage" + + errNotDatabase = "managed resource is not a Database custom resource" + errSelectDB = "cannot select database" + errCreateDB = "cannot create database" + errDropDB = "cannot drop database" + + maxConcurrency = 5 +) + +// TODO(nateinaction): This looks wrong, can tracker creation be improved? +type tracker struct { + tracker *resource.ProviderConfigUsageTracker +} + +var _ resource.Tracker = &tracker{} + +func (t *tracker) Track(ctx context.Context, mg resource.Managed) error { + return t.tracker.Track(ctx, mg.(resource.ModernManaged)) +} + +// Setup adds a controller that reconciles Database managed resources. +func Setup(mgr ctrl.Manager, o xpcontroller.Options) error { + name := managed.ControllerName(namespacedv1alpha1.DatabaseGroupKind) + + t := resource.NewProviderConfigUsageTracker(mgr.GetClient(), &namespacedv1alpha1.ProviderConfigUsage{}) + trk := &tracker{tracker: t} + + reconcilerOptions := []managed.ReconcilerOption{ + managed.WithTypedExternalConnector(&connector{kube: mgr.GetClient(), usage: trk, newClient: mssql.New}), + managed.WithLogger(o.Logger.WithValues("controller", name)), + managed.WithPollInterval(o.PollInterval), + managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), + } + if o.Features.Enabled(feature.EnableBetaManagementPolicies) { + reconcilerOptions = append(reconcilerOptions, managed.WithManagementPolicies()) + } + r := managed.NewReconciler(mgr, + resource.ManagedKind(namespacedv1alpha1.DatabaseGroupVersionKind), + reconcilerOptions..., + ) + return ctrl.NewControllerManagedBy(mgr). + Named(name). + For(&namespacedv1alpha1.Database{}). + WithOptions(controller.Options{ + MaxConcurrentReconciles: maxConcurrency, + }). + Complete(r) +} + +type connector struct { + kube client.Client + usage resource.Tracker + newClient func(creds map[string][]byte, database string) xsql.DB +} + +var _ managed.TypedExternalConnector[resource.Managed] = &connector{} + +func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.TypedExternalClient[resource.Managed], error) { + cr, ok := mg.(*namespacedv1alpha1.Database) + if !ok { + return nil, errors.New(errNotDatabase) + } + + if err := c.usage.Track(ctx, mg); err != nil { + return nil, errors.Wrap(err, errTrackPCUsage) + } + + // ProviderConfigReference could theoretically be nil, but in practice the + // DefaultProviderConfig initializer will set it before we get here. + providerInfo, err := provider.GetProviderConfig(ctx, c.kube, cr) + if err != nil { + return nil, err + } + + return &external{db: c.newClient(providerInfo.SecretData, "")}, nil +} + +type external struct{ db xsql.DB } + +var _ managed.TypedExternalClient[resource.Managed] = &external{} + +func (c *external) Disconnect(ctx context.Context) error { + // Do we need to implement this? Clean up any db connections? + // The xsql.DB interface does not have a Disconnect method. + return nil +} + +func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { + cr, ok := mg.(*namespacedv1alpha1.Database) + if !ok { + return managed.ExternalObservation{}, errors.New(errNotDatabase) + } + + var name string + query := "SELECT name FROM master.sys.databases WHERE name = @p1" + err := c.db.Scan(ctx, xsql.Query{String: query, Parameters: []interface{}{meta.GetExternalName(cr)}}, &name) + if xsql.IsNoRows(err) { + return managed.ExternalObservation{ResourceExists: false}, nil + } + if err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, errSelectDB) + } + + cr.SetConditions(xpv1.Available()) + + return managed.ExternalObservation{ + ResourceExists: true, + + // TODO(turkenh): Support these when we have anything to update. + ResourceLateInitialized: false, + ResourceUpToDate: true, + }, nil +} + +func (c *external) Create(ctx context.Context, mg resource.Managed) (managed.ExternalCreation, error) { + + cr, ok := mg.(*namespacedv1alpha1.Database) + if !ok { + return managed.ExternalCreation{}, errors.New(errNotDatabase) + } + + err := c.db.Exec(ctx, xsql.Query{String: "CREATE DATABASE " + mssql.QuoteIdentifier(meta.GetExternalName(cr))}) + return managed.ExternalCreation{}, errors.Wrap(err, errCreateDB) +} + +func (c *external) Update(_ context.Context, _ resource.Managed) (managed.ExternalUpdate, error) { + // TODO(turkenh): Support updates once we have anything to update. + return managed.ExternalUpdate{}, nil +} + +func (c *external) Delete(ctx context.Context, mg resource.Managed) (managed.ExternalDelete, error) { + cr, ok := mg.(*namespacedv1alpha1.Database) + if !ok { + return managed.ExternalDelete{}, errors.New(errNotDatabase) + } + + err := c.db.Exec(ctx, xsql.Query{String: "DROP DATABASE IF EXISTS " + mssql.QuoteIdentifier(meta.GetExternalName(cr))}) + return managed.ExternalDelete{}, errors.Wrap(err, errDropDB) +} diff --git a/pkg/controller/namespaced/mssql/database/reconciler_test.go b/pkg/controller/namespaced/mssql/database/reconciler_test.go new file mode 100644 index 00000000..39254c0a --- /dev/null +++ b/pkg/controller/namespaced/mssql/database/reconciler_test.go @@ -0,0 +1,457 @@ +/* +Copyright 2021 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 database + +import ( + "context" + "database/sql" + "testing" + + "github.com/crossplane-contrib/provider-sql/apis/namespaced/mssql/v1alpha1" + "github.com/google/go-cmp/cmp" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/crossplane/crossplane-runtime/v2/apis/common" + xpv2 "github.com/crossplane/crossplane-runtime/v2/apis/common/v2" + "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/crossplane-contrib/provider-sql/pkg/clients/xsql" + provErrors "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/errors" +) + +type mockDB struct { + MockExec func(ctx context.Context, q xsql.Query) error + MockScan func(ctx context.Context, q xsql.Query, dest ...interface{}) error + MockExecTx func(ctx context.Context, ql []xsql.Query) error + MockGetConnectionDetails func(username, password string) managed.ConnectionDetails +} + +func (m mockDB) ExecTx(ctx context.Context, ql []xsql.Query) error { + return m.MockExecTx(ctx, ql) +} +func (m mockDB) Exec(ctx context.Context, q xsql.Query) error { + return m.MockExec(ctx, q) +} +func (m mockDB) Scan(ctx context.Context, q xsql.Query, dest ...interface{}) error { + return m.MockScan(ctx, q, dest...) +} +func (m mockDB) Query(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return &sql.Rows{}, nil +} +func (m mockDB) GetConnectionDetails(username, password string) managed.ConnectionDetails { + return m.MockGetConnectionDetails(username, password) +} + +func TestConnect(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + kube client.Client + usage resource.Tracker + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + cases := map[string]struct { + reason string + fields fields + args args + want error + }{ + "ErrNotDatabase": { + reason: "An error should be returned if the managed resource is not a *Database", + args: args{ + mg: nil, + }, + want: errors.New(errNotDatabase), + }, + "ErrTrackProviderConfigUsage": { + reason: "An error should be returned if we can't track our ProviderConfig usage", + fields: fields{ + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return errBoom }), + }, + args: args{ + mg: &v1alpha1.Database{}, + }, + want: errors.Wrap(errBoom, errTrackPCUsage), + }, + "InvalidProviderConfigKind": { + reason: "An error should be returned if our ProviderConfig has an invalid kind", + fields: fields{ + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Database{ + Spec: v1alpha1.DatabaseSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: "Invalid", + }, + }, + }, + }, + }, + want: provErrors.InvalidProviderConfigKindError("Invalid"), + }, + "ErrGetProviderConfig": { + reason: "An error should be returned if we can't get our ProviderConfig", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(errBoom), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Database{ + Spec: v1alpha1.DatabaseSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetProviderConfigError(errBoom), + }, + "ErrGetClusterProviderConfig": { + reason: "An error should be returned if we can't get our ClusterProviderConfig", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(errBoom), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Database{ + Spec: v1alpha1.DatabaseSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ClusterProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetClusterProviderConfigError(errBoom), + }, + "ErrMissingConnectionSecret": { + reason: "An error should be returned if our ProviderConfig doesn't specify a connection secret", + fields: fields{ + kube: &test.MockClient{ + // We call get to populate the Database struct, then again + // to populate the (empty) ProviderConfig struct, resulting + // in a ProviderConfig with a nil connection secret. + MockGet: test.NewMockGetFn(nil), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Database{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.DatabaseSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.MissingSecretRefError(), + }, + "ErrGetConnectionSecret": { + reason: "An error should be returned if we can't get our ProviderConfig's connection secret", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + switch o := obj.(type) { + case *v1alpha1.ProviderConfig: + o.Spec.Credentials.ConnectionSecretRef = common.LocalSecretReference{Name: "example"} + case *corev1.Secret: + return errBoom + } + return nil + }), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Database{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.DatabaseSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetSecretError(errBoom), + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := &connector{kube: tc.fields.kube, usage: tc.fields.usage} + _, err := e.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") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + o managed.ExternalObservation + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotDatabase": { + reason: "An error should be returned if the managed resource is not a *Database", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotDatabase), + }, + }, + "ErrNoDatabase": { + reason: "We should return ResourceExists: false when no database is found", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return sql.ErrNoRows }, + }, + }, + args: args{ + mg: &v1alpha1.Database{}, + }, + want: want{ + o: managed.ExternalObservation{ResourceExists: false}, + }, + }, + "ErrSelectDatabase": { + reason: "We should return any errors encountered while trying to select the database", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return errBoom }, + }, + }, + args: args{ + mg: &v1alpha1.Database{}, + }, + want: want{ + err: errors.Wrap(errBoom, errSelectDB), + }, + }, + "Success": { + reason: "We should return no error if we can successfully select our database", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.Database{}, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + ResourceLateInitialized: false, + }, + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + 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) + } + }) + } +} + +func TestCreate(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + c managed.ExternalCreation + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotDatabase": { + reason: "An error should be returned if the managed resource is not a *Database", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotDatabase), + }, + }, + "ErrExec": { + reason: "Any errors encountered while creating the database should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return errBoom }, + }, + }, + args: args{ + mg: &v1alpha1.Database{}, + }, + want: want{ + err: errors.Wrap(errBoom, errCreateDB), + }, + }, + "Success": { + reason: "No error should be returned when we successfully create a database", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.Database{}, + }, + want: want{ + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + 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) + } + }) + } +} + +func TestDelete(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + cases := map[string]struct { + reason string + fields fields + args args + want error + }{ + "ErrNotDatabase": { + reason: "An error should be returned if the managed resource is not a *Database", + args: args{ + mg: nil, + }, + want: errors.New(errNotDatabase), + }, + "ErrDropDB": { + reason: "Errors dropping a database should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + return errBoom + }, + }, + }, + args: args{ + mg: &v1alpha1.Database{}, + }, + want: errors.Wrap(errBoom, errDropDB), + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + _, 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/pkg/controller/namespaced/mssql/grant/reconciler.go b/pkg/controller/namespaced/mssql/grant/reconciler.go new file mode 100644 index 00000000..e574293f --- /dev/null +++ b/pkg/controller/namespaced/mssql/grant/reconciler.go @@ -0,0 +1,303 @@ +/* +Copyright 2021 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 grant + +import ( + "context" + "fmt" + "sort" + "strings" + + "github.com/pkg/errors" + "k8s.io/utils/ptr" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/feature" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + namespacedv1alpha1 "github.com/crossplane-contrib/provider-sql/apis/namespaced/mssql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/pkg/clients/mssql" + "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/mssql/provider" +) + +const ( + errTrackPCUsage = "cannot track ProviderConfig usage" + + errNotGrant = "managed resource is not a Grant custom resource" + errGrant = "cannot grant" + errRevoke = "cannot revoke" + errCannotGetGrants = "cannot get current grants" + + maxConcurrency = 5 +) + +// TODO(nateinaction): This looks wrong, can tracker creation be improved? +type tracker struct { + tracker *resource.ProviderConfigUsageTracker +} + +var _ resource.Tracker = &tracker{} + +func (t *tracker) Track(ctx context.Context, mg resource.Managed) error { + return t.tracker.Track(ctx, mg.(resource.ModernManaged)) +} + +// Setup adds a controller that reconciles Database managed resources. +func Setup(mgr ctrl.Manager, o xpcontroller.Options) error { + name := managed.ControllerName(namespacedv1alpha1.GrantGroupKind) + + t := resource.NewProviderConfigUsageTracker(mgr.GetClient(), &namespacedv1alpha1.ProviderConfigUsage{}) + trk := &tracker{tracker: t} + + reconcilerOptions := []managed.ReconcilerOption{ + managed.WithTypedExternalConnector(&connector{kube: mgr.GetClient(), usage: trk, newClient: mssql.New}), + managed.WithReferenceResolver(managed.NewAPISimpleReferenceResolver(mgr.GetClient())), + managed.WithLogger(o.Logger.WithValues("controller", name)), + managed.WithPollInterval(o.PollInterval), + managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), + } + if o.Features.Enabled(feature.EnableBetaManagementPolicies) { + reconcilerOptions = append(reconcilerOptions, managed.WithManagementPolicies()) + } + r := managed.NewReconciler(mgr, + resource.ManagedKind(namespacedv1alpha1.GrantGroupVersionKind), + reconcilerOptions..., + ) + return ctrl.NewControllerManagedBy(mgr). + Named(name). + For(&namespacedv1alpha1.Grant{}). + WithOptions(controller.Options{ + MaxConcurrentReconciles: maxConcurrency, + }). + Complete(r) +} + +type connector struct { + kube client.Client + usage resource.Tracker + newClient func(creds map[string][]byte, database string) xsql.DB +} + +var _ managed.TypedExternalConnector[resource.Managed] = &connector{} + +func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.TypedExternalClient[resource.Managed], error) { + cr, ok := mg.(*namespacedv1alpha1.Grant) + if !ok { + return nil, errors.New(errNotGrant) + } + + if err := c.usage.Track(ctx, mg); err != nil { + return nil, errors.Wrap(err, errTrackPCUsage) + } + + // ProviderConfigReference could theoretically be nil, but in practice the + // DefaultProviderConfig initializer will set it before we get here. + providerInfo, err := provider.GetProviderConfig(ctx, c.kube, cr) + if err != nil { + return nil, err + } + + return &external{db: c.newClient(providerInfo.SecretData, ptr.Deref(cr.Spec.ForProvider.Database, ""))}, nil +} + +type external struct{ db xsql.DB } + +var _ managed.TypedExternalClient[resource.Managed] = &external{} + +func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { + cr, ok := mg.(*namespacedv1alpha1.Grant) + if !ok { + return managed.ExternalObservation{}, errors.New(errNotGrant) + } + + permissions, err := c.getPermissions(ctx, cr) + if err != nil { + return managed.ExternalObservation{}, err + } + if len(permissions) == 0 { + return managed.ExternalObservation{}, nil + } + + cr.SetConditions(xpv1.Available()) + + g, r := diffPermissions(cr.Spec.ForProvider.Permissions.ToStringSlice(), permissions) + return managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: len(g) == 0 && len(r) == 0, + }, nil +} + +func (c *external) Create(ctx context.Context, mg resource.Managed) (managed.ExternalCreation, error) { + cr, ok := mg.(*namespacedv1alpha1.Grant) + if !ok { + return managed.ExternalCreation{}, errors.New(errNotGrant) + } + + username := *cr.Spec.ForProvider.User + permissions := strings.Join(cr.Spec.ForProvider.Permissions.ToStringSlice(), ", ") + + query := fmt.Sprintf("GRANT %s %s TO %s", permissions, onSchemaQuery(cr), mssql.QuoteIdentifier(username)) + return managed.ExternalCreation{}, errors.Wrap(c.db.Exec(ctx, xsql.Query{String: query}), errGrant) +} + +func (c *external) Update(ctx context.Context, mg resource.Managed) (managed.ExternalUpdate, error) { + cr, ok := mg.(*namespacedv1alpha1.Grant) + if !ok { + return managed.ExternalUpdate{}, errors.New(errNotGrant) + } + + observed, err := c.getPermissions(ctx, cr) + if err != nil { + return managed.ExternalUpdate{}, err + } + desired := cr.Spec.ForProvider.Permissions.ToStringSlice() + toGrant, toRevoke := diffPermissions(desired, observed) + + if len(toRevoke) > 0 { + sort.Strings(toRevoke) + query := fmt.Sprintf("REVOKE %s %s FROM %s", + strings.Join(toRevoke, ", "), onSchemaQuery(cr), mssql.QuoteIdentifier(*cr.Spec.ForProvider.User)) + if err = c.db.Exec(ctx, xsql.Query{String: query}); err != nil { + return managed.ExternalUpdate{}, errors.Wrap(err, errRevoke) + } + } + if len(toGrant) > 0 { + sort.Strings(toGrant) + query := fmt.Sprintf("GRANT %s %s TO %s", + strings.Join(toGrant, ", "), onSchemaQuery(cr), mssql.QuoteIdentifier(*cr.Spec.ForProvider.User)) + if err = c.db.Exec(ctx, xsql.Query{String: query}); err != nil { + return managed.ExternalUpdate{}, errors.Wrap(err, errGrant) + } + } + return managed.ExternalUpdate{}, nil +} + +func (c *external) Disconnect(ctx context.Context) error { + return nil +} + +func (c *external) Delete(ctx context.Context, mg resource.Managed) (managed.ExternalDelete, error) { + cr, ok := mg.(*namespacedv1alpha1.Grant) + if !ok { + return managed.ExternalDelete{}, errors.New(errNotGrant) + } + + username := *cr.Spec.ForProvider.User + + query := fmt.Sprintf("REVOKE %s %s FROM %s", + strings.Join(cr.Spec.ForProvider.Permissions.ToStringSlice(), ", "), + onSchemaQuery(cr), + mssql.QuoteIdentifier(username), + ) + return managed.ExternalDelete{}, errors.Wrap(c.db.Exec(ctx, xsql.Query{String: query}), errRevoke) +} + +// TODO(turkenh/ulucinar): Possible performance improvement. We first +// +// calculate the Cartesian product, and then filter. It would be more +// efficient to first filter principals by name, and then join. +const queryPermissionDefault = `SELECT pe.permission_name + FROM sys.database_principals AS pr + JOIN sys.database_permissions AS pe + ON pe.grantee_principal_id = pr.principal_id + WHERE + pe.class = 0 /* DATABASE (default) */ + AND pr.name = %s` + +const queryPermissionSchema = `SELECT pe.permission_name + FROM sys.database_principals AS pr + JOIN sys.database_permissions AS pe + ON pe.grantee_principal_id = pr.principal_id + JOIN sys.schemas AS s + ON s.schema_id = pe.major_id + WHERE + pe.class = 3 /* SCHEMA */ + AND s.name = %s + AND pr.name = %s` + +func (c *external) getPermissions(ctx context.Context, cr *namespacedv1alpha1.Grant) ([]string, error) { + var query string + if cr.Spec.ForProvider.Schema == nil { + query = fmt.Sprintf(queryPermissionDefault, mssql.QuoteValue(*cr.Spec.ForProvider.User)) + } else { + query = fmt.Sprintf(queryPermissionSchema, + mssql.QuoteValue(*cr.Spec.ForProvider.Schema), + mssql.QuoteValue(*cr.Spec.ForProvider.User), + ) + } + rows, err := c.db.Query(ctx, xsql.Query{String: query}) + if err != nil { + return nil, errors.Wrap(err, errCannotGetGrants) + } + defer rows.Close() //nolint:errcheck + + var permissions []string + for rows.Next() { + var grant string + if err := rows.Scan(&grant); err != nil { + return nil, errors.Wrap(err, errCannotGetGrants) + } + permissions = append(permissions, grant) + } + if err := rows.Err(); err != nil { + return nil, errors.Wrap(err, errCannotGetGrants) + } + return permissions, nil +} + +func onSchemaQuery(cr *namespacedv1alpha1.Grant) (schema string) { + if cr.Spec.ForProvider.Schema != nil { + schema = fmt.Sprintf("ON SCHEMA::%s", *cr.Spec.ForProvider.Schema) + } + return +} + +func diffPermissions(desired, observed []string) ([]string, []string) { + md := make(map[string]struct{}, len(desired)) + mo := make(map[string]struct{}, len(observed)) + + for _, v := range desired { + md[v] = struct{}{} + } + for _, v := range observed { + mo[v] = struct{}{} + } + + var toGrant []string + var toRevoke []string + + for p := range md { + if _, ok := mo[p]; !ok { + toGrant = append(toGrant, p) + } + } + + for p := range mo { + if _, ok := md[p]; !ok { + toRevoke = append(toRevoke, p) + } + } + + return toGrant, toRevoke +} diff --git a/pkg/controller/namespaced/mssql/grant/reconciler_test.go b/pkg/controller/namespaced/mssql/grant/reconciler_test.go new file mode 100644 index 00000000..ddb2bed4 --- /dev/null +++ b/pkg/controller/namespaced/mssql/grant/reconciler_test.go @@ -0,0 +1,953 @@ +/* +Copyright 2021 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 grant + +import ( + "context" + "database/sql" + "strings" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/crossplane/crossplane-runtime/v2/apis/common" + xpv2 "github.com/crossplane/crossplane-runtime/v2/apis/common/v2" + "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/crossplane-contrib/provider-sql/apis/namespaced/mssql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" + provErrors "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/errors" +) + +type mockDB struct { + MockExec func(ctx context.Context, q xsql.Query) error + MockExecTx func(ctx context.Context, ql []xsql.Query) error + MockScan func(ctx context.Context, q xsql.Query, dest ...interface{}) error + MockQuery func(ctx context.Context, q xsql.Query) (*sql.Rows, error) + MockGetConnectionDetails func(username, password string) managed.ConnectionDetails +} + +func (m mockDB) Exec(ctx context.Context, q xsql.Query) error { + return m.MockExec(ctx, q) +} + +func (m mockDB) ExecTx(ctx context.Context, ql []xsql.Query) error { + return m.MockExecTx(ctx, ql) +} + +func (m mockDB) Scan(ctx context.Context, q xsql.Query, dest ...interface{}) error { + return m.MockScan(ctx, q, dest...) +} + +func (m mockDB) Query(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return m.MockQuery(ctx, q) +} + +func (m mockDB) GetConnectionDetails(username, password string) managed.ConnectionDetails { + return m.MockGetConnectionDetails(username, password) +} + +func TestConnect(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + kube client.Client + usage resource.Tracker + newDB func(creds map[string][]byte, database string) xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + cases := map[string]struct { + reason string + fields fields + args args + want error + }{ + "ErrNotGrant": { + reason: "An error should be returned if the managed resource is not a *Grant", + args: args{ + mg: nil, + }, + want: errors.New(errNotGrant), + }, + "ErrTrackProviderConfigUsage": { + reason: "An error should be returned if we can't track our ProviderConfig usage", + fields: fields{ + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return errBoom }), + }, + args: args{ + mg: &v1alpha1.Grant{}, + }, + want: errors.Wrap(errBoom, errTrackPCUsage), + }, + "ErrInvalidProviderConfigKind": { + reason: "An error should be returned if our ProviderConfig kind is invalid", + fields: fields{ + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{Kind: "Invalid"}, + }, + }, + }, + }, + want: provErrors.InvalidProviderConfigKindError("Invalid"), + }, + "ErrGetProviderConfig": { + reason: "An error should be returned if we can't get our ProviderConfig", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(errBoom), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Grant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.GrantSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetProviderConfigError(errBoom), + }, + "ErrGetClusterProviderConfig": { + reason: "An error should be returned if we can't get our ClusterProviderConfig", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(errBoom), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ClusterProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetClusterProviderConfigError(errBoom), + }, + "ErrMissingConnectionSecret": { + reason: "An error should be returned if our ProviderConfig doesn't specify a connection secret", + fields: fields{ + kube: &test.MockClient{ + // We call get to populate the Grant struct, then again + // to populate the (empty) ProviderConfig struct, resulting + // in a ProviderConfig with a nil connection secret. + MockGet: test.NewMockGetFn(nil), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.MissingSecretRefError(), + }, + "ErrGetConnectionSecret": { + reason: "An error should be returned if we can't get our ProviderConfig's connection secret", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + switch o := obj.(type) { + case *v1alpha1.ProviderConfig: + o.Spec.Credentials.ConnectionSecretRef = common.LocalSecretReference{Name: "example"} + case *corev1.Secret: + return errBoom + } + return nil + }), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Grant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.GrantSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetSecretError(errBoom), + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := &connector{kube: tc.fields.kube, usage: tc.fields.usage, newClient: tc.fields.newDB} + _, err := e.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") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + o managed.ExternalObservation + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotGrant": { + reason: "An error should be returned if the managed resource is not a *Grant", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotGrant), + }, + }, + "SuccessNoGrant": { + reason: "We should return ResourceExists: false when no grant is found", + fields: fields{ + db: mockDB{ + MockQuery: func(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return mockRowsToSQLRows(sqlmock.NewRows([]string{})), nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + User: ptr.To("test-example"), + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ResourceExists: false}, + }, + }, + "ErrSelectGrant": { + reason: "We should return any errors encountered while trying to show the grants", + fields: fields{ + db: mockDB{ + MockQuery: func(ctx context.Context, q xsql.Query) (*sql.Rows, error) { return &sql.Rows{}, errBoom }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + User: ptr.To("test-example"), + }, + }, + }, + }, + want: want{ + err: errors.Wrap(errBoom, errCannotGetGrants), + }, + }, + "Success": { + reason: "We should return no error if we can successfully get our permissions", + fields: fields{ + db: mockDB{ + MockQuery: func(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + if strings.Contains(q.String, "sys.schemas") { + return nil, errBoom + } + return mockRowsToSQLRows( + sqlmock.NewRows( + []string{"Grants"}, + ).AddRow("CREATE TABLE"), + ), nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("success-db"), + User: ptr.To("success-user"), + Permissions: v1alpha1.GrantPermissions{"CREATE TABLE"}, + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + }, + err: nil, + }, + }, + "SuccessSchema": { + reason: "We should return no error if we can successfully get our permissions", + fields: fields{ + db: mockDB{ + MockQuery: func(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + if !strings.Contains(q.String, "sys.schemas") { + return nil, errBoom + } + return mockRowsToSQLRows( + sqlmock.NewRows( + []string{"Grants"}, + ).AddRow("ALTER"), + ), nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("success-db"), + User: ptr.To("success-user"), + Schema: ptr.To("success-schema"), + Permissions: v1alpha1.GrantPermissions{"ALTER"}, + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + }, + err: nil, + }, + }, + "SuccessDiffPermissions": { + reason: "We should return no error if different permissions exist", + fields: fields{ + db: mockDB{ + MockQuery: func(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return mockRowsToSQLRows( + sqlmock.NewRows( + []string{"Grants"}, + ).AddRow("CREATE TABLE"), + ), nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("success-db"), + User: ptr.To("diff-user"), + Permissions: v1alpha1.GrantPermissions{"DELETE"}, + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: false, + }, + }, + }, + "SuccessManyPermissions": { + reason: "We should return no error if there are more than one permission for a user", + fields: fields{ + db: mockDB{ + MockQuery: func(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return mockRowsToSQLRows( + sqlmock.NewRows([]string{"Grants"}). + AddRow("CREATE"). + AddRow("DELETE"). + AddRow("EVENT"), + ), nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("success-db"), + User: ptr.To("success-user"), + Permissions: v1alpha1.GrantPermissions{"DELETE", "CREATE"}, + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: false, + }, + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + 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) + } + }) + } +} + +func TestCreate(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + c managed.ExternalCreation + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotGrant": { + reason: "An error should be returned if the managed resource is not a *Grant", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotGrant), + }, + }, + "ErrExec": { + reason: "Any errors encountered while creating the grant should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return errBoom }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + User: ptr.To("test-example"), + }, + }, + }, + }, + want: want{ + err: errors.Wrap(errBoom, errGrant), + }, + }, + "Success": { + reason: "No error should be returned when we successfully create a grant", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + if strings.Contains(q.String, "ON SCHEMA::") { + return errBoom + } + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + User: ptr.To("test-example"), + Permissions: v1alpha1.GrantPermissions{"DELETE", "CREATE"}, + }, + }, + }, + }, + want: want{ + err: nil, + }, + }, + "SuccessSchema": { + reason: "No error should be returned when we successfully create a grant", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + if !strings.Contains(q.String, "ON SCHEMA::") { + return errBoom + } + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + User: ptr.To("test-example"), + Schema: ptr.To("success-schema"), + Permissions: v1alpha1.GrantPermissions{"ALTER"}, + }, + }, + }, + }, + want: want{ + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + 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) + } + }) + } +} + +func TestUpdate(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + c managed.ExternalUpdate + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotGrant": { + reason: "An error should be returned if the managed resource is not a *Grant", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotGrant), + }, + }, + "ErrExec": { + reason: "Any errors encountered while updating the grant should be returned", + fields: fields{ + db: &mockDB{ + MockQuery: func(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return mockRowsToSQLRows(sqlmock.NewRows([]string{})), nil + }, + MockExec: func(ctx context.Context, q xsql.Query) error { return errBoom }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + User: ptr.To("test-example"), + Permissions: v1alpha1.GrantPermissions{"DELETE", "CREATE"}, + }, + }, + }, + }, + want: want{ + err: errors.Wrap(errBoom, errGrant), + }, + }, + "Success": { + reason: "No error should be returned when we update a grant", + fields: fields{ + db: &mockDB{ + MockQuery: func(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return mockRowsToSQLRows(sqlmock.NewRows([]string{})), nil + }, + MockExec: func(ctx context.Context, q xsql.Query) error { + if strings.Contains(q.String, "ON SCHEMA::") { + return errBoom + } + if strings.Contains(q.String, "CREATE, DELETE") { + return nil + } + return errBoom + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + User: ptr.To("test-example"), + Permissions: v1alpha1.GrantPermissions{"CREATE", "DELETE"}, + }, + }, + }, + }, + want: want{ + err: nil, + c: managed.ExternalUpdate{}, + }, + }, + "SuccessSchema": { + reason: "No error should be returned when we update a grant", + fields: fields{ + db: &mockDB{ + MockQuery: func(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return mockRowsToSQLRows(sqlmock.NewRows([]string{})), nil + }, + MockExec: func(ctx context.Context, q xsql.Query) error { + if !strings.Contains(q.String, "ON SCHEMA::") { + return errBoom + } + if strings.Contains(q.String, "ALTER") { + return nil + } + return errBoom + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + User: ptr.To("test-example"), + Schema: ptr.To("success-schema"), + Permissions: v1alpha1.GrantPermissions{"ALTER"}, + }, + }, + }, + }, + want: want{ + err: nil, + c: managed.ExternalUpdate{}, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{ + db: tc.fields.db, + } + got, err := e.Update(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, cmpopts.IgnoreMapEntries(func(key string, _ []byte) bool { return key == "password" })); diff != "" { + t.Errorf("\n%s\ne.Create(...): -want, +got:\n%s\n", tc.reason, diff) + } + }) + } +} + +func TestDelete(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + cases := map[string]struct { + reason string + fields fields + args args + want error + }{ + "ErrNotGrant": { + reason: "An error should be returned if the managed resource is not a *Grant", + args: args{ + mg: nil, + }, + want: errors.New(errNotGrant), + }, + "ErrDropGrant": { + reason: "Errors dropping a grant should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + return errBoom + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + User: ptr.To("test-example"), + }, + }, + }, + }, + want: errors.Wrap(errBoom, errRevoke), + }, + "Success": { + reason: "No error should be returned if the grant was revoked", + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + User: ptr.To("test-example"), + }, + }, + }, + }, + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + if strings.Contains(q.String, "ON SCHEMA::") { + return errBoom + } + return nil + }, + }, + }, + want: nil, + }, + "SuccessSchema": { + reason: "No error should be returned if the grant was revoked", + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + User: ptr.To("test-example"), + Schema: ptr.To("success-schema"), + }, + }, + }, + }, + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + if !strings.Contains(q.String, "ON SCHEMA::") { + return errBoom + } + return nil + }, + }, + }, + want: nil, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + _, 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) + } + }) + } +} + +func mockRowsToSQLRows(mockRows *sqlmock.Rows) *sql.Rows { + db, mock, _ := sqlmock.New() + mock.ExpectQuery("select").WillReturnRows(mockRows) + rows, err := db.Query("select") + if err != nil { + println("%v", err) + return nil + } + return rows +} + +func Test_diffPermissions(t *testing.T) { + type args struct { + desired []string + observed []string + } + type want struct { + toGrant []string + toRevoke []string + } + cases := map[string]struct { + args + want + }{ + "AsDesired": { + args: args{ + desired: []string{"CREATE TABLE", "DELETE"}, + observed: []string{"CREATE TABLE", "DELETE"}, + }, + want: want{ + toGrant: nil, + toRevoke: nil, + }, + }, + "AsDesiredOrderNotMatter": { + args: args{ + desired: []string{"CREATE TABLE", "DELETE"}, + observed: []string{"DELETE", "CREATE TABLE"}, + }, + want: want{ + toGrant: nil, + toRevoke: nil, + }, + }, + "NeedsGrant": { + args: args{ + desired: []string{"CREATE TABLE", "DELETE"}, + observed: []string{"CREATE TABLE"}, + }, + want: want{ + toGrant: []string{"DELETE"}, + }, + }, + "NeedsRevoke": { + args: args{ + desired: []string{"CREATE TABLE"}, + observed: []string{"CREATE TABLE", "DELETE"}, + }, + want: want{ + toRevoke: []string{"DELETE"}, + }, + }, + "NeedsBoth": { + args: args{ + desired: []string{"CREATE TABLE"}, + observed: []string{"DELETE"}, + }, + want: want{ + toGrant: []string{"CREATE TABLE"}, + toRevoke: []string{"DELETE"}, + }, + }, + "GrantAll": { + args: args{ + desired: []string{"CREATE TABLE", "DELETE", "INSERT"}, + }, + want: want{ + toGrant: []string{"CREATE TABLE", "DELETE", "INSERT"}, + }, + }, + "RevokeAll": { + args: args{ + observed: []string{"CREATE TABLE", "DELETE", "INSERT"}, + }, + want: want{ + toRevoke: []string{"CREATE TABLE", "DELETE", "INSERT"}, + }, + }, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + gotToGrant, gotToRevoke := diffPermissions(tc.desired, tc.observed) + if diff := cmp.Diff(tc.toGrant, gotToGrant, equateSlices()); diff != "" { + t.Errorf("\ndiffPermissions(...): -want toGrant, +got toGrant:\n%s", diff) + } + if diff := cmp.Diff(tc.toRevoke, gotToRevoke, equateSlices()); diff != "" { + t.Errorf("\ndiffPermissions(...): -want toRevoke, +got toRevoke:\n%s", diff) + } + }) + } +} + +func equateSlices() cmp.Option { + return cmpopts.SortSlices(func(x, y string) bool { + return x < y + }) +} diff --git a/pkg/controller/namespaced/mssql/mssql.go b/pkg/controller/namespaced/mssql/mssql.go new file mode 100644 index 00000000..39d6706a --- /dev/null +++ b/pkg/controller/namespaced/mssql/mssql.go @@ -0,0 +1,44 @@ +/* +Copyright 2021 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 mssql + +import ( + ctrl "sigs.k8s.io/controller-runtime" + + "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/mssql/config" + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/mssql/database" + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/mssql/grant" + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/mssql/user" +) + +// Setup creates all MSSQL controllers with the supplied logger and adds +// them to the supplied manager. +func Setup(mgr ctrl.Manager, o controller.Options) error { + for _, setup := range []func(ctrl.Manager, controller.Options) error{ + config.Setup, + database.Setup, + user.Setup, + grant.Setup, + } { + if err := setup(mgr, o); err != nil { + return err + } + } + return nil +} diff --git a/pkg/controller/namespaced/mssql/provider/provider.go b/pkg/controller/namespaced/mssql/provider/provider.go new file mode 100644 index 00000000..3d4c0bf6 --- /dev/null +++ b/pkg/controller/namespaced/mssql/provider/provider.go @@ -0,0 +1,77 @@ +package provider + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + "context" + + "github.com/crossplane-contrib/provider-sql/apis/namespaced/mssql/v1alpha1" + provErrors "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/errors" + + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type ProviderInfo struct { + ProviderConfigName string + SecretData map[string][]byte +} + +func GetProviderConfig(ctx context.Context, kube client.Client, mg resource.ModernManaged) (ProviderInfo, error) { + var secretKey *client.ObjectKey + + switch mg.GetProviderConfigReference().Kind { + case v1alpha1.ProviderConfigKind: + providerConfig := &v1alpha1.ProviderConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: mg.GetProviderConfigReference().Name, + Namespace: mg.GetNamespace(), + }, + } + + if err := kube.Get(ctx, client.ObjectKeyFromObject(providerConfig), providerConfig); err != nil { + return ProviderInfo{}, provErrors.GetProviderConfigError(err) + } + + secretKey = &client.ObjectKey{ + Name: providerConfig.Spec.Credentials.ConnectionSecretRef.Name, + Namespace: mg.GetNamespace(), + } + + case v1alpha1.ClusterProviderConfigKind: + clusterProviderConfig := &v1alpha1.ClusterProviderConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: mg.GetProviderConfigReference().Name, + }, + } + + if err := kube.Get(ctx, client.ObjectKeyFromObject(clusterProviderConfig), clusterProviderConfig); err != nil { + return ProviderInfo{}, provErrors.GetClusterProviderConfigError(err) + } + + secretKey = &client.ObjectKey{ + Name: clusterProviderConfig.Spec.Credentials.ConnectionSecretRef.Name, + Namespace: clusterProviderConfig.Spec.Credentials.ConnectionSecretRef.Namespace, + } + + default: + return ProviderInfo{}, provErrors.InvalidProviderConfigKindError(mg.GetProviderConfigReference().Kind) + } + + if secretKey.Name == "" || secretKey.Namespace == "" { + return ProviderInfo{}, provErrors.MissingSecretRefError() + } + + s := &corev1.Secret{} + err := kube.Get(ctx, *secretKey, s) + if err != nil { + return ProviderInfo{}, provErrors.GetSecretError(err) + } + + return ProviderInfo{ + ProviderConfigName: mg.GetProviderConfigReference().Name, + SecretData: s.Data, + }, nil +} diff --git a/pkg/controller/namespaced/mssql/user/reconciler.go b/pkg/controller/namespaced/mssql/user/reconciler.go new file mode 100644 index 00000000..67009c2d --- /dev/null +++ b/pkg/controller/namespaced/mssql/user/reconciler.go @@ -0,0 +1,287 @@ +/* +Copyright 2021 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 user + +import ( + "context" + "fmt" + + "github.com/pkg/errors" + "k8s.io/utils/ptr" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/feature" + "github.com/crossplane/crossplane-runtime/v2/pkg/meta" + "github.com/crossplane/crossplane-runtime/v2/pkg/password" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + namespacedv1alpha1 "github.com/crossplane-contrib/provider-sql/apis/namespaced/mssql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/pkg/clients/mssql" + "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/mssql/provider" +) + +const ( + errTrackPCUsage = "cannot track ProviderConfig usage" + + errNotUser = "managed resource is not a User custom resource" + errSelectUser = "cannot select user" + errCreateUser = "cannot create user %s" + errCreateLogin = "cannot create login %s" + errDropUser = "error dropping user %s" + errDropLogin = "error dropping login %s" + errCannotGetLogins = "cannot get current logins" + errCannotKillLoginSession = "error killing session %d for login %s" + + errUpdateUser = "cannot update user" + errGetPasswordSecretFailed = "cannot get password secret" + + maxConcurrency = 5 +) + +// TODO(nateinaction): This looks wrong, can tracker creation be improved? +type tracker struct { + tracker *resource.ProviderConfigUsageTracker +} + +var _ resource.Tracker = &tracker{} + +func (t *tracker) Track(ctx context.Context, mg resource.Managed) error { + return t.tracker.Track(ctx, mg.(resource.ModernManaged)) +} + +// Setup adds a controller that reconciles Database managed resources. +func Setup(mgr ctrl.Manager, o xpcontroller.Options) error { + name := managed.ControllerName(namespacedv1alpha1.UserGroupKind) + + t := resource.NewProviderConfigUsageTracker(mgr.GetClient(), &namespacedv1alpha1.ProviderConfigUsage{}) + trk := &tracker{tracker: t} + + reconcilerOptions := []managed.ReconcilerOption{ + managed.WithTypedExternalConnector(&connector{kube: mgr.GetClient(), usage: trk, newClient: mssql.New}), + managed.WithLogger(o.Logger.WithValues("controller", name)), + managed.WithPollInterval(o.PollInterval), + managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), + } + if o.Features.Enabled(feature.EnableBetaManagementPolicies) { + reconcilerOptions = append(reconcilerOptions, managed.WithManagementPolicies()) + } + r := managed.NewReconciler(mgr, + resource.ManagedKind(namespacedv1alpha1.UserGroupVersionKind), + reconcilerOptions..., + ) + return ctrl.NewControllerManagedBy(mgr). + Named(name). + For(&namespacedv1alpha1.User{}). + WithOptions(controller.Options{ + MaxConcurrentReconciles: maxConcurrency, + }). + Complete(r) +} + +type connector struct { + kube client.Client + usage resource.Tracker + newClient func(creds map[string][]byte, database string) xsql.DB +} + +var _ managed.TypedExternalConnector[resource.Managed] = &connector{} + +func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.TypedExternalClient[resource.Managed], error) { + cr, ok := mg.(*namespacedv1alpha1.User) + if !ok { + return nil, errors.New(errNotUser) + } + + if err := c.usage.Track(ctx, mg); err != nil { + return nil, errors.Wrap(err, errTrackPCUsage) + } + + // ProviderConfigReference could theoretically be nil, but in practice the + // DefaultProviderConfig initializer will set it before we get here. + providerInfo, err := provider.GetProviderConfig(ctx, c.kube, cr) + if err != nil { + return nil, err + } + + userDB := c.newClient(providerInfo.SecretData, ptr.Deref(cr.Spec.ForProvider.Database, "")) + loginDB := userDB + if cr.Spec.ForProvider.LoginDatabase != nil { + loginDB = c.newClient(providerInfo.SecretData, ptr.Deref(cr.Spec.ForProvider.LoginDatabase, "")) + } + + return &external{ + userDB: userDB, + loginDB: loginDB, + kube: c.kube, + }, nil +} + +type external struct { + userDB xsql.DB + loginDB xsql.DB + kube client.Client +} + +var _ managed.TypedExternalClient[resource.Managed] = &external{} + +func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { + cr, ok := mg.(*namespacedv1alpha1.User) + if !ok { + return managed.ExternalObservation{}, errors.New(errNotUser) + } + + var name string + + query := "SELECT name FROM sys.database_principals WHERE type = 'S' AND name = @p1" + err := c.userDB.Scan(ctx, xsql.Query{ + String: query, Parameters: []interface{}{ + meta.GetExternalName(cr), + }, + }, &name) + if xsql.IsNoRows(err) { + return managed.ExternalObservation{ResourceExists: false}, nil + } + if err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, errSelectUser) + } + + cr.SetConditions(xpv1.Available()) + + _, pwdChanged, err := c.getPassword(ctx, cr) + if err != nil { + return managed.ExternalObservation{}, err + } + + return managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: !pwdChanged, + }, nil +} + +func (c *external) Create(ctx context.Context, mg resource.Managed) (managed.ExternalCreation, error) { + cr, ok := mg.(*namespacedv1alpha1.User) + if !ok { + return managed.ExternalCreation{}, errors.New(errNotUser) + } + + pw, _, err := c.getPassword(ctx, cr) + if err != nil { + return managed.ExternalCreation{}, err + } + if pw == "" { + pw, err = password.Generate() + if err != nil { + return managed.ExternalCreation{}, err + } + } + + loginQuery := fmt.Sprintf("CREATE LOGIN %s WITH PASSWORD=%s", mssql.QuoteIdentifier(meta.GetExternalName(cr)), mssql.QuoteValue(pw)) + if err := c.loginDB.Exec(ctx, xsql.Query{ + String: loginQuery, + }); err != nil { + return managed.ExternalCreation{}, errors.Wrapf(err, errCreateLogin, meta.GetExternalName(cr)) + } + + userQuery := fmt.Sprintf("CREATE USER %s FOR LOGIN %s", mssql.QuoteIdentifier(meta.GetExternalName(cr)), mssql.QuoteIdentifier(meta.GetExternalName(cr))) + if err := c.userDB.Exec(ctx, xsql.Query{ + String: userQuery, + }); err != nil { + return managed.ExternalCreation{}, errors.Wrapf(err, errCreateUser, meta.GetExternalName(cr)) + } + + return managed.ExternalCreation{ + ConnectionDetails: c.userDB.GetConnectionDetails(meta.GetExternalName(cr), pw), + }, nil +} + +func (c *external) Update(ctx context.Context, mg resource.Managed) (managed.ExternalUpdate, error) { + cr, ok := mg.(*namespacedv1alpha1.User) + if !ok { + return managed.ExternalUpdate{}, errors.New(errNotUser) + } + + pw, changed, err := c.getPassword(ctx, cr) + if err != nil { + return managed.ExternalUpdate{}, err + } + + if changed { + query := fmt.Sprintf("ALTER LOGIN %s WITH PASSWORD=%s", mssql.QuoteIdentifier(meta.GetExternalName(cr)), mssql.QuoteValue(pw)) + if err := c.loginDB.Exec(ctx, xsql.Query{ + String: query, + }); err != nil { + return managed.ExternalUpdate{}, errors.Wrap(err, errUpdateUser) + } + + return managed.ExternalUpdate{ + ConnectionDetails: c.userDB.GetConnectionDetails(meta.GetExternalName(cr), pw), + }, nil + } + return managed.ExternalUpdate{}, nil +} + +func (c *external) Disconnect(ctx context.Context) error { + return nil +} + +func (c *external) Delete(ctx context.Context, mg resource.Managed) (managed.ExternalDelete, error) { + cr, ok := mg.(*namespacedv1alpha1.User) + if !ok { + return managed.ExternalDelete{}, errors.New(errNotUser) + } + + query := fmt.Sprintf("SELECT session_id FROM sys.dm_exec_sessions WHERE login_name = %s", mssql.QuoteValue(meta.GetExternalName(cr))) + rows, err := c.userDB.Query(ctx, xsql.Query{String: query}) + if err != nil { + return managed.ExternalDelete{}, errors.Wrap(err, errCannotGetLogins) + } + defer rows.Close() //nolint:errcheck + + for rows.Next() { + var sessionID int + if err := rows.Scan(&sessionID); err != nil { + return managed.ExternalDelete{}, errors.Wrap(err, errCannotGetLogins) + } + if err := c.userDB.Exec(ctx, xsql.Query{String: fmt.Sprintf("KILL %d", sessionID)}); err != nil { + return managed.ExternalDelete{}, errors.Wrapf(err, errCannotKillLoginSession, sessionID, meta.GetExternalName(cr)) + } + } + if err := rows.Err(); err != nil { + return managed.ExternalDelete{}, errors.Wrap(err, errCannotGetLogins) + } + + if err := c.userDB.Exec(ctx, xsql.Query{ + String: fmt.Sprintf("DROP USER IF EXISTS %s", mssql.QuoteIdentifier(meta.GetExternalName(cr))), + }); err != nil { + return managed.ExternalDelete{}, errors.Wrapf(err, errDropUser, meta.GetExternalName(cr)) + } + + if err := c.loginDB.Exec(ctx, xsql.Query{ + String: fmt.Sprintf("DROP LOGIN %s", mssql.QuoteIdentifier(meta.GetExternalName(cr))), + }); err != nil { + return managed.ExternalDelete{}, errors.Wrapf(err, errDropLogin, meta.GetExternalName(cr)) + } + + return managed.ExternalDelete{}, nil +} diff --git a/pkg/controller/namespaced/mssql/user/reconciler_test.go b/pkg/controller/namespaced/mssql/user/reconciler_test.go new file mode 100644 index 00000000..7df32bf4 --- /dev/null +++ b/pkg/controller/namespaced/mssql/user/reconciler_test.go @@ -0,0 +1,968 @@ +/* +Copyright 2021 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 user + +import ( + "context" + "database/sql" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/crossplane-contrib/provider-sql/apis/namespaced/mssql/v1alpha1" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/crossplane/crossplane-runtime/v2/apis/common" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpv2 "github.com/crossplane/crossplane-runtime/v2/apis/common/v2" + "github.com/crossplane/crossplane-runtime/v2/pkg/meta" + "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/crossplane-contrib/provider-sql/pkg/clients/xsql" + provErrors "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/errors" +) + +type mockDB struct { + database string + MockExec func(ctx context.Context, q xsql.Query) error + MockExecTx func(ctx context.Context, ql []xsql.Query) error + MockScan func(ctx context.Context, q xsql.Query, dest ...interface{}) error + MockQuery func(ctx context.Context, q xsql.Query) (*sql.Rows, error) +} + +func (m mockDB) Exec(ctx context.Context, q xsql.Query) error { + return m.MockExec(ctx, q) +} +func (m mockDB) ExecTx(ctx context.Context, ql []xsql.Query) error { + return m.MockExecTx(ctx, ql) +} +func (m mockDB) Scan(ctx context.Context, q xsql.Query, dest ...interface{}) error { + return m.MockScan(ctx, q, dest...) +} +func (m mockDB) Query(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return m.MockQuery(ctx, q) +} +func (m mockDB) GetConnectionDetails(username, password string) managed.ConnectionDetails { + return managed.ConnectionDetails{ + xpv1.ResourceCredentialsSecretUserKey: []byte(username), + xpv1.ResourceCredentialsSecretPasswordKey: []byte(password), + xpv1.ResourceCredentialsSecretEndpointKey: []byte("localhost"), + xpv1.ResourceCredentialsSecretPortKey: []byte("3306"), + } +} + +func mockRowsToSQLRows(mockRows *sqlmock.Rows) *sql.Rows { + db, mock, _ := sqlmock.New() + mock.ExpectQuery("select").WillReturnRows(mockRows) + rows, err := db.Query("select") + if err != nil { + println("%v", err) + return nil + } + return rows +} + +func TestConnect(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + kube client.Client + usage resource.Tracker + newDB func(creds map[string][]byte, database string) xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + sameClient *bool + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotUser": { + reason: "An error should be returned if the managed resource is not a *User", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotUser), + }, + }, + "ErrTrackProviderConfigUsage": { + reason: "An error should be returned if we can't track our ProviderConfig usage", + fields: fields{ + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return errBoom }), + }, + args: args{ + mg: &v1alpha1.User{}, + }, + want: want{ + err: errors.Wrap(errBoom, errTrackPCUsage), + }, + }, + "ErrInvalidProviderConfigKind": { + reason: "An error should be returned if our ProviderConfigReference kind is invalid", + fields: fields{ + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.User{ + Spec: v1alpha1.UserSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{Kind: "InvalidKind"}, + }, + }, + }, + }, + want: want{ + err: provErrors.InvalidProviderConfigKindError("InvalidKind"), + }, + }, + "ErrGetProviderConfig": { + reason: "An error should be returned if we can't get our ProviderConfig", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(errBoom), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.UserSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: want{ + err: provErrors.GetProviderConfigError(errBoom), + }, + }, + "ErrGetClusterProviderConfig": { + reason: "An error should be returned if we can't get our ClusterProviderConfig", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(errBoom), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.User{ + Spec: v1alpha1.UserSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ClusterProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: want{ + err: provErrors.GetClusterProviderConfigError(errBoom), + }, + }, + "ErrMissingConnectionSecret": { + reason: "An error should be returned if our ProviderConfig doesn't specify a connection secret", + fields: fields{ + kube: &test.MockClient{ + // We call get to populate the User struct, then again + // to populate the (empty) ProviderConfig struct, resulting + // in a ProviderConfig with a nil connection secret. + MockGet: test.NewMockGetFn(nil), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.UserSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: want{ + err: provErrors.MissingSecretRefError(), + }, + }, + "ErrGetConnectionSecret": { + reason: "An error should be returned if we can't get our ProviderConfig's connection secret", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + switch o := obj.(type) { + case *v1alpha1.ProviderConfig: + o.Spec.Credentials.ConnectionSecretRef = common.LocalSecretReference{Name: "example"} + case *corev1.Secret: + return errBoom + } + return nil + }), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.UserSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: want{ + err: provErrors.GetSecretError(errBoom), + }, + }, + "Success": { + reason: "With NO login database defined, the clients should be the same", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + switch o := obj.(type) { + case *v1alpha1.ProviderConfig: + o.Spec.Credentials.ConnectionSecretRef = common.LocalSecretReference{Name: "example"} + case *corev1.Secret: + secret := corev1.Secret{ + Data: map[string][]byte{}, + } + secret.DeepCopyInto(obj.(*corev1.Secret)) + } + return nil + }), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + newDB: func(creds map[string][]byte, database string) xsql.DB { return mockDB{database: database} }, + }, + args: args{ + mg: &v1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.UserSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + ForProvider: v1alpha1.UserParameters{ + Database: ptr.To("success-database"), + }, + }, + }, + }, + want: want{ + err: nil, + sameClient: ptr.To(true), + }, + }, + "SuccessLoginDB": { + reason: "With the login database defined, the clients should differ", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + switch o := obj.(type) { + case *v1alpha1.ProviderConfig: + o.Spec.Credentials.ConnectionSecretRef = common.LocalSecretReference{Name: "example"} + case *corev1.Secret: + secret := corev1.Secret{ + Data: map[string][]byte{}, + } + secret.DeepCopyInto(obj.(*corev1.Secret)) + } + return nil + }), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + newDB: func(creds map[string][]byte, database string) xsql.DB { return mockDB{database: database} }, + }, + args: args{ + mg: &v1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.UserSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + ForProvider: v1alpha1.UserParameters{ + Database: ptr.To("success-database"), + LoginDatabase: ptr.To("success-login-database"), + }, + }, + }, + }, + want: want{ + err: nil, + sameClient: ptr.To(false), + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := &connector{kube: tc.fields.kube, usage: tc.fields.usage, newClient: tc.fields.newDB} + ec, err := e.Connect(tc.args.ctx, tc.args.mg) + if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { + t.Errorf("\n%s\ne.Connect(...): -want error, +got error:\n%s\n", tc.reason, diff) + } + if tc.want.sameClient != nil { + ext := ec.(*external) + db1 := ext.userDB.(mockDB).database + db2 := ext.loginDB.(mockDB).database + if *tc.want.sameClient && db1 != db2 { + t.Errorf("\n%s\ne.Connect(...): want clients to be on the same database\n%s / %s\n", + tc.reason, db1, db2) + } else if !*tc.want.sameClient && db1 == db2 { + t.Errorf("\n%s\ne.Connect(...): want clients NOT to be the same instance\n%s\n", + tc.reason, db1) + } + } + }) + } +} + +func TestObserve(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + db xsql.DB + kube client.Client + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + o managed.ExternalObservation + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotUser": { + reason: "An error should be returned if the managed resource is not a *User", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotUser), + }, + }, + "ErrNoUser": { + reason: "We should return ResourceExists: false when no user is found", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return sql.ErrNoRows }, + }, + }, + args: args{ + mg: &v1alpha1.User{}, + }, + want: want{ + o: managed.ExternalObservation{ResourceExists: false}, + }, + }, + "ErrSelectUser": { + reason: "We should return any errors encountered while trying to select the user", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return errBoom }, + }, + }, + args: args{ + mg: &v1alpha1.User{}, + }, + want: want{ + err: errors.Wrap(errBoom, errSelectUser), + }, + }, + "Success": { + reason: "We should return no error if we can successfully select our user", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.User{}, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + ResourceLateInitialized: false, + }, + err: nil, + }, + }, + "PasswordChanged": { + reason: "We should return ResourceUpToDate=false if the password changed", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return nil }, + }, + kube: &test.MockClient{ + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + secret := corev1.Secret{ + Data: map[string][]byte{}, + } + secret.Data[xpv1.ResourceCredentialsSecretPasswordKey] = []byte(key.Name) + secret.DeepCopyInto(obj.(*corev1.Secret)) + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.User{ + Spec: v1alpha1.UserSpec{ + ForProvider: v1alpha1.UserParameters{ + PasswordSecretRef: &common.LocalSecretKeySelector{ + LocalSecretReference: common.LocalSecretReference{ + Name: "example", + }, + Key: "password", + }, + }, + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + WriteConnectionSecretToReference: &common.LocalSecretReference{ + Name: "connection-secret", + }, + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: false, + }, + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{ + userDB: tc.fields.db, + loginDB: tc.fields.db, + kube: tc.fields.kube, + } + 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) + } + }) + } +} + +func TestCreate(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + db xsql.DB + kube client.Client + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + c managed.ExternalCreation + err error + } + + cases := map[string]struct { + reason string + comparePw bool + fields fields + args args + want want + }{ + "ErrNotUser": { + reason: "An error should be returned if the managed resource is not a *User", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotUser), + }, + }, + "ErrExec": { + reason: "Any errors encountered while creating the user should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return errBoom }, + }, + }, + args: args{ + mg: &v1alpha1.User{}, + }, + want: want{ + err: errors.Wrapf(errBoom, errCreateLogin, ""), + }, + }, + "Success": { + reason: "No error should be returned when we successfully create a user", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: "example", + }, + }, + }, + }, + want: want{ + err: nil, + c: managed.ExternalCreation{ + ConnectionDetails: managed.ConnectionDetails{ + xpv1.ResourceCredentialsSecretUserKey: []byte("example"), + xpv1.ResourceCredentialsSecretPasswordKey: []byte(""), + xpv1.ResourceCredentialsSecretEndpointKey: []byte("localhost"), + xpv1.ResourceCredentialsSecretPortKey: []byte("3306"), + }, + }, + }, + }, + "UserWithPasswordRef": { + reason: "The password must be read from the secret", + comparePw: true, + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return nil }, + }, + kube: &test.MockClient{ + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + switch key.Name { + case "example": + secret := corev1.Secret{ + Data: map[string][]byte{}, + } + secret.Data["password-custom"] = []byte("test1234") + secret.DeepCopyInto(obj.(*corev1.Secret)) + return nil + default: + return nil + } + }, + }, + }, + args: args{ + mg: &v1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: "example", + }, + }, + Spec: v1alpha1.UserSpec{ + ForProvider: v1alpha1.UserParameters{ + PasswordSecretRef: &common.LocalSecretKeySelector{ + LocalSecretReference: common.LocalSecretReference{ + Name: "example", + }, + Key: "password-custom", + }, + }, + }, + }, + }, + want: want{ + err: nil, + c: managed.ExternalCreation{ + ConnectionDetails: managed.ConnectionDetails{ + xpv1.ResourceCredentialsSecretUserKey: []byte("example"), + xpv1.ResourceCredentialsSecretPasswordKey: []byte("test1234"), + xpv1.ResourceCredentialsSecretEndpointKey: []byte("localhost"), + xpv1.ResourceCredentialsSecretPortKey: []byte("3306"), + }, + }, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{ + userDB: tc.fields.db, + loginDB: tc.fields.db, + kube: tc.fields.kube, + } + 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) + } + var opts []cmp.Option + if !tc.comparePw { + opts = append(opts, cmpopts.IgnoreMapEntries(func(key string, _ []byte) bool { return key == "password" })) + } + if diff := cmp.Diff(tc.want.c, got, opts...); diff != "" { + t.Errorf("\n%s\ne.Create(...): -want, +got:\n%s\n", tc.reason, diff) + } + }) + } +} + +func TestUpdate(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + kube client.Client + } + + type want struct { + c managed.ExternalUpdate + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotUser": { + reason: "An error should be returned if the managed resource is not a *User", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotUser), + }, + }, + "ErrExec": { + reason: "Any errors encountered while updating the user should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return errBoom }, + }, + }, + args: args{ + mg: &v1alpha1.User{ + Spec: v1alpha1.UserSpec{ + ForProvider: v1alpha1.UserParameters{ + PasswordSecretRef: &common.LocalSecretKeySelector{ + LocalSecretReference: common.LocalSecretReference{ + Name: "connection-secret", + }, + Key: xpv1.ResourceCredentialsSecretPasswordKey, + }, + }, + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + WriteConnectionSecretToReference: &common.LocalSecretReference{ + Name: "password-secret", + }, + }, + }, + }, + kube: &test.MockClient{ + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + secret := corev1.Secret{ + Data: map[string][]byte{}, + } + secret.Data[xpv1.ResourceCredentialsSecretPasswordKey] = []byte(key.Name) + secret.DeepCopyInto(obj.(*corev1.Secret)) + return nil + }, + }, + }, + want: want{ + err: errors.Wrap(errBoom, errUpdateUser), + }, + }, + "Success": { + reason: "No error should be returned when we don't have to update a user", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: "example", + }, + }, + }, + }, + want: want{ + err: nil, + c: managed.ExternalUpdate{}, + }, + }, + "SamePassword": { + reason: "No DB query should be executed if the password didn't change", + fields: fields{ + db: &mockDB{}, + }, + args: args{ + mg: &v1alpha1.User{ + Spec: v1alpha1.UserSpec{ + ForProvider: v1alpha1.UserParameters{ + PasswordSecretRef: &common.LocalSecretKeySelector{ + LocalSecretReference: common.LocalSecretReference{ + Name: "connection-secret", + }, + Key: xpv1.ResourceCredentialsSecretPasswordKey, + }, + }, + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + WriteConnectionSecretToReference: &common.LocalSecretReference{ + Name: "connection-secret", + }, + }, + }, + }, + kube: &test.MockClient{ + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + secret := corev1.Secret{ + Data: map[string][]byte{}, + } + secret.Data[xpv1.ResourceCredentialsSecretPasswordKey] = []byte("samesame") + secret.DeepCopyInto(obj.(*corev1.Secret)) + return nil + }, + }, + }, + want: want{}, + }, + "UpdatePassword": { + reason: "The password must be updated", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: "example", + }, + }, + Spec: v1alpha1.UserSpec{ + ForProvider: v1alpha1.UserParameters{ + PasswordSecretRef: &common.LocalSecretKeySelector{ + LocalSecretReference: common.LocalSecretReference{ + Name: "example", + }, + Key: "password-custom", + }, + }, + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + WriteConnectionSecretToReference: &common.LocalSecretReference{ + Name: "connection-secret", + }, + }, + }, + }, + kube: &test.MockClient{ + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + switch key.Name { + case "example": + secret := corev1.Secret{ + Data: map[string][]byte{}, + } + secret.Data["password-custom"] = []byte("newpassword") + secret.DeepCopyInto(obj.(*corev1.Secret)) + return nil + case "connection-secret": + secret := corev1.Secret{ + Data: map[string][]byte{}, + } + secret.Data[xpv1.ResourceCredentialsSecretPasswordKey] = []byte("oldpassword") + secret.DeepCopyInto(obj.(*corev1.Secret)) + return nil + default: + return nil + } + }, + }, + }, + want: want{ + err: nil, + c: managed.ExternalUpdate{ + ConnectionDetails: managed.ConnectionDetails{ + xpv1.ResourceCredentialsSecretUserKey: []byte("example"), + xpv1.ResourceCredentialsSecretPasswordKey: []byte("newpassword"), + xpv1.ResourceCredentialsSecretEndpointKey: []byte("localhost"), + xpv1.ResourceCredentialsSecretPortKey: []byte("3306"), + }, + }, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{ + userDB: tc.fields.db, + loginDB: tc.fields.db, + kube: tc.args.kube, + } + got, err := e.Update(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, cmpopts.IgnoreMapEntries(func(key string, _ []byte) bool { return key == "password" })); diff != "" { + t.Errorf("\n%s\ne.Create(...): -want, +got:\n%s\n", tc.reason, diff) + } + }) + } +} + +func TestDelete(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + userDB xsql.DB + loginDB xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + cases := map[string]struct { + reason string + fields fields + args args + want error + }{ + "ErrNotUser": { + reason: "An error should be returned if the managed resource is not a *User", + args: args{ + mg: nil, + }, + want: errors.New(errNotUser), + }, + "ErrDropDB": { + reason: "Errors dropping a user should be returned", + fields: fields{ + userDB: &mockDB{ + MockQuery: func(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return mockRowsToSQLRows(sqlmock.NewRows([]string{})), nil + }, + MockExec: func(ctx context.Context, q xsql.Query) error { + return errBoom + }, + }, + loginDB: &mockDB{}, + }, + args: args{ + mg: &v1alpha1.User{}, + }, + want: errors.Wrapf(errBoom, errDropUser, ""), + }, + "Success": { + reason: "No error should be returned", + fields: fields{ + userDB: &mockDB{ + MockQuery: func(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return mockRowsToSQLRows(sqlmock.NewRows([]string{})), nil + }, + MockExec: func(ctx context.Context, q xsql.Query) error { + return nil + }, + }, + loginDB: &mockDB{ + + MockExec: func(ctx context.Context, q xsql.Query) error { + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.User{}, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{userDB: tc.fields.userDB, loginDB: tc.fields.loginDB} + _, 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/pkg/controller/namespaced/mssql/user/utils.go b/pkg/controller/namespaced/mssql/user/utils.go new file mode 100644 index 00000000..53d53dd9 --- /dev/null +++ b/pkg/controller/namespaced/mssql/user/utils.go @@ -0,0 +1,66 @@ +/* +Copyright 2021 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 user + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + + "github.com/pkg/errors" + + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + "github.com/crossplane-contrib/provider-sql/apis/namespaced/mssql/v1alpha1" +) + +func (c *external) getPassword(ctx context.Context, user *v1alpha1.User) (newPwd string, changed bool, err error) { + if user.Spec.ForProvider.PasswordSecretRef == nil { + return "", false, nil + } + nn := types.NamespacedName{ + Name: user.Spec.ForProvider.PasswordSecretRef.Name, + Namespace: user.Namespace, + } + s := &corev1.Secret{} + if err := c.kube.Get(ctx, nn, s); err != nil { + return "", false, errors.Wrap(err, errGetPasswordSecretFailed) + } + newPwd = string(s.Data[user.Spec.ForProvider.PasswordSecretRef.Key]) + + if user.Spec.WriteConnectionSecretToReference == nil { + return newPwd, false, nil + } + + nn = types.NamespacedName{ + Name: user.Spec.WriteConnectionSecretToReference.Name, + Namespace: user.Namespace, + } + s = &corev1.Secret{} + // the output secret may not exist yet, so we can skip returning an + // error if the error is NotFound + if err := c.kube.Get(ctx, nn, s); resource.IgnoreNotFound(err) != nil { + return "", false, err + } + // if newPwd was set to some value, compare value in output secret with + // newPwd + changed = newPwd != "" && newPwd != string(s.Data[xpv1.ResourceCredentialsSecretPasswordKey]) + + return newPwd, changed, nil +} diff --git a/pkg/controller/namespaced/mysql/config/config.go b/pkg/controller/namespaced/mysql/config/config.go new file mode 100644 index 00000000..ac4bcaa1 --- /dev/null +++ b/pkg/controller/namespaced/mysql/config/config.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 config + +import ( + 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/reconciler/providerconfig" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + "github.com/crossplane-contrib/provider-sql/apis/namespaced/mysql/v1alpha1" +) + +// Setup adds a controller that reconciles ProviderConfigs and ClusterProviderConfigs by accounting for +// their current usage. +func Setup(mgr ctrl.Manager, o controller.Options) error { + if err := setupNamespacedProviderConfig(mgr, o); err != nil { + return err + } + + return setupClusterProviderConfig(mgr, o) +} + +func setupNamespacedProviderConfig(mgr ctrl.Manager, o controller.Options) error { + name := providerconfig.ControllerName(v1alpha1.ProviderConfigGroupKind) + + of := resource.ProviderConfigKinds{ + Config: v1alpha1.ProviderConfigGroupVersionKind, + Usage: v1alpha1.ProviderConfigUsageGroupVersionKind, + UsageList: v1alpha1.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(&v1alpha1.ProviderConfig{}). + Watches(&v1alpha1.ProviderConfigUsage{}, &resource.EnqueueRequestForProviderConfig{}). + Complete(r) +} + +func setupClusterProviderConfig(mgr ctrl.Manager, o controller.Options) error { + name := providerconfig.ControllerName(v1alpha1.ClusterProviderConfigGroupKind) + of := resource.ProviderConfigKinds{ + Config: v1alpha1.ClusterProviderConfigGroupVersionKind, + // Usage types are shared + Usage: v1alpha1.ProviderConfigUsageGroupVersionKind, + UsageList: v1alpha1.ProviderConfigUsageListGroupVersionKind, + } + + return ctrl.NewControllerManagedBy(mgr). + Named(name). + WithOptions(o.ForControllerRuntime()). + For(&v1alpha1.ClusterProviderConfig{}). + // Usage types are shared + Watches(&v1alpha1.ProviderConfigUsage{}, &resource.EnqueueRequestForProviderConfig{}). + Complete(providerconfig.NewReconciler(mgr, of, + providerconfig.WithLogger(o.Logger.WithValues("controller", name)), + providerconfig.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))))) +} diff --git a/pkg/controller/namespaced/mysql/database/reconciler.go b/pkg/controller/namespaced/mysql/database/reconciler.go new file mode 100644 index 00000000..8bfcf6cd --- /dev/null +++ b/pkg/controller/namespaced/mysql/database/reconciler.go @@ -0,0 +1,196 @@ +/* +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 database + +import ( + "context" + + "github.com/pkg/errors" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/feature" + "github.com/crossplane/crossplane-runtime/v2/pkg/meta" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + namespacedv1alpha1 "github.com/crossplane-contrib/provider-sql/apis/namespaced/mysql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/pkg/clients/mysql" + "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/mysql/provider" + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/mysql/tls" +) + +const ( + errTrackPCUsage = "cannot track ProviderConfig usage" + errTLSConfig = "cannot load TLS config" + + errNotDatabase = "managed resource is not a Database custom resource" + errSelectDB = "cannot select database" + errCreateDB = "cannot create database" + errDropDB = "cannot drop database" + + maxConcurrency = 5 +) + +// TODO(nateinaction): This looks wrong, can tracker creation be improved? +type tracker struct { + tracker *resource.ProviderConfigUsageTracker +} + +var _ resource.Tracker = &tracker{} + +func (t *tracker) Track(ctx context.Context, mg resource.Managed) error { + return t.tracker.Track(ctx, mg.(resource.ModernManaged)) +} + +// Setup adds a controller that reconciles Database managed resources. +func Setup(mgr ctrl.Manager, o xpcontroller.Options) error { + name := managed.ControllerName(namespacedv1alpha1.DatabaseGroupKind) + + t := resource.NewProviderConfigUsageTracker(mgr.GetClient(), &namespacedv1alpha1.ProviderConfigUsage{}) + trk := &tracker{tracker: t} + + reconcilerOptions := []managed.ReconcilerOption{ + managed.WithTypedExternalConnector(&connector{kube: mgr.GetClient(), usage: trk, newDB: mysql.New}), + managed.WithLogger(o.Logger.WithValues("controller", name)), + managed.WithPollInterval(o.PollInterval), + managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), + } + if o.Features.Enabled(feature.EnableBetaManagementPolicies) { + reconcilerOptions = append(reconcilerOptions, managed.WithManagementPolicies()) + } + r := managed.NewReconciler(mgr, + resource.ManagedKind(namespacedv1alpha1.DatabaseGroupVersionKind), + reconcilerOptions..., + ) + + return ctrl.NewControllerManagedBy(mgr). + Named(name). + For(&namespacedv1alpha1.Database{}). + WithOptions(controller.Options{ + MaxConcurrentReconciles: maxConcurrency, + }). + Complete(r) +} + +type connector struct { + kube client.Client + usage resource.Tracker + newDB func(creds map[string][]byte, tls *string, binlog *bool) xsql.DB +} + +var _ managed.TypedExternalConnector[resource.Managed] = &connector{} + +func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.TypedExternalClient[resource.Managed], error) { + cr, ok := mg.(*namespacedv1alpha1.Database) + if !ok { + return nil, errors.New(errNotDatabase) + } + + if err := c.usage.Track(ctx, mg); err != nil { + return nil, errors.Wrap(err, errTrackPCUsage) + } + + // ProviderConfigReference could theoretically be nil, but in practice the + // DefaultProviderConfig initializer will set it before we get here. + providerInfo, err := provider.GetProviderConfig(ctx, c.kube, cr) + if err != nil { + return nil, err + } + + tlsName, err := tls.LoadConfig(ctx, c.kube, providerInfo.ProviderConfigName, providerInfo.TLS, providerInfo.TLSConfig) + if err != nil { + return nil, errors.Wrap(err, errTLSConfig) + } + + return &external{db: c.newDB(providerInfo.SecretData, tlsName, cr.Spec.ForProvider.BinLog)}, nil +} + +type external struct{ db xsql.DB } + +var _ managed.TypedExternalClient[resource.Managed] = &external{} + +func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { + cr, ok := mg.(*namespacedv1alpha1.Database) + if !ok { + return managed.ExternalObservation{}, errors.New(errNotDatabase) + } + + var name string + query := "SELECT schema_name FROM information_schema.schemata WHERE schema_name = ?" + err := c.db.Scan(ctx, xsql.Query{String: query, Parameters: []interface{}{meta.GetExternalName(cr)}}, &name) + if xsql.IsNoRows(err) { + return managed.ExternalObservation{ResourceExists: false}, nil + } + if err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, errSelectDB) + } + + cr.SetConditions(xpv1.Available()) + + return managed.ExternalObservation{ + ResourceExists: true, + + // TODO(negz): Support these when we have anything to update. + ResourceLateInitialized: false, + ResourceUpToDate: true, + }, nil +} + +func (c *external) Create(ctx context.Context, mg resource.Managed) (managed.ExternalCreation, error) { + cr, ok := mg.(*namespacedv1alpha1.Database) + if !ok { + return managed.ExternalCreation{}, errors.New(errNotDatabase) + } + + query := "CREATE DATABASE " + mysql.QuoteIdentifier(meta.GetExternalName(cr)) + + if err := mysql.ExecWrapper(ctx, c.db, mysql.ExecQuery{Query: query, ErrorValue: errCreateDB}); err != nil { + return managed.ExternalCreation{}, err + } + + return managed.ExternalCreation{}, nil +} + +func (c *external) Update(ctx context.Context, mg resource.Managed) (managed.ExternalUpdate, error) { + // TODO(negz): Support updates once we have anything to update. + return managed.ExternalUpdate{}, nil +} + +func (c *external) Disconnect(ctx context.Context) error { + return nil +} + +func (c *external) Delete(ctx context.Context, mg resource.Managed) (managed.ExternalDelete, error) { + cr, ok := mg.(*namespacedv1alpha1.Database) + if !ok { + return managed.ExternalDelete{}, errors.New(errNotDatabase) + } + + query := "DROP DATABASE IF EXISTS " + mysql.QuoteIdentifier(meta.GetExternalName(cr)) + + if err := mysql.ExecWrapper(ctx, c.db, mysql.ExecQuery{Query: query, ErrorValue: errDropDB}); err != nil { + return managed.ExternalDelete{}, err + } + + return managed.ExternalDelete{}, nil +} diff --git a/pkg/controller/namespaced/mysql/database/reconciler_test.go b/pkg/controller/namespaced/mysql/database/reconciler_test.go new file mode 100644 index 00000000..923cff22 --- /dev/null +++ b/pkg/controller/namespaced/mysql/database/reconciler_test.go @@ -0,0 +1,450 @@ +/* +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 database + +import ( + "context" + "database/sql" + "testing" + + "github.com/crossplane-contrib/provider-sql/apis/namespaced/mysql/v1alpha1" + "github.com/google/go-cmp/cmp" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/crossplane/crossplane-runtime/v2/apis/common" + xpv2 "github.com/crossplane/crossplane-runtime/v2/apis/common/v2" + "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/crossplane-contrib/provider-sql/pkg/clients/xsql" + provErrors "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/errors" +) + +type mockDB struct { + MockExec func(ctx context.Context, q xsql.Query) error + MockScan func(ctx context.Context, q xsql.Query, dest ...interface{}) error + MockExecTx func(ctx context.Context, ql []xsql.Query) error + MockGetConnectionDetails func(username, password string) managed.ConnectionDetails +} + +func (m mockDB) ExecTx(ctx context.Context, ql []xsql.Query) error { + return m.MockExecTx(ctx, ql) +} +func (m mockDB) Exec(ctx context.Context, q xsql.Query) error { + return m.MockExec(ctx, q) +} +func (m mockDB) Scan(ctx context.Context, q xsql.Query, dest ...interface{}) error { + return m.MockScan(ctx, q, dest...) +} +func (m mockDB) Query(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return &sql.Rows{}, nil +} +func (m mockDB) GetConnectionDetails(username, password string) managed.ConnectionDetails { + return m.MockGetConnectionDetails(username, password) +} + +func TestConnect(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + kube client.Client + usage resource.Tracker + newDB func(creds map[string][]byte, tls *string, binlog *bool) xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + cases := map[string]struct { + reason string + fields fields + args args + want error + }{ + "ErrNotDatabase": { + reason: "An error should be returned if the managed resource is not a *Database", + args: args{ + mg: nil, + }, + want: errors.New(errNotDatabase), + }, + "ErrTrackProviderConfigUsage": { + reason: "An error should be returned if we can't track our ProviderConfig usage", + fields: fields{ + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return errBoom }), + }, + args: args{ + mg: &v1alpha1.Database{}, + }, + want: errors.Wrap(errBoom, errTrackPCUsage), + }, + "ErrInvalidProviderConfigKind": { + reason: "An error should be returned if the ProviderConfig kind is not valid", + fields: fields{ + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Database{ + Spec: v1alpha1.DatabaseSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{Kind: "NotValid"}, + }, + }, + }, + }, + want: provErrors.InvalidProviderConfigKindError("NotValid"), + }, + "ErrGetProviderConfig": { + reason: "An error should be returned if we can't get our ProviderConfig", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(errBoom), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Database{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.DatabaseSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetProviderConfigError(errBoom), + }, + "ErrClusterGetProviderConfig": { + reason: "An error should be returned if we can't get our ClusterProviderConfig", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(errBoom), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Database{ + Spec: v1alpha1.DatabaseSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ClusterProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetClusterProviderConfigError(errBoom), + }, + "ErrMissingConnectionSecret": { + reason: "An error should be returned if our ProviderConfig doesn't specify a connection secret", + fields: fields{ + kube: &test.MockClient{ + // We call get to populate the Database struct, then again + // to populate the (empty) ProviderConfig struct, resulting + // in a ProviderConfig with a nil connection secret. + MockGet: test.NewMockGetFn(nil), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Database{ + Spec: v1alpha1.DatabaseSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{Kind: v1alpha1.ProviderConfigKind}, + }, + }, + }, + }, + want: provErrors.MissingSecretRefError(), + }, + "ErrGetConnectionSecret": { + reason: "An error should be returned if we can't get our ProviderConfig's connection secret", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + switch o := obj.(type) { + case *v1alpha1.ProviderConfig: + o.Spec.Credentials.ConnectionSecretRef = common.LocalSecretReference{Name: "example"} + case *corev1.Secret: + return errBoom + } + return nil + }), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Database{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.DatabaseSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{Kind: v1alpha1.ProviderConfigKind}, + }, + }, + }, + }, + want: provErrors.GetSecretError(errBoom), + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := &connector{kube: tc.fields.kube, usage: tc.fields.usage, newDB: tc.fields.newDB} + _, err := e.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") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + o managed.ExternalObservation + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotDatabase": { + reason: "An error should be returned if the managed resource is not a *Database", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotDatabase), + }, + }, + "ErrNoDatabase": { + reason: "We should return ResourceExists: false when no database is found", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return sql.ErrNoRows }, + }, + }, + args: args{ + mg: &v1alpha1.Database{}, + }, + want: want{ + o: managed.ExternalObservation{ResourceExists: false}, + }, + }, + "ErrSelectDatabase": { + reason: "We should return any errors encountered while trying to select the database", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return errBoom }, + }, + }, + args: args{ + mg: &v1alpha1.Database{}, + }, + want: want{ + err: errors.Wrap(errBoom, errSelectDB), + }, + }, + "Success": { + reason: "We should return no error if we can successfully select our database", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.Database{}, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + ResourceLateInitialized: false, + }, + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + 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) + } + }) + } +} + +func TestCreate(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + c managed.ExternalCreation + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotDatabase": { + reason: "An error should be returned if the managed resource is not a *Database", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotDatabase), + }, + }, + "ErrExec": { + reason: "Any errors encountered while creating the database should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return errBoom }, + }, + }, + args: args{ + mg: &v1alpha1.Database{}, + }, + want: want{ + err: errors.Wrap(errBoom, errCreateDB), + }, + }, + "Success": { + reason: "No error should be returned when we successfully create a database", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.Database{}, + }, + want: want{ + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + 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) + } + }) + } +} + +func TestDelete(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + cases := map[string]struct { + reason string + fields fields + args args + want error + }{ + "ErrNotDatabase": { + reason: "An error should be returned if the managed resource is not a *Database", + args: args{ + mg: nil, + }, + want: errors.New(errNotDatabase), + }, + "ErrDropDB": { + reason: "Errors dropping a database should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + return errBoom + }, + }, + }, + args: args{ + mg: &v1alpha1.Database{}, + }, + want: errors.Wrap(errBoom, errDropDB), + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + _, 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/pkg/controller/namespaced/mysql/grant/reconciler.go b/pkg/controller/namespaced/mysql/grant/reconciler.go new file mode 100644 index 00000000..9857220b --- /dev/null +++ b/pkg/controller/namespaced/mysql/grant/reconciler.go @@ -0,0 +1,424 @@ +/* +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 grant + +import ( + "context" + "fmt" + "regexp" + "sort" + "strings" + + mysqldriver "github.com/go-sql-driver/mysql" + "github.com/pkg/errors" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/feature" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + namespacedv1alpha1 "github.com/crossplane-contrib/provider-sql/apis/namespaced/mysql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/pkg/clients/mysql" + "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/mysql/provider" + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/mysql/tls" +) + +const ( + errTrackPCUsage = "cannot track ProviderConfig usage" + errTLSConfig = "cannot load TLS config" + + errNotGrant = "managed resource is not a Grant custom resource" + errCreateGrant = "cannot create grant" + errRevokeGrant = "cannot revoke grant" + errCurrentGrant = "cannot show current grants" + + allPrivileges = "ALL PRIVILEGES" + errCodeNoSuchGrant = 1141 + maxConcurrency = 5 +) + +var ( + grantRegex = regexp.MustCompile(`^GRANT (.+) ON (\S+)\.(\S+) TO \S+@\S+?(\sWITH GRANT OPTION)?$`) +) + +// TODO(nateinaction): This looks wrong, can tracker creation be improved? +type tracker struct { + tracker *resource.ProviderConfigUsageTracker +} + +var _ resource.Tracker = &tracker{} + +func (t *tracker) Track(ctx context.Context, mg resource.Managed) error { + return t.tracker.Track(ctx, mg.(resource.ModernManaged)) +} + +// Setup adds a controller that reconciles Database managed resources. +func Setup(mgr ctrl.Manager, o xpcontroller.Options) error { + name := managed.ControllerName(namespacedv1alpha1.GrantGroupKind) + + t := resource.NewProviderConfigUsageTracker(mgr.GetClient(), &namespacedv1alpha1.ProviderConfigUsage{}) + trk := &tracker{tracker: t} + + reconcilerOptions := []managed.ReconcilerOption{ + managed.WithTypedExternalConnector(&connector{kube: mgr.GetClient(), usage: trk, newDB: mysql.New}), + managed.WithReferenceResolver(managed.NewAPISimpleReferenceResolver(mgr.GetClient())), + managed.WithLogger(o.Logger.WithValues("controller", name)), + managed.WithPollInterval(o.PollInterval), + managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), + } + if o.Features.Enabled(feature.EnableBetaManagementPolicies) { + reconcilerOptions = append(reconcilerOptions, managed.WithManagementPolicies()) + } + r := managed.NewReconciler(mgr, + resource.ManagedKind(namespacedv1alpha1.GrantGroupVersionKind), + reconcilerOptions..., + ) + return ctrl.NewControllerManagedBy(mgr). + Named(name). + For(&namespacedv1alpha1.Grant{}). + WithOptions(controller.Options{ + MaxConcurrentReconciles: maxConcurrency, + }). + Complete(r) +} + +type connector struct { + kube client.Client + usage resource.Tracker + newDB func(creds map[string][]byte, tls *string, binlog *bool) xsql.DB +} + +var _ managed.TypedExternalConnector[resource.Managed] = &connector{} + +func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.TypedExternalClient[resource.Managed], error) { + cr, ok := mg.(*namespacedv1alpha1.Grant) + if !ok { + return nil, errors.New(errNotGrant) + } + + if err := c.usage.Track(ctx, mg); err != nil { + return nil, errors.Wrap(err, errTrackPCUsage) + } + + // ProviderConfigReference could theoretically be nil, but in practice the + // DefaultProviderConfig initializer will set it before we get here. + providerInfo, err := provider.GetProviderConfig(ctx, c.kube, cr) + if err != nil { + return nil, err + } + + tlsName, err := tls.LoadConfig(ctx, c.kube, providerInfo.ProviderConfigName, providerInfo.TLS, providerInfo.TLSConfig) + if err != nil { + return nil, errors.Wrap(err, errTLSConfig) + } + + return &external{db: c.newDB(providerInfo.SecretData, tlsName, cr.Spec.ForProvider.BinLog)}, nil +} + +type external struct{ db xsql.DB } + +var _ managed.TypedExternalClient[resource.Managed] = &external{} + +func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { + cr, ok := mg.(*namespacedv1alpha1.Grant) + if !ok { + return managed.ExternalObservation{}, errors.New(errNotGrant) + } + + username, host := mysql.SplitUserHost(*cr.Spec.ForProvider.User) + dbname := defaultIdentifier(cr.Spec.ForProvider.Database) + table := defaultIdentifier(cr.Spec.ForProvider.Table) + + observedPrivileges, result, err := c.getPrivileges(ctx, username, host, dbname, table) + if err != nil { + return managed.ExternalObservation{}, err + } + if result != nil { + return *result, nil + } + + cr.Status.AtProvider.Privileges = observedPrivileges + + desiredPrivileges := cr.Spec.ForProvider.Privileges.ToStringSlice() + toGrant, toRevoke := diffPermissions(desiredPrivileges, observedPrivileges) + + cr.SetConditions(xpv1.Available()) + + return managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: len(toGrant) == 0 && len(toRevoke) == 0, + }, nil +} + +func defaultIdentifier(identifier *string) string { + if identifier != nil && *identifier != "*" { + return mysql.QuoteIdentifier(*identifier) + } + + return "*" +} + +func parseGrant(grant, dbname string, table string) (privileges []string) { + matches := grantRegex.FindStringSubmatch(grant) + if len(matches) == 5 && matches[2] == dbname && matches[3] == table { + privileges := strings.Split(matches[1], ", ") + + if matches[4] != "" { + privileges = append(privileges, "GRANT OPTION") + } + + return privileges + } + + return nil +} + +func (c *external) getPrivileges(ctx context.Context, username, host, dbname, table string) ([]string, *managed.ExternalObservation, error) { + privileges, err := c.parseGrantRows(ctx, username, host, dbname, table) + if err != nil { + var myErr *mysqldriver.MySQLError + if errors.As(err, &myErr) && myErr.Number == errCodeNoSuchGrant { + // The user doesn't (yet) exist and therefore no grants either + return nil, &managed.ExternalObservation{ResourceExists: false}, nil + } + + return nil, nil, errors.Wrap(err, errCurrentGrant) + } + + // In mysql when all grants are revoked from user, it still grants usage (meaning no + // privileges) on *.* So the grant can be considered as non existent, just like when + // privileges slice is nil/empty + // https://dev.mysql.com/doc/refman/8.0/en/privileges-provided.html#priv_usage + var ret []string + for _, p := range privileges { + if p != "USAGE" { + ret = append(ret, p) + } + } + + if ret == nil { + return nil, &managed.ExternalObservation{ResourceExists: false}, nil + } + + return ret, nil, nil +} + +func (c *external) parseGrantRows(ctx context.Context, username, host, dbname, table string) ([]string, error) { + query := fmt.Sprintf("SHOW GRANTS FOR %s@%s", mysql.QuoteValue(username), mysql.QuoteValue(host)) + rows, err := c.db.Query(ctx, xsql.Query{String: query}) + + if err != nil { + return nil, err + } + defer rows.Close() //nolint:errcheck + + var privileges []string + for rows.Next() { + var grant string + if err := rows.Scan(&grant); err != nil { + return nil, err + } + p := parseGrant(grant, dbname, table) + + if p != nil { + // found the grant we were looking for + privileges = p + break + } + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return privileges, nil +} + +func (c *external) Create(ctx context.Context, mg resource.Managed) (managed.ExternalCreation, error) { + cr, ok := mg.(*namespacedv1alpha1.Grant) + if !ok { + return managed.ExternalCreation{}, errors.New(errNotGrant) + } + + username, host := mysql.SplitUserHost(*cr.Spec.ForProvider.User) + dbname := defaultIdentifier(cr.Spec.ForProvider.Database) + table := defaultIdentifier(cr.Spec.ForProvider.Table) + + privileges, grantOption := getPrivilegesString(cr.Spec.ForProvider.Privileges.ToStringSlice()) + query := createGrantQuery(privileges, dbname, username, host, table, grantOption) + + if err := mysql.ExecWrapper(ctx, c.db, mysql.ExecQuery{Query: query, ErrorValue: errCreateGrant}); err != nil { + return managed.ExternalCreation{}, err + } + return managed.ExternalCreation{}, nil +} + +func (c *external) Update(ctx context.Context, mg resource.Managed) (managed.ExternalUpdate, error) { + cr, ok := mg.(*namespacedv1alpha1.Grant) + if !ok { + return managed.ExternalUpdate{}, errors.New(errNotGrant) + } + + username, host := mysql.SplitUserHost(*cr.Spec.ForProvider.User) + dbname := defaultIdentifier(cr.Spec.ForProvider.Database) + table := defaultIdentifier(cr.Spec.ForProvider.Table) + + observed := cr.Status.AtProvider.Privileges + desired := cr.Spec.ForProvider.Privileges.ToStringSlice() + toGrant, toRevoke := diffPermissions(desired, observed) + + if len(toRevoke) > 0 { + sort.Strings(toRevoke) + privileges, grantOption := getPrivilegesString(toRevoke) + query := createRevokeQuery(privileges, dbname, username, host, table, grantOption) + if err := mysql.ExecWrapper(ctx, c.db, + mysql.ExecQuery{ + Query: query, ErrorValue: errRevokeGrant, + }); err != nil { + return managed.ExternalUpdate{}, err + } + } + + if len(toGrant) > 0 { + sort.Strings(toGrant) + privileges, grantOption := getPrivilegesString(toGrant) + query := createGrantQuery(privileges, dbname, username, host, table, grantOption) + if err := mysql.ExecWrapper(ctx, c.db, + mysql.ExecQuery{ + Query: query, ErrorValue: errCreateGrant, + }); err != nil { + return managed.ExternalUpdate{}, err + } + } + return managed.ExternalUpdate{}, nil +} + +// getPrivilegesString returns a privileges string without grant option item and a grantOption boolean +func getPrivilegesString(privileges []string) (string, bool) { + privilegesWithoutGrantOption := []string{} + grantOption := false + for _, p := range privileges { + if p == "GRANT OPTION" { + grantOption = true + continue + } + privilegesWithoutGrantOption = append(privilegesWithoutGrantOption, p) + } + out := strings.Join(privilegesWithoutGrantOption, ", ") + return out, grantOption +} + +func createRevokeQuery(privileges, dbname, username, host, table string, grantOption bool) string { + result := fmt.Sprintf("REVOKE %s ON %s.%s FROM %s@%s", + privileges, + dbname, + table, + mysql.QuoteValue(username), + mysql.QuoteValue(host), + ) + + if grantOption { + result = fmt.Sprintf("%s WITH GRANT OPTION", result) + } + + return result +} + +func createGrantQuery(privileges, dbname, username, host, table string, grantOption bool) string { + result := fmt.Sprintf("GRANT %s ON %s.%s TO %s@%s", + privileges, + dbname, + table, + mysql.QuoteValue(username), + mysql.QuoteValue(host), + ) + + if grantOption { + result = fmt.Sprintf("%s WITH GRANT OPTION", result) + } + + return result +} + +func (c *external) Disconnect(ctx context.Context) error { + return nil +} + +func (c *external) Delete(ctx context.Context, mg resource.Managed) (managed.ExternalDelete, error) { + cr, ok := mg.(*namespacedv1alpha1.Grant) + if !ok { + return managed.ExternalDelete{}, errors.New(errNotGrant) + } + + username, host := mysql.SplitUserHost(*cr.Spec.ForProvider.User) + dbname := defaultIdentifier(cr.Spec.ForProvider.Database) + table := defaultIdentifier(cr.Spec.ForProvider.Table) + + privileges, grantOption := getPrivilegesString(cr.Spec.ForProvider.Privileges.ToStringSlice()) + query := createRevokeQuery(privileges, dbname, username, host, table, grantOption) + + if err := mysql.ExecWrapper(ctx, c.db, mysql.ExecQuery{Query: query, ErrorValue: errRevokeGrant}); err != nil { + var myErr *mysqldriver.MySQLError + if errors.As(err, &myErr) && myErr.Number == errCodeNoSuchGrant { + // MySQL automatically deletes related grants if the user has been deleted + return managed.ExternalDelete{}, nil + } + + return managed.ExternalDelete{}, err + } + + return managed.ExternalDelete{}, nil +} + +func diffPermissions(desired, observed []string) ([]string, []string) { + desiredMap := make(map[string]struct{}, len(desired)) + observedMap := make(map[string]struct{}, len(observed)) + + for _, desiredPrivilege := range desired { + // Special case because ALL is an alias for "ALL PRIVILEGES" + desiredPrivilegeMapped := strings.ReplaceAll(desiredPrivilege, allPrivileges, "ALL") + desiredMap[desiredPrivilegeMapped] = struct{}{} + } + for _, observedPrivilege := range observed { + // Special case because ALL is an alias for "ALL PRIVILEGES" + observedPrivilegeMapped := strings.ReplaceAll(observedPrivilege, allPrivileges, "ALL") + observedMap[observedPrivilegeMapped] = struct{}{} + } + + var toGrant []string + var toRevoke []string + + for desiredPrivilege := range desiredMap { + if _, ok := observedMap[desiredPrivilege]; !ok { + toGrant = append(toGrant, desiredPrivilege) + } + } + + for observedPrivilege := range observedMap { + if _, ok := desiredMap[observedPrivilege]; !ok { + toRevoke = append(toRevoke, observedPrivilege) + } + } + + return toGrant, toRevoke +} diff --git a/pkg/controller/namespaced/mysql/grant/reconciler_test.go b/pkg/controller/namespaced/mysql/grant/reconciler_test.go new file mode 100644 index 00000000..ccc9fab3 --- /dev/null +++ b/pkg/controller/namespaced/mysql/grant/reconciler_test.go @@ -0,0 +1,1308 @@ +/* +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 grant + +import ( + "context" + "database/sql" + "strings" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/crossplane-contrib/provider-sql/apis/namespaced/mysql/v1alpha1" + "github.com/go-sql-driver/mysql" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/crossplane/crossplane-runtime/v2/apis/common" + xpv2 "github.com/crossplane/crossplane-runtime/v2/apis/common/v2" + "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/crossplane-contrib/provider-sql/pkg/clients/xsql" + provErrors "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/errors" +) + +type mockDB struct { + MockExec func(ctx context.Context, q xsql.Query) error + MockExecTx func(ctx context.Context, ql []xsql.Query) error + MockScan func(ctx context.Context, q xsql.Query, dest ...interface{}) error + MockQuery func(ctx context.Context, q xsql.Query) (*sql.Rows, error) + MockGetConnectionDetails func(username, password string) managed.ConnectionDetails +} + +func (m mockDB) Exec(ctx context.Context, q xsql.Query) error { + return m.MockExec(ctx, q) +} + +func (m mockDB) ExecTx(ctx context.Context, ql []xsql.Query) error { + return m.MockExecTx(ctx, ql) +} + +func (m mockDB) Scan(ctx context.Context, q xsql.Query, dest ...interface{}) error { + return m.MockScan(ctx, q, dest...) +} + +func (m mockDB) Query(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return m.MockQuery(ctx, q) +} + +func (m mockDB) GetConnectionDetails(username, password string) managed.ConnectionDetails { + return m.MockGetConnectionDetails(username, password) +} + +func TestConnect(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + kube client.Client + usage resource.Tracker + newDB func(creds map[string][]byte, tls *string, binlog *bool) xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + cases := map[string]struct { + reason string + fields fields + args args + want error + }{ + "ErrNotGrant": { + reason: "An error should be returned if the managed resource is not a *Grant", + args: args{ + mg: nil, + }, + want: errors.New(errNotGrant), + }, + "ErrTrackProviderConfigUsage": { + reason: "An error should be returned if we can't track our ProviderConfig usage", + fields: fields{ + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return errBoom }), + }, + args: args{ + mg: &v1alpha1.Grant{}, + }, + want: errors.Wrap(errBoom, errTrackPCUsage), + }, + "ErrInvalidProviderConfigKind": { + reason: "An error should be returned if the ProviderConfig kind is invalid", + fields: fields{ + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{Kind: "NotValid"}, + }, + }, + }, + }, + want: provErrors.InvalidProviderConfigKindError("NotValid"), + }, + "ErrGetProviderConfig": { + reason: "An error should be returned if we can't get our ProviderConfig", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(errBoom), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetProviderConfigError(errBoom), + }, + "ErrClusterGetProviderConfig": { + reason: "An error should be returned if we can't get our ClusterProviderConfig", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(errBoom), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ClusterProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetClusterProviderConfigError(errBoom), + }, + "ErrMissingConnectionSecret": { + reason: "An error should be returned if our ProviderConfig doesn't specify a connection secret", + fields: fields{ + kube: &test.MockClient{ + // We call get to populate the Grant struct, then again + // to populate the (empty) ProviderConfig struct, resulting + // in a ProviderConfig with a nil connection secret. + MockGet: test.NewMockGetFn(nil), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ClusterProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.MissingSecretRefError(), + }, + "ErrGetConnectionSecret": { + reason: "An error should be returned if we can't get our ProviderConfig's connection secret", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + switch o := obj.(type) { + case *v1alpha1.ClusterProviderConfig: + o.Spec.Credentials.ConnectionSecretRef = common.SecretReference{ + Name: "example", + Namespace: "default", + } + case *corev1.Secret: + return errBoom + } + return nil + }), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ClusterProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetSecretError(errBoom), + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := &connector{kube: tc.fields.kube, usage: tc.fields.usage, newDB: tc.fields.newDB} + _, err := e.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") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + o managed.ExternalObservation + err error + observedPrivileges []string + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotGrant": { + reason: "An error should be returned if the managed resource is not a *Grant", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotGrant), + }, + }, + "SuccessNoGrant": { + reason: "We should return ResourceExists: false when no grant is found, being privileges result empty", + fields: fields{ + db: mockDB{ + MockQuery: func(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return mockRowsToSQLRows(sqlmock.NewRows([]string{})), nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + User: ptr.To("test-example"), + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ResourceExists: false}, + }, + }, + "ErrSelectGrant": { + reason: "We should return any errors encountered while trying to show the grants", + fields: fields{ + db: mockDB{ + MockQuery: func(ctx context.Context, q xsql.Query) (*sql.Rows, error) { return &sql.Rows{}, errBoom }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + User: ptr.To("test-example"), + }, + }, + }, + }, + want: want{ + err: errors.Wrap(errBoom, errCurrentGrant), + }, + }, + "SuccessNoUser": { + reason: "We should return no error if the user doesn't exist", + fields: fields{ + db: mockDB{ + MockQuery: func(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return mockRowsToSQLRows( + sqlmock.NewRows([]string{"Grants"}). + AddRow("GRANT CREATE, DROP ON `success-db`.* TO 'no-user'@%"). + RowError(0, &mysql.MySQLError{Number: errCodeNoSuchGrant}), + ), nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("success-db"), + User: ptr.To("no-user"), + Privileges: v1alpha1.GrantPrivileges{"DROP", "CREATE"}, + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ResourceExists: false}, + }, + }, + "Success": { + reason: "We should return no error if we can successfully show our grants", + fields: fields{ + db: mockDB{ + MockQuery: func(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return mockRowsToSQLRows( + sqlmock.NewRows( + []string{"Grants"}, + ).AddRow("GRANT " + allPrivileges + " ON `success-db`.* TO 'success-user'@%"), + ), nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("success-db"), + User: ptr.To("success-user"), + Privileges: v1alpha1.GrantPrivileges{"ALL"}, + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + }, + err: nil, + observedPrivileges: []string{allPrivileges}, + }, + }, + "SuccessGrantOptionNoDatabase": { + reason: "We should return no error if we can successfully show our grants", + fields: fields{ + db: mockDB{ + MockQuery: func(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return mockRowsToSQLRows( + sqlmock.NewRows( + []string{"Grants"}, + ).AddRow("GRANT INSERT, SELECT ON *.* TO 'success-user'@% WITH GRANT OPTION"), + ), nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + User: ptr.To("success-user"), + Privileges: v1alpha1.GrantPrivileges{"INSERT", "SELECT", "GRANT OPTION"}, + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + }, + err: nil, + observedPrivileges: []string{ + "GRANT OPTION", + "INSERT", + "SELECT", + }, + }, + }, + "SuccessGrantOptionWithDatabase": { + reason: "We should return no error if we can successfully show our grants", + fields: fields{ + db: mockDB{ + MockQuery: func(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return mockRowsToSQLRows( + sqlmock.NewRows( + []string{"Grants"}, + ).AddRow("GRANT INSERT, SELECT ON `success-db`.* TO 'success-user'@% WITH GRANT OPTION"), + ), nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("success-db"), + User: ptr.To("success-user"), + Privileges: v1alpha1.GrantPrivileges{"INSERT", "SELECT", "GRANT OPTION"}, + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + }, + err: nil, + observedPrivileges: []string{ + "GRANT OPTION", + "INSERT", + "SELECT", + }, + }, + }, + "SuccessDiffGrants": { + reason: "We should return no error if different grants exist for the provided database", + fields: fields{ + db: mockDB{ + MockQuery: func(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return mockRowsToSQLRows( + sqlmock.NewRows( + []string{"Grants"}, + ).AddRow("GRANT CREATE ON `success-db`.* TO 'diff-user'@%"), + ), nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("success-db"), + User: ptr.To("diff-user"), + Privileges: v1alpha1.GrantPrivileges{"DROP"}, + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: false, + }, + observedPrivileges: []string{"CREATE"}, + }, + }, + "SuccessDiffGrantNoDatabaseNoTable": { + reason: "We should return no error if different grants exist and no database and table are provided", + fields: fields{ + db: mockDB{ + MockQuery: func(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return mockRowsToSQLRows( + sqlmock.NewRows([]string{"Grants"}). + AddRow("GRANT INSERT ON *.* TO 'success-user'@%"), + ), nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + User: ptr.To("success-user"), + Privileges: v1alpha1.GrantPrivileges{"DROP", "CREATE"}, + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: false, + }, + observedPrivileges: []string{"INSERT"}, + err: nil, + }, + }, + "SuccessDiffGrantUsage": { + reason: "We should return ResourceExists: false when a USAGE grant is found, since it is equivalent to having no grants", + fields: fields{ + db: mockDB{ + MockQuery: func(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return mockRowsToSQLRows( + sqlmock.NewRows([]string{"Grants"}). + AddRow("GRANT USAGE ON *.* TO 'success-user'@%"), + ), nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + User: ptr.To("success-user"), + Privileges: v1alpha1.GrantPrivileges{"DROP", "CREATE"}, + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: false, + }, + err: nil, + }, + }, + "SuccessManyGrants": { + reason: "We should return no error if there are more than one grant for a user", + fields: fields{ + db: mockDB{ + MockQuery: func(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return mockRowsToSQLRows( + sqlmock.NewRows([]string{"Grants"}). + AddRow("GRANT CREATE, DROP ON `success-db`.* TO 'success-user'@%"). + AddRow("GRANT EVENT ON `success-db`.* TO 'success-user'@%"), + ), nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("success-db"), + User: ptr.To("success-user"), + Privileges: v1alpha1.GrantPrivileges{"DROP", "CREATE"}, + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + }, + err: nil, + observedPrivileges: []string{"CREATE", "DROP"}, + }, + }, + "SuccessGrantNoDatabaseNoTable": { + reason: "We should return no error if no database and table were provided and grants were equal to the ones in resource spec", + fields: fields{ + db: mockDB{ + MockQuery: func(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return mockRowsToSQLRows( + sqlmock.NewRows([]string{"Grants"}). + AddRow("GRANT CREATE, DROP ON *.* TO 'success-user'@%"), + ), nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + User: ptr.To("success-user"), + Privileges: v1alpha1.GrantPrivileges{"DROP", "CREATE"}, + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + }, + err: nil, + observedPrivileges: []string{"CREATE", "DROP"}, + }, + }, + "SuccessGrantWithTables": { + reason: "We should see the grants in sync when using a table", + fields: fields{ + db: mockDB{ + MockQuery: func(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return mockRowsToSQLRows( + sqlmock.NewRows([]string{"Grants"}). + AddRow("GRANT CREATE, DROP ON `success-db`.`success-table` TO 'success-user'@%"), + ), nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("success-db"), + User: ptr.To("success-user"), + Table: ptr.To("success-table"), + Privileges: v1alpha1.GrantPrivileges{"DROP", "CREATE"}, + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + }, + err: nil, + observedPrivileges: []string{"CREATE", "DROP"}, + }, + }, + "SuccessDiffGrantWithTables": { + reason: "We should see the grants out of sync when using a table", + fields: fields{ + db: mockDB{ + MockQuery: func(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return mockRowsToSQLRows( + sqlmock.NewRows([]string{"Grants"}). + AddRow("GRANT CREATE, DROP ON `success-db`.`success-table` TO 'success-user'@%"), + ), nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("success-db"), + User: ptr.To("success-user"), + Table: ptr.To("success-table"), + Privileges: v1alpha1.GrantPrivileges{"INSERT", "CREATE"}, + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: false, + }, + err: nil, + observedPrivileges: []string{"CREATE", "DROP"}, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + 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 { + cr, _ := tc.args.mg.(*v1alpha1.Grant) + if diff := cmp.Diff(tc.want.observedPrivileges, cr.Status.AtProvider.Privileges, equateSlices()...); 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 { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + c managed.ExternalCreation + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotGrant": { + reason: "An error should be returned if the managed resource is not a *Grant", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotGrant), + }, + }, + "ErrExec": { + reason: "Any errors encountered while creating the grant should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + if strings.HasPrefix(q.String, "GRANT") { + return errBoom + } + + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + User: ptr.To("test-example"), + }, + }, + }, + }, + want: want{ + err: errors.Wrap(errBoom, errCreateGrant), + }, + }, + "Success": { + reason: "No error should be returned when we successfully create a grant", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + User: ptr.To("test-example"), + Privileges: v1alpha1.GrantPrivileges{"INSERT", "SELECT"}, + }, + }, + }, + }, + want: want{ + err: nil, + }, + }, + "SuccessNoDatabase": { + reason: "No error should be returned when we successfully create a grant with no database", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + User: ptr.To("test-example"), + Privileges: v1alpha1.GrantPrivileges{"INSERT", "SELECT"}, + }, + }, + }, + }, + want: want{ + err: nil, + }, + }, + "SuccessGrantOption": { + reason: "No error should be returned when we successfully create a grant with grant option", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + if strings.HasPrefix(q.String, "GRANT") && + !strings.HasSuffix(q.String, "WITH GRANT OPTION") { + return errBoom + } + + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + User: ptr.To("test-example"), + Privileges: v1alpha1.GrantPrivileges{"GRANT OPTION", "ALL"}, + }, + }, + }, + }, + want: want{ + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + 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) + } + }) + } +} + +func TestUpdate(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + c managed.ExternalUpdate + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotGrant": { + reason: "An error should be returned if the managed resource is not a *Grant", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotGrant), + }, + }, + "ErrExecRevokeNotRequired": { + reason: "Any errors encountered while revoking a not required privilege from the desired ones should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + if strings.HasPrefix(q.String, "REVOKE") { + return errBoom + } + + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + User: ptr.To("test-example"), + Privileges: v1alpha1.GrantPrivileges{"CREATE"}, + }, + }, + Status: v1alpha1.GrantStatus{ + AtProvider: v1alpha1.GrantObservation{ + Privileges: []string{"INSERT", "CREATE"}, + }, + }, + }, + }, + want: want{ + err: errors.Wrap(errBoom, errRevokeGrant), + }, + }, + "ErrExecGrantMissing": { + reason: "Any errors encountered while granting a missing privilege from the desired ones should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + if strings.HasPrefix(q.String, "GRANT") { + return errBoom + } + + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + User: ptr.To("test-example"), + Privileges: v1alpha1.GrantPrivileges{"CREATE", "SELECT"}, + }, + }, + Status: v1alpha1.GrantStatus{ + AtProvider: v1alpha1.GrantObservation{ + Privileges: []string{"CREATE"}, + }, + }, + }, + }, + want: want{ + err: errors.Wrap(errBoom, errCreateGrant), + }, + }, + "SuccessEqualObservedDesired": { + reason: "No query should be executed and no error should be returned when there is no diff between desired and observed privileges", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + return errBoom + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + User: ptr.To("test-example"), + Privileges: v1alpha1.GrantPrivileges{"CREATE", "DROP"}, + }, + }, + Status: v1alpha1.GrantStatus{ + AtProvider: v1alpha1.GrantObservation{ + Privileges: []string{"DROP", "CREATE"}, + }, + }, + }, + }, + want: want{ + err: nil, + c: managed.ExternalUpdate{}, + }, + }, + "SuccessDiffObservedDesiredGrantMissing": { + reason: "No error should be returned when granting a missing privilege from the desired ones", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + if strings.HasPrefix(q.String, "REVOKE") { + return errBoom + } + + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + User: ptr.To("test-example"), + Privileges: v1alpha1.GrantPrivileges{"CREATE", "DROP"}, + }, + }, + Status: v1alpha1.GrantStatus{ + AtProvider: v1alpha1.GrantObservation{ + Privileges: []string{"DROP"}, + }, + }, + }, + }, + want: want{ + err: nil, + c: managed.ExternalUpdate{}, + }, + }, + "SuccessDiffObservedDesiredRevokeNotRequired": { + reason: "No error should be returned when revoking a not required privilege from the desired ones", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + if strings.HasPrefix(q.String, "GRANT") { + return errBoom + } + + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + User: ptr.To("test-example"), + Privileges: v1alpha1.GrantPrivileges{"DROP"}, + }, + }, + Status: v1alpha1.GrantStatus{ + AtProvider: v1alpha1.GrantObservation{ + Privileges: []string{"DROP", "SELECT"}, + }, + }, + }, + }, + want: want{ + err: nil, + c: managed.ExternalUpdate{}, + }, + }, + "SuccessGrantOption": { + reason: "No error should be returned when we successfully create a grant with grant option", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + if strings.HasPrefix(q.String, "GRANT") && + !strings.HasSuffix(q.String, "WITH GRANT OPTION") { + return errBoom + } + + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + User: ptr.To("test-example"), + Privileges: v1alpha1.GrantPrivileges{"GRANT OPTION", "ALL"}, + }, + }, + }, + }, + want: want{ + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{ + db: tc.fields.db, + } + got, err := e.Update(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, cmpopts.IgnoreMapEntries(func(key string, _ []byte) bool { return key == "password" })); diff != "" { + t.Errorf("\n%s\ne.Create(...): -want, +got:\n%s\n", tc.reason, diff) + } + }) + } +} + +func TestDelete(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + cases := map[string]struct { + reason string + fields fields + args args + want error + }{ + "ErrNotGrant": { + reason: "An error should be returned if the managed resource is not a *Grant", + args: args{ + mg: nil, + }, + want: errors.New(errNotGrant), + }, + "ErrDropGrant": { + reason: "Errors dropping a grant should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + if strings.HasPrefix(q.String, "REVOKE") { + return errBoom + } + + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + User: ptr.To("test-example"), + }, + }, + }, + }, + want: errors.Wrap(errBoom, errRevokeGrant), + }, + "Success": { + reason: "No error should be returned if the grant was revoked", + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + User: ptr.To("test-example"), + }, + }, + }, + }, + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return nil }, + }, + }, + want: nil, + }, + "SuccessGrantGone": { + reason: "No error should be returned if the grant is already revoked", + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + User: ptr.To("test-example"), + }, + }, + }, + }, + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + if strings.HasPrefix(q.String, "REVOKE") { + return &mysql.MySQLError{Number: errCodeNoSuchGrant} + } + + return nil + }, + }, + }, + want: nil, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + _, 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) + } + }) + } +} + +func mockRowsToSQLRows(mockRows *sqlmock.Rows) *sql.Rows { + db, mock, _ := sqlmock.New() + mock.ExpectQuery("select").WillReturnRows(mockRows) + rows, err := db.Query("select") + if err != nil { + println("%v", err) + return nil + } + return rows +} + +func Test_diffPermissions(t *testing.T) { + type args struct { + desired []string + observed []string + } + type want struct { + toGrant []string + toRevoke []string + } + cases := map[string]struct { + args + want + }{ + "AsDesired": { + args: args{ + desired: []string{"CREATE TABLE", "DELETE"}, + observed: []string{"CREATE TABLE", "DELETE"}, + }, + want: want{ + toGrant: nil, + toRevoke: nil, + }, + }, + "AsDesiredOrderNotMatter": { + args: args{ + desired: []string{"CREATE TABLE", "DELETE"}, + observed: []string{"DELETE", "CREATE TABLE"}, + }, + want: want{ + toGrant: nil, + toRevoke: nil, + }, + }, + "NeedsGrant": { + args: args{ + desired: []string{"CREATE TABLE", "DELETE"}, + observed: []string{"CREATE TABLE"}, + }, + want: want{ + toGrant: []string{"DELETE"}, + }, + }, + "NeedsRevoke": { + args: args{ + desired: []string{"CREATE TABLE"}, + observed: []string{"CREATE TABLE", "DELETE"}, + }, + want: want{ + toRevoke: []string{"DELETE"}, + }, + }, + "NeedsBoth": { + args: args{ + desired: []string{"CREATE TABLE"}, + observed: []string{"DELETE"}, + }, + want: want{ + toGrant: []string{"CREATE TABLE"}, + toRevoke: []string{"DELETE"}, + }, + }, + "GrantAll": { + args: args{ + desired: []string{"CREATE TABLE", "DELETE", "INSERT"}, + }, + want: want{ + toGrant: []string{"CREATE TABLE", "DELETE", "INSERT"}, + }, + }, + "RevokeAll": { + args: args{ + observed: []string{"CREATE TABLE", "DELETE", "INSERT"}, + }, + want: want{ + toRevoke: []string{"CREATE TABLE", "DELETE", "INSERT"}, + }, + }, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + gotToGrant, gotToRevoke := diffPermissions(tc.desired, tc.observed) + if diff := cmp.Diff(tc.toGrant, gotToGrant, equateSlices()...); diff != "" { + t.Errorf("\ndiffPermissions(...): -want toGrant, +got toGrant:\n%s", diff) + } + if diff := cmp.Diff(tc.toRevoke, gotToRevoke, equateSlices()...); diff != "" { + t.Errorf("\ndiffPermissions(...): -want toRevoke, +got toRevoke:\n%s", diff) + } + }) + } +} + +func equateSlices() []cmp.Option { + return []cmp.Option{ + cmp.Transformer("mapAllPrivileges", func(s string) string { + if s == "ALL PRIVILEGES" { + return "ALL" + } + return s + }), + cmpopts.SortSlices(func(x, y string) bool { + return x < y + }), + } +} diff --git a/pkg/controller/namespaced/mysql/mysql.go b/pkg/controller/namespaced/mysql/mysql.go new file mode 100644 index 00000000..24fae805 --- /dev/null +++ b/pkg/controller/namespaced/mysql/mysql.go @@ -0,0 +1,44 @@ +/* +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 mysql + +import ( + ctrl "sigs.k8s.io/controller-runtime" + + "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/mysql/config" + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/mysql/database" + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/mysql/grant" + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/mysql/user" +) + +// Setup creates all MySQL controllers with the supplied logger and adds +// them to the supplied manager. +func Setup(mgr ctrl.Manager, o controller.Options) error { + for _, setup := range []func(ctrl.Manager, controller.Options) error{ + config.Setup, + database.Setup, + user.Setup, + grant.Setup, + } { + if err := setup(mgr, o); err != nil { + return err + } + } + return nil +} diff --git a/pkg/controller/namespaced/mysql/provider/provider.go b/pkg/controller/namespaced/mysql/provider/provider.go new file mode 100644 index 00000000..612cee8a --- /dev/null +++ b/pkg/controller/namespaced/mysql/provider/provider.go @@ -0,0 +1,89 @@ +package provider + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + "context" + + "github.com/crossplane-contrib/provider-sql/apis/namespaced/mysql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/errors" + + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type ProviderInfo struct { + ProviderConfigName string + SecretData map[string][]byte + TLS *string + TLSConfig *v1alpha1.TLSConfig +} + +func GetProviderConfig(ctx context.Context, kube client.Client, mg resource.ModernManaged) (ProviderInfo, error) { + var ( + secretKey *client.ObjectKey + tlsMode *string + tlsConfig *v1alpha1.TLSConfig + ) + + switch mg.GetProviderConfigReference().Kind { + case v1alpha1.ProviderConfigKind: + providerConfig := &v1alpha1.ProviderConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: mg.GetProviderConfigReference().Name, + Namespace: mg.GetNamespace(), + }, + } + + if err := kube.Get(ctx, client.ObjectKeyFromObject(providerConfig), providerConfig); err != nil { + return ProviderInfo{}, errors.GetProviderConfigError(err) + } + + secretKey = &client.ObjectKey{ + Name: providerConfig.Spec.Credentials.ConnectionSecretRef.Name, + Namespace: mg.GetNamespace(), + } + tlsMode = providerConfig.Spec.TLS + tlsConfig = providerConfig.Spec.TLSConfig + + case v1alpha1.ClusterProviderConfigKind: + clusterProviderConfig := &v1alpha1.ClusterProviderConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: mg.GetProviderConfigReference().Name, + }, + } + + if err := kube.Get(ctx, client.ObjectKeyFromObject(clusterProviderConfig), clusterProviderConfig); err != nil { + return ProviderInfo{}, errors.GetClusterProviderConfigError(err) + } + + secretKey = &client.ObjectKey{ + Name: clusterProviderConfig.Spec.Credentials.ConnectionSecretRef.Name, + Namespace: clusterProviderConfig.Spec.Credentials.ConnectionSecretRef.Namespace, + } + tlsMode = clusterProviderConfig.Spec.TLS + tlsConfig = clusterProviderConfig.Spec.TLSConfig + + default: + return ProviderInfo{}, errors.InvalidProviderConfigKindError(mg.GetProviderConfigReference().Kind) + } + + if secretKey.Name == "" || secretKey.Namespace == "" { + return ProviderInfo{}, errors.MissingSecretRefError() + } + + s := &corev1.Secret{} + err := kube.Get(ctx, *secretKey, s) + if err != nil { + return ProviderInfo{}, errors.GetSecretError(err) + } + + return ProviderInfo{ + ProviderConfigName: mg.GetProviderConfigReference().Name, + SecretData: s.Data, + TLS: tlsMode, + TLSConfig: tlsConfig, + }, nil +} diff --git a/pkg/controller/namespaced/mysql/tls/tls.go b/pkg/controller/namespaced/mysql/tls/tls.go new file mode 100644 index 00000000..0bdb1531 --- /dev/null +++ b/pkg/controller/namespaced/mysql/tls/tls.go @@ -0,0 +1,119 @@ +package tls + +import ( + "context" + "crypto/tls" + "crypto/x509" + "fmt" + + namespacedv1alpha1 "github.com/crossplane-contrib/provider-sql/apis/namespaced/mysql/v1alpha1" + "github.com/crossplane/crossplane-runtime/v2/apis/common" + "github.com/go-sql-driver/mysql" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// LoadConfig loads the TLS configuration when tls mode is set to custom and +// returns the tls name of registered configuration. +func LoadConfig( + ctx context.Context, + kube client.Client, + providerConfigName string, + tlsMode *string, + tlsConfig *namespacedv1alpha1.TLSConfig, +) (*string, error) { + if tlsMode == nil || *tlsMode != "custom" { + if tlsConfig != nil { + return nil, fmt.Errorf("tlsConfig is allowed only when tls=custom") + } + + return tlsMode, nil + } + + if err := validateTLSConfig(tlsConfig); err != nil { + return nil, err + } + + tlsName := fmt.Sprintf("custom-%s", providerConfigName) + err := registerTLS(ctx, kube, tlsName, tlsConfig) + if err != nil { + return nil, err + } + return &tlsName, nil +} + +func validateTLSConfig(cfg *namespacedv1alpha1.TLSConfig) error { + if cfg == nil || + cfg.CACert.SecretRef.Name == "" || + cfg.CACert.SecretRef.Key == "" || + cfg.ClientCert.SecretRef.Name == "" || + cfg.ClientCert.SecretRef.Key == "" || + cfg.ClientKey.SecretRef.Name == "" || + cfg.ClientKey.SecretRef.Key == "" { + return fmt.Errorf("tlsConfig is required when tls=custom") + } + return nil +} + +func registerTLS(ctx context.Context, kube client.Client, tlsName string, cfg *namespacedv1alpha1.TLSConfig) error { + if cfg == nil { + return nil + } + + caCert, err := getSecret(ctx, kube, cfg.CACert.SecretRef) + if err != nil { + return fmt.Errorf("cannot get CA certificate: %w", err) + } + + pool := x509.NewCertPool() + if ok := pool.AppendCertsFromPEM(caCert); !ok { + return fmt.Errorf("cannot append CA certificate to pool") + } + + keyPair, err := getClientKeyPair(ctx, kube, cfg) + if err != nil { + return err + } + + return mysql.RegisterTLSConfig(tlsName, &tls.Config{ + RootCAs: pool, + Certificates: []tls.Certificate{keyPair}, + InsecureSkipVerify: cfg.InsecureSkipVerify, //nolint:gosec // This is only required by integration tests and should never be used in production + }) +} + +func getClientKeyPair(ctx context.Context, kube client.Client, cfg *namespacedv1alpha1.TLSConfig) (tls.Certificate, error) { + cert, err := getSecret(ctx, kube, cfg.ClientCert.SecretRef) + if err != nil { + return tls.Certificate{}, fmt.Errorf("cannot get client certificate: %w", err) + } + + key, err := getSecret(ctx, kube, cfg.ClientKey.SecretRef) + if err != nil { + return tls.Certificate{}, fmt.Errorf("cannot get client key: %w", err) + } + + keyPair, err := tls.X509KeyPair(cert, key) + if err != nil { + return tls.Certificate{}, errors.Wrap(err, "cannot make client certificate") + } + return keyPair, nil +} + +func getSecret(ctx context.Context, kube client.Client, sel common.SecretKeySelector) ([]byte, error) { + secret := &corev1.Secret{} + if err := kube.Get(ctx, types.NamespacedName{ + Namespace: sel.Namespace, + Name: sel.Name}, secret); err != nil { + return nil, fmt.Errorf("cannot get Secret %q in namespace %q: %w", sel.Name, sel.Namespace, err) + } + + data, ok := secret.Data[sel.Key] + if !ok { + return nil, fmt.Errorf("key %q not found in Secret %q", sel.Key, sel.Name) + } + + return data, nil +} diff --git a/pkg/controller/namespaced/mysql/user/reconciler.go b/pkg/controller/namespaced/mysql/user/reconciler.go new file mode 100644 index 00000000..843011ec --- /dev/null +++ b/pkg/controller/namespaced/mysql/user/reconciler.go @@ -0,0 +1,402 @@ +/* +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 user + +import ( + "context" + "fmt" + "strings" + + "github.com/pkg/errors" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/feature" + "github.com/crossplane/crossplane-runtime/v2/pkg/meta" + "github.com/crossplane/crossplane-runtime/v2/pkg/password" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + namespacedv1alpha1 "github.com/crossplane-contrib/provider-sql/apis/namespaced/mysql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/pkg/clients/mysql" + "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/mysql/provider" + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/mysql/tls" +) + +const ( + errTrackPCUsage = "cannot track ProviderConfig usage" + errTLSConfig = "cannot load TLS config" + + errNotUser = "managed resource is not a User custom resource" + errSelectUser = "cannot select user" + errCreateUser = "cannot create user" + errDropUser = "cannot drop user" + errUpdateUser = "cannot update user" + errGetPasswordSecretFailed = "cannot get password secret" + errCompareResourceOptions = "cannot compare desired and observed resource options" + + maxConcurrency = 5 +) + +// TODO(nateinaction): This looks wrong, can tracker creation be improved? +type tracker struct { + tracker *resource.ProviderConfigUsageTracker +} + +var _ resource.Tracker = &tracker{} + +func (t *tracker) Track(ctx context.Context, mg resource.Managed) error { + return t.tracker.Track(ctx, mg.(resource.ModernManaged)) +} + +// Setup adds a controller that reconciles Database managed resources. +func Setup(mgr ctrl.Manager, o xpcontroller.Options) error { + name := managed.ControllerName(namespacedv1alpha1.UserGroupKind) + + t := resource.NewProviderConfigUsageTracker(mgr.GetClient(), &namespacedv1alpha1.ProviderConfigUsage{}) + trk := &tracker{tracker: t} + + reconcilerOptions := []managed.ReconcilerOption{ + managed.WithTypedExternalConnector(&connector{kube: mgr.GetClient(), usage: trk, newDB: mysql.New}), + managed.WithLogger(o.Logger.WithValues("controller", name)), + managed.WithPollInterval(o.PollInterval), + managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), + } + if o.Features.Enabled(feature.EnableBetaManagementPolicies) { + reconcilerOptions = append(reconcilerOptions, managed.WithManagementPolicies()) + } + r := managed.NewReconciler(mgr, + resource.ManagedKind(namespacedv1alpha1.UserGroupVersionKind), + reconcilerOptions..., + ) + return ctrl.NewControllerManagedBy(mgr). + Named(name). + For(&namespacedv1alpha1.User{}). + WithOptions(controller.Options{ + MaxConcurrentReconciles: maxConcurrency, + }). + Complete(r) +} + +type connector struct { + kube client.Client + usage resource.Tracker + newDB func(creds map[string][]byte, tls *string, binlog *bool) xsql.DB +} + +var _ managed.TypedExternalConnector[resource.Managed] = &connector{} + +func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.TypedExternalClient[resource.Managed], error) { + cr, ok := mg.(*namespacedv1alpha1.User) + if !ok { + return nil, errors.New(errNotUser) + } + + if err := c.usage.Track(ctx, mg); err != nil { + return nil, errors.Wrap(err, errTrackPCUsage) + } + + // ProviderConfigReference could theoretically be nil, but in practice the + // DefaultProviderConfig initializer will set it before we get here. + providerInfo, err := provider.GetProviderConfig(ctx, c.kube, cr) + if err != nil { + return nil, err + } + + tlsName, err := tls.LoadConfig(ctx, c.kube, providerInfo.ProviderConfigName, providerInfo.TLS, providerInfo.TLSConfig) + if err != nil { + return nil, errors.Wrap(err, errTLSConfig) + } + + return &external{ + db: c.newDB(providerInfo.SecretData, tlsName, cr.Spec.ForProvider.BinLog), + kube: c.kube, + }, nil +} + +type external struct { + db xsql.DB + kube client.Client +} + +var _ managed.TypedExternalClient[resource.Managed] = &external{} + +func handleClause(clause string, value *int, out *[]string) { + // If clause is not set (nil pointer), do not push a setting. + // This means the default is applied. + if value == nil { + return + } + + *out = append(*out, fmt.Sprintf("%s %d", clause, *value)) +} + +func resourceOptionsToClauses(r *namespacedv1alpha1.ResourceOptions) []string { + // Never copy user inputted data to this string. These values are + // passed directly into the query. + ro := []string{} + + if r == nil { + return ro + } + + handleClause("MAX_QUERIES_PER_HOUR", r.MaxQueriesPerHour, &ro) + handleClause("MAX_UPDATES_PER_HOUR", r.MaxUpdatesPerHour, &ro) + handleClause("MAX_CONNECTIONS_PER_HOUR", r.MaxConnectionsPerHour, &ro) + handleClause("MAX_USER_CONNECTIONS", r.MaxUserConnections, &ro) + + return ro +} + +func changedResourceOptions(existing []string, desired []string) ([]string, error) { + out := []string{} + + // Make sure existing observation has at least as many items as + // desired. If it does not, then we cannot safely compare + // resource options. + if len(existing) < len(desired) { + return nil, errors.New(errCompareResourceOptions) + } + + // The input slices here are outputted by resourceOptionsToClauses above. + // Because these are created by repeated calls to negateClause in the + // same order, we can rely on each clause being in the same array + // position in the 'desired' and 'existing' inputs. + + for i, v := range desired { + if v != existing[i] { + out = append(out, v) + } + } + return out, nil +} + +func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { + cr, ok := mg.(*namespacedv1alpha1.User) + if !ok { + return managed.ExternalObservation{}, errors.New(errNotUser) + } + + username, host := mysql.SplitUserHost(meta.GetExternalName(cr)) + + observed := &namespacedv1alpha1.UserParameters{ + ResourceOptions: &namespacedv1alpha1.ResourceOptions{}, + } + + query := "SELECT " + + "max_questions, " + + "max_updates, " + + "max_connections, " + + "max_user_connections " + + "FROM mysql.user WHERE User = ? AND Host = ?" + err := c.db.Scan(ctx, + xsql.Query{ + String: query, + Parameters: []interface{}{ + username, + host, + }, + }, + &observed.ResourceOptions.MaxQueriesPerHour, + &observed.ResourceOptions.MaxUpdatesPerHour, + &observed.ResourceOptions.MaxConnectionsPerHour, + &observed.ResourceOptions.MaxUserConnections, + ) + if xsql.IsNoRows(err) { + return managed.ExternalObservation{ResourceExists: false}, nil + } + if err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, errSelectUser) + } + + _, pwdChanged, err := c.getPassword(ctx, cr) + if err != nil { + return managed.ExternalObservation{}, err + } + + cr.Status.AtProvider.ResourceOptionsAsClauses = resourceOptionsToClauses(observed.ResourceOptions) + + cr.SetConditions(xpv1.Available()) + + return managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: !pwdChanged && upToDate(observed, &cr.Spec.ForProvider), + }, nil +} + +func (c *external) Create(ctx context.Context, mg resource.Managed) (managed.ExternalCreation, error) { + cr, ok := mg.(*namespacedv1alpha1.User) + if !ok { + return managed.ExternalCreation{}, errors.New(errNotUser) + } + + cr.SetConditions(xpv1.Creating()) + + username, host := mysql.SplitUserHost(meta.GetExternalName(cr)) + pw, _, err := c.getPassword(ctx, cr) + if err != nil { + return managed.ExternalCreation{}, err + } + + if pw == "" { + pw, err = password.Generate() + if err != nil { + return managed.ExternalCreation{}, err + } + } + + ro := resourceOptionsToClauses(cr.Spec.ForProvider.ResourceOptions) + if err := c.executeCreateUserQuery(ctx, username, host, ro, pw); err != nil { + return managed.ExternalCreation{}, err + } + + if len(ro) != 0 { + cr.Status.AtProvider.ResourceOptionsAsClauses = ro + } + + return managed.ExternalCreation{ + ConnectionDetails: c.db.GetConnectionDetails(username, pw), + }, nil +} + +func (c *external) executeCreateUserQuery(ctx context.Context, username string, host string, resourceOptionsClauses []string, pw string) error { + resourceOptions := "" + if len(resourceOptionsClauses) != 0 { + resourceOptions = fmt.Sprintf(" WITH %s", strings.Join(resourceOptionsClauses, " ")) + } + + query := fmt.Sprintf( + "CREATE USER %s@%s IDENTIFIED BY %s%s", + mysql.QuoteValue(username), + mysql.QuoteValue(host), + mysql.QuoteValue(pw), + resourceOptions, + ) + + if err := mysql.ExecWrapper(ctx, c.db, mysql.ExecQuery{Query: query, ErrorValue: errCreateUser}); err != nil { + return err + } + + return nil +} + +func (c *external) Update(ctx context.Context, mg resource.Managed) (managed.ExternalUpdate, error) { + cr, ok := mg.(*namespacedv1alpha1.User) + if !ok { + return managed.ExternalUpdate{}, errors.New(errNotUser) + } + + username, host := mysql.SplitUserHost(meta.GetExternalName(cr)) + + ro := resourceOptionsToClauses(cr.Spec.ForProvider.ResourceOptions) + rochanged, err := changedResourceOptions(cr.Status.AtProvider.ResourceOptionsAsClauses, ro) + if err != nil { + return managed.ExternalUpdate{}, errors.Wrap(err, errUpdateUser) + } + + if len(rochanged) > 0 { + resourceOptions := fmt.Sprintf("WITH %s", strings.Join(ro, " ")) + + query := fmt.Sprintf( + "ALTER USER %s@%s %s", + mysql.QuoteValue(username), + mysql.QuoteValue(host), + resourceOptions, + ) + if err := mysql.ExecWrapper(ctx, c.db, mysql.ExecQuery{Query: query, ErrorValue: errUpdateUser}); err != nil { + return managed.ExternalUpdate{}, err + } + + cr.Status.AtProvider.ResourceOptionsAsClauses = ro + } + + connectionDetails, err := c.UpdatePassword(ctx, cr, username, host) + if err != nil { + return managed.ExternalUpdate{}, err + } + + if len(connectionDetails) > 0 { + return managed.ExternalUpdate{ConnectionDetails: connectionDetails}, nil + } + + return managed.ExternalUpdate{}, nil +} + +func (c *external) UpdatePassword(ctx context.Context, cr *namespacedv1alpha1.User, username, host string) (managed.ConnectionDetails, error) { + pw, pwchanged, err := c.getPassword(ctx, cr) + if err != nil { + return managed.ConnectionDetails{}, err + } + + if pwchanged { + query := fmt.Sprintf("ALTER USER %s@%s IDENTIFIED BY %s", mysql.QuoteValue(username), mysql.QuoteValue(host), mysql.QuoteValue(pw)) + if err := mysql.ExecWrapper(ctx, c.db, mysql.ExecQuery{Query: query, ErrorValue: errUpdateUser}); err != nil { + return managed.ConnectionDetails{}, err + } + + return c.db.GetConnectionDetails(username, pw), nil + } + + return managed.ConnectionDetails{}, nil +} + +func (c *external) Disconnect(ctx context.Context) error { + return nil +} + +func (c *external) Delete(ctx context.Context, mg resource.Managed) (managed.ExternalDelete, error) { + cr, ok := mg.(*namespacedv1alpha1.User) + if !ok { + return managed.ExternalDelete{}, errors.New(errNotUser) + } + + cr.SetConditions(xpv1.Deleting()) + + username, host := mysql.SplitUserHost(meta.GetExternalName(cr)) + + query := fmt.Sprintf("DROP USER IF EXISTS %s@%s", mysql.QuoteValue(username), mysql.QuoteValue(host)) + if err := mysql.ExecWrapper(ctx, c.db, mysql.ExecQuery{Query: query, ErrorValue: errDropUser}); err != nil { + return managed.ExternalDelete{}, err + } + + return managed.ExternalDelete{}, nil +} + +func upToDate(observed *namespacedv1alpha1.UserParameters, desired *namespacedv1alpha1.UserParameters) bool { + if desired.ResourceOptions == nil { + // Return true if there are no desired ResourceOptions + return true + } + if observed.ResourceOptions.MaxQueriesPerHour != desired.ResourceOptions.MaxQueriesPerHour { + return false + } + if observed.ResourceOptions.MaxUpdatesPerHour != desired.ResourceOptions.MaxUpdatesPerHour { + return false + } + if observed.ResourceOptions.MaxConnectionsPerHour != desired.ResourceOptions.MaxConnectionsPerHour { + return false + } + if observed.ResourceOptions.MaxUserConnections != desired.ResourceOptions.MaxUserConnections { + return false + } + return true +} diff --git a/pkg/controller/namespaced/mysql/user/reconciler_test.go b/pkg/controller/namespaced/mysql/user/reconciler_test.go new file mode 100644 index 00000000..71630b02 --- /dev/null +++ b/pkg/controller/namespaced/mysql/user/reconciler_test.go @@ -0,0 +1,934 @@ +/* +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 user + +import ( + "context" + "database/sql" + "strings" + "testing" + + "github.com/crossplane-contrib/provider-sql/apis/namespaced/mysql/v1alpha1" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/crossplane/crossplane-runtime/v2/apis/common" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpv2 "github.com/crossplane/crossplane-runtime/v2/apis/common/v2" + "github.com/crossplane/crossplane-runtime/v2/pkg/meta" + "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/crossplane-contrib/provider-sql/pkg/clients/xsql" + provErrors "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/errors" +) + +type mockDB struct { + MockExec func(ctx context.Context, q xsql.Query) error + MockExecTx func(ctx context.Context, ql []xsql.Query) error + MockScan func(ctx context.Context, q xsql.Query, dest ...interface{}) error +} + +func (m mockDB) Exec(ctx context.Context, q xsql.Query) error { + return m.MockExec(ctx, q) +} +func (m mockDB) ExecTx(ctx context.Context, ql []xsql.Query) error { + return m.MockExecTx(ctx, ql) +} +func (m mockDB) Scan(ctx context.Context, q xsql.Query, dest ...interface{}) error { + return m.MockScan(ctx, q, dest...) +} +func (m mockDB) Query(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return &sql.Rows{}, nil +} +func (m mockDB) GetConnectionDetails(username, password string) managed.ConnectionDetails { + return managed.ConnectionDetails{ + xpv1.ResourceCredentialsSecretUserKey: []byte(username), + xpv1.ResourceCredentialsSecretPasswordKey: []byte(password), + xpv1.ResourceCredentialsSecretEndpointKey: []byte("localhost"), + xpv1.ResourceCredentialsSecretPortKey: []byte("3306"), + } +} + +func TestConnect(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + kube client.Client + usage resource.Tracker + newDB func(creds map[string][]byte, tls *string, binlog *bool) xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + cases := map[string]struct { + reason string + fields fields + args args + want error + }{ + "ErrNotUser": { + reason: "An error should be returned if the managed resource is not a *User", + args: args{ + mg: nil, + }, + want: errors.New(errNotUser), + }, + "ErrTrackProviderConfigUsage": { + reason: "An error should be returned if we can't track our ProviderConfig usage", + fields: fields{ + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return errBoom }), + }, + args: args{ + mg: &v1alpha1.User{}, + }, + want: errors.Wrap(errBoom, errTrackPCUsage), + }, + "ErrInvalidProviderConfigKind": { + reason: "An error should be returned if the ProviderConfig kind is not valid", + fields: fields{ + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.User{ + Spec: v1alpha1.UserSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{Kind: "NotValid"}, + }, + }, + }, + }, + want: provErrors.InvalidProviderConfigKindError("NotValid"), + }, + "ErrGetProviderConfig": { + reason: "An error should be returned if we can't get our ProviderConfig", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(errBoom), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.UserSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{Kind: v1alpha1.ProviderConfigKind}, + }, + }, + }, + }, + want: provErrors.GetProviderConfigError(errBoom), + }, + "ErrGetClusterProviderConfig": { + reason: "An error should be returned if we can't get our ClusterProviderConfig", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(errBoom), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.UserSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{Kind: v1alpha1.ClusterProviderConfigKind}, + }, + }, + }, + }, + want: provErrors.GetClusterProviderConfigError(errBoom), + }, + "ErrMissingConnectionSecret": { + reason: "An error should be returned if our ProviderConfig doesn't specify a connection secret", + fields: fields{ + kube: &test.MockClient{ + // We call get to populate the User struct, then again + // to populate the (empty) ProviderConfig struct, resulting + // in a ProviderConfig with a nil connection secret. + MockGet: test.NewMockGetFn(nil), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.UserSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.MissingSecretRefError(), + }, + "ErrGetConnectionSecret": { + reason: "An error should be returned if we can't get our ProviderConfig's connection secret", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + switch o := obj.(type) { + case *v1alpha1.ProviderConfig: + o.Spec.Credentials.ConnectionSecretRef = common.LocalSecretReference{Name: "example"} + case *corev1.Secret: + return errBoom + } + return nil + }), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{Namespace: "default"}, + Spec: v1alpha1.UserSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetSecretError(errBoom), + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := &connector{kube: tc.fields.kube, usage: tc.fields.usage, newDB: tc.fields.newDB} + _, err := e.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") + + type fields struct { + db xsql.DB + kube client.Client + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + o managed.ExternalObservation + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotUser": { + reason: "An error should be returned if the managed resource is not a *User", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotUser), + }, + }, + "ErrNoUser": { + reason: "We should return ResourceExists: false when no user is found", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return sql.ErrNoRows }, + }, + }, + args: args{ + mg: &v1alpha1.User{ + Spec: v1alpha1.UserSpec{ + ForProvider: v1alpha1.UserParameters{}, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ResourceExists: false}, + }, + }, + "ErrSelectUser": { + reason: "We should return any errors encountered while trying to select the user", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return errBoom }, + }, + }, + args: args{ + mg: &v1alpha1.User{}, + }, + want: want{ + err: errors.Wrap(errBoom, errSelectUser), + }, + }, + "Success": { + reason: "We should return no error if we can successfully select our user", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.User{ + Spec: v1alpha1.UserSpec{ + ForProvider: v1alpha1.UserParameters{}, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + }, + err: nil, + }, + }, + "PasswordChanged": { + reason: "We should return ResourceUpToDate=false if the password changed", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return nil }, + }, + kube: &test.MockClient{ + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + secret := corev1.Secret{ + Data: map[string][]byte{}, + } + secret.Data[xpv1.ResourceCredentialsSecretPasswordKey] = []byte(key.Name) + secret.DeepCopyInto(obj.(*corev1.Secret)) + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.User{ + Spec: v1alpha1.UserSpec{ + ForProvider: v1alpha1.UserParameters{ + PasswordSecretRef: &common.LocalSecretKeySelector{ + LocalSecretReference: common.LocalSecretReference{ + Name: "example", + }, + Key: "password", + }, + }, + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + WriteConnectionSecretToReference: &common.LocalSecretReference{ + Name: "connection-secret", + }, + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: false, + }, + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{ + db: tc.fields.db, + kube: tc.fields.kube, + } + 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) + } + }) + } +} + +func TestCreate(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + db xsql.DB + kube client.Client + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + c managed.ExternalCreation + err error + } + + cases := map[string]struct { + reason string + comparePw bool + fields fields + args args + want want + }{ + "ErrNotUser": { + reason: "An error should be returned if the managed resource is not a *User", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotUser), + }, + }, + "ErrExec": { + reason: "Any errors encountered while creating the user should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + if strings.HasPrefix(q.String, "CREATE") { + return errBoom + } + + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.User{}, + }, + want: want{ + err: errors.Wrap(errBoom, errCreateUser), + }, + }, + "Success": { + reason: "No error should be returned when we successfully create a user", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: "example", + }, + }, + Spec: v1alpha1.UserSpec{ + ForProvider: v1alpha1.UserParameters{}, + }, + }, + }, + want: want{ + err: nil, + c: managed.ExternalCreation{ + ConnectionDetails: managed.ConnectionDetails{ + xpv1.ResourceCredentialsSecretUserKey: []byte("example"), + xpv1.ResourceCredentialsSecretPasswordKey: []byte(""), + xpv1.ResourceCredentialsSecretEndpointKey: []byte("localhost"), + xpv1.ResourceCredentialsSecretPortKey: []byte("3306"), + }, + }, + }, + }, + "UserWithHost": { + reason: "The username must be split if it contains a host", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: "example@127.0.0.1", + }, + }, + }, + }, + want: want{ + err: nil, + c: managed.ExternalCreation{ + ConnectionDetails: managed.ConnectionDetails{ + xpv1.ResourceCredentialsSecretUserKey: []byte("example"), + xpv1.ResourceCredentialsSecretPasswordKey: []byte(""), + xpv1.ResourceCredentialsSecretEndpointKey: []byte("localhost"), + xpv1.ResourceCredentialsSecretPortKey: []byte("3306"), + }, + }, + }, + }, + "UserWithPasswordRef": { + reason: "The password must be read from the secret", + comparePw: true, + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return nil }, + }, + kube: &test.MockClient{ + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + switch key.Name { + case "example": + secret := corev1.Secret{ + Data: map[string][]byte{}, + } + secret.Data["password-custom"] = []byte("test1234") + secret.DeepCopyInto(obj.(*corev1.Secret)) + return nil + default: + return nil + } + }, + }, + }, + args: args{ + mg: &v1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: "example", + }, + }, + Spec: v1alpha1.UserSpec{ + ForProvider: v1alpha1.UserParameters{ + PasswordSecretRef: &common.LocalSecretKeySelector{ + LocalSecretReference: common.LocalSecretReference{ + Name: "example", + }, + Key: "password-custom", + }, + }, + }, + }, + }, + want: want{ + err: nil, + c: managed.ExternalCreation{ + ConnectionDetails: managed.ConnectionDetails{ + xpv1.ResourceCredentialsSecretUserKey: []byte("example"), + xpv1.ResourceCredentialsSecretPasswordKey: []byte("test1234"), + xpv1.ResourceCredentialsSecretEndpointKey: []byte("localhost"), + xpv1.ResourceCredentialsSecretPortKey: []byte("3306"), + }, + }, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{ + db: tc.fields.db, + kube: tc.fields.kube, + } + 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) + } + var opts []cmp.Option + if !tc.comparePw { + opts = append(opts, cmpopts.IgnoreMapEntries(func(key string, _ []byte) bool { return key == "password" })) + } + if diff := cmp.Diff(tc.want.c, got, opts...); diff != "" { + t.Errorf("\n%s\ne.Create(...): -want, +got:\n%s\n", tc.reason, diff) + } + }) + } +} + +func TestUpdate(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + kube client.Client + } + + type want struct { + c managed.ExternalUpdate + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotUser": { + reason: "An error should be returned if the managed resource is not a *User", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotUser), + }, + }, + "ErrExec": { + reason: "Any errors encountered while updating the user should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + if strings.HasPrefix(q.String, "ALTER") { + return errBoom + } + + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.User{ + Spec: v1alpha1.UserSpec{ + ForProvider: v1alpha1.UserParameters{ + PasswordSecretRef: &common.LocalSecretKeySelector{ + LocalSecretReference: common.LocalSecretReference{ + Name: "connection-secret", + }, + Key: xpv1.ResourceCredentialsSecretPasswordKey, + }, + }, + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + WriteConnectionSecretToReference: &common.LocalSecretReference{ + Name: "password-secret", + }, + }, + }, + }, + kube: &test.MockClient{ + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + secret := corev1.Secret{ + Data: map[string][]byte{}, + } + secret.Data[xpv1.ResourceCredentialsSecretPasswordKey] = []byte(key.Name) + secret.DeepCopyInto(obj.(*corev1.Secret)) + return nil + }, + }, + }, + want: want{ + err: errors.Wrap(errBoom, errUpdateUser), + }, + }, + "Success": { + reason: "No error should be returned when we don't have to update a user", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: "example", + }, + }, + }, + }, + want: want{ + err: nil, + c: managed.ExternalUpdate{}, + }, + }, + "SamePassword": { + reason: "No DB query should be executed if the password didn't change", + fields: fields{ + db: &mockDB{}, + }, + args: args{ + mg: &v1alpha1.User{ + Spec: v1alpha1.UserSpec{ + ForProvider: v1alpha1.UserParameters{ + PasswordSecretRef: &common.LocalSecretKeySelector{ + LocalSecretReference: common.LocalSecretReference{ + Name: "connection-secret", + }, + Key: xpv1.ResourceCredentialsSecretPasswordKey, + }, + }, + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + WriteConnectionSecretToReference: &common.LocalSecretReference{ + Name: "connection-secret", + }, + }, + }, + }, + kube: &test.MockClient{ + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + secret := corev1.Secret{ + Data: map[string][]byte{}, + } + secret.Data[xpv1.ResourceCredentialsSecretPasswordKey] = []byte("samesame") + secret.DeepCopyInto(obj.(*corev1.Secret)) + return nil + }, + }, + }, + want: want{}, + }, + "UpdatePassword": { + reason: "The password must be updated", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: "example", + }, + }, + Spec: v1alpha1.UserSpec{ + ForProvider: v1alpha1.UserParameters{ + PasswordSecretRef: &common.LocalSecretKeySelector{ + LocalSecretReference: common.LocalSecretReference{ + Name: "example", + }, + Key: "password-custom", + }, + }, + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + WriteConnectionSecretToReference: &common.LocalSecretReference{ + Name: "connection-secret", + }, + }, + }, + }, + kube: &test.MockClient{ + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + switch key.Name { + case "example": + secret := corev1.Secret{ + Data: map[string][]byte{}, + } + secret.Data["password-custom"] = []byte("newpassword") + secret.DeepCopyInto(obj.(*corev1.Secret)) + return nil + case "connection-secret": + secret := corev1.Secret{ + Data: map[string][]byte{}, + } + secret.Data[xpv1.ResourceCredentialsSecretPasswordKey] = []byte("oldpassword") + secret.DeepCopyInto(obj.(*corev1.Secret)) + return nil + default: + return nil + } + }, + }, + }, + want: want{ + err: nil, + c: managed.ExternalUpdate{ + ConnectionDetails: managed.ConnectionDetails{ + xpv1.ResourceCredentialsSecretUserKey: []byte("example"), + xpv1.ResourceCredentialsSecretPasswordKey: []byte("newpassword"), + xpv1.ResourceCredentialsSecretEndpointKey: []byte("localhost"), + xpv1.ResourceCredentialsSecretPortKey: []byte("3306"), + }, + }, + }, + }, + "NoUpdateQueryUnchangedResourceOptions": { + reason: "We should not execute an SQL query if the resource options are unchanged.", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + if strings.HasPrefix(q.String, "ALTER") { + return errBoom + } + + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: "example", + }, + }, + Spec: v1alpha1.UserSpec{ + ForProvider: v1alpha1.UserParameters{ + PasswordSecretRef: &common.LocalSecretKeySelector{ + LocalSecretReference: common.LocalSecretReference{ + Name: "connection-secret", + }, + Key: xpv1.ResourceCredentialsSecretPasswordKey, + }, + ResourceOptions: &v1alpha1.ResourceOptions{ + MaxQueriesPerHour: new(int), + MaxUpdatesPerHour: new(int), + MaxConnectionsPerHour: new(int), + MaxUserConnections: new(int), + }, + }, + }, + Status: v1alpha1.UserStatus{ + AtProvider: v1alpha1.UserObservation{ + ResourceOptionsAsClauses: []string{ + "MAX_QUERIES_PER_HOUR 0", + "MAX_UPDATES_PER_HOUR 0", + "MAX_CONNECTIONS_PER_HOUR 0", + "MAX_USER_CONNECTIONS 0", + }, + }, + }, + }, + kube: &test.MockClient{ + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + secret := corev1.Secret{ + Data: map[string][]byte{}, + } + secret.Data[xpv1.ResourceCredentialsSecretPasswordKey] = []byte("samesame") + secret.DeepCopyInto(obj.(*corev1.Secret)) + return nil + }, + }, + }, + want: want{ + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{ + db: tc.fields.db, + kube: tc.args.kube, + } + got, err := e.Update(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, cmpopts.IgnoreMapEntries(func(key string, _ []byte) bool { return key == "password" })); diff != "" { + t.Errorf("\n%s\ne.Create(...): -want, +got:\n%s\n", tc.reason, diff) + } + }) + } +} + +func TestDelete(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + cases := map[string]struct { + reason string + fields fields + args args + want error + }{ + "ErrNotUser": { + reason: "An error should be returned if the managed resource is not a *User", + args: args{ + mg: nil, + }, + want: errors.New(errNotUser), + }, + "ErrDropUser": { + reason: "Errors dropping a user should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + if strings.HasPrefix(q.String, "DROP") { + return errBoom + } + + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.User{}, + }, + want: errors.Wrap(errBoom, errDropUser), + }, + "Success": { + reason: "No error should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.User{ + Spec: v1alpha1.UserSpec{ + ForProvider: v1alpha1.UserParameters{}, + }, + }, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + _, 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/pkg/controller/namespaced/mysql/user/utils.go b/pkg/controller/namespaced/mysql/user/utils.go new file mode 100644 index 00000000..2e7335c2 --- /dev/null +++ b/pkg/controller/namespaced/mysql/user/utils.go @@ -0,0 +1,66 @@ +/* +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 user + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + + "github.com/pkg/errors" + + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + "github.com/crossplane-contrib/provider-sql/apis/namespaced/mysql/v1alpha1" +) + +func (c *external) getPassword(ctx context.Context, user *v1alpha1.User) (newPwd string, changed bool, err error) { + if user.Spec.ForProvider.PasswordSecretRef == nil { + return "", false, nil + } + nn := types.NamespacedName{ + Name: user.Spec.ForProvider.PasswordSecretRef.Name, + Namespace: user.Namespace, + } + s := &corev1.Secret{} + if err := c.kube.Get(ctx, nn, s); err != nil { + return "", false, errors.Wrap(err, errGetPasswordSecretFailed) + } + newPwd = string(s.Data[user.Spec.ForProvider.PasswordSecretRef.Key]) + + if user.Spec.WriteConnectionSecretToReference == nil { + return newPwd, false, nil + } + + nn = types.NamespacedName{ + Name: user.Spec.WriteConnectionSecretToReference.Name, + Namespace: user.Namespace, + } + s = &corev1.Secret{} + // the output secret may not exist yet, so we can skip returning an + // error if the error is NotFound + if err := c.kube.Get(ctx, nn, s); resource.IgnoreNotFound(err) != nil { + return "", false, err + } + // if newPwd was set to some value, compare value in output secret with + // newPwd + changed = newPwd != "" && newPwd != string(s.Data[xpv1.ResourceCredentialsSecretPasswordKey]) + + return newPwd, changed, nil +} diff --git a/pkg/controller/namespaced/postgresql/config/config.go b/pkg/controller/namespaced/postgresql/config/config.go new file mode 100644 index 00000000..9986547e --- /dev/null +++ b/pkg/controller/namespaced/postgresql/config/config.go @@ -0,0 +1,48 @@ +/* +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 ( + 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/reconciler/providerconfig" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + "github.com/crossplane-contrib/provider-sql/apis/namespaced/postgresql/v1alpha1" +) + +// Setup adds a controller that reconciles ProviderConfigs by accounting for +// their current usage. +func Setup(mgr ctrl.Manager, o controller.Options) error { + name := providerconfig.ControllerName(v1alpha1.ProviderConfigGroupKind) + + of := resource.ProviderConfigKinds{ + Config: v1alpha1.ProviderConfigGroupVersionKind, + Usage: v1alpha1.ProviderConfigUsageGroupVersionKind, + UsageList: v1alpha1.ProviderConfigUsageListGroupVersionKind, + } + + return ctrl.NewControllerManagedBy(mgr). + Named(name). + For(&v1alpha1.ProviderConfig{}). + Watches(&v1alpha1.ProviderConfigUsage{}, &resource.EnqueueRequestForProviderConfig{}). + Complete(providerconfig.NewReconciler(mgr, of, + providerconfig.WithLogger(o.Logger.WithValues("controller", name)), + providerconfig.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))))) +} diff --git a/pkg/controller/namespaced/postgresql/database/reconciler.go b/pkg/controller/namespaced/postgresql/database/reconciler.go new file mode 100644 index 00000000..d13c10e2 --- /dev/null +++ b/pkg/controller/namespaced/postgresql/database/reconciler.go @@ -0,0 +1,362 @@ +/* +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 database + +import ( + "context" + "fmt" + "strings" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/lib/pq" + "github.com/pkg/errors" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/feature" + "github.com/crossplane/crossplane-runtime/v2/pkg/meta" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + namespacedv1alpha1 "github.com/crossplane-contrib/provider-sql/apis/namespaced/postgresql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/pkg/clients" + "github.com/crossplane-contrib/provider-sql/pkg/clients/postgresql" + "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/postgresql/provider" +) + +const ( + errTrackPCUsage = "cannot track ProviderConfig usage" + + errNotDatabase = "managed resource is not a Database custom resource" + errSelectDB = "cannot select database" + errCreateDB = "cannot create database" + errAlterDBOwner = "cannot alter database owner" + errAlterDBConnLimit = "cannot alter database connection limit" + errAlterDBAllowConns = "cannot alter database allow connections" + errAlterDBIsTmpl = "cannot alter database is template" + errDropDB = "cannot drop database" + + maxConcurrency = 5 +) + +// TODO(nateinaction): This looks wrong, can tracker creation be improved? +type tracker struct { + tracker *resource.ProviderConfigUsageTracker +} + +var _ resource.Tracker = &tracker{} + +func (t *tracker) Track(ctx context.Context, mg resource.Managed) error { + return t.tracker.Track(ctx, mg.(resource.ModernManaged)) +} + +// Setup adds a controller that reconciles Database managed resources. +func Setup(mgr ctrl.Manager, o xpcontroller.Options) error { + name := managed.ControllerName(namespacedv1alpha1.DatabaseGroupKind) + + t := resource.NewProviderConfigUsageTracker(mgr.GetClient(), &namespacedv1alpha1.ProviderConfigUsage{}) + trk := &tracker{tracker: t} + + reconcilerOptions := []managed.ReconcilerOption{ + managed.WithTypedExternalConnector(&connector{kube: mgr.GetClient(), usage: trk, newDB: postgresql.New}), + managed.WithLogger(o.Logger.WithValues("controller", name)), + managed.WithPollInterval(o.PollInterval), + managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), + } + if o.Features.Enabled(feature.EnableBetaManagementPolicies) { + reconcilerOptions = append(reconcilerOptions, managed.WithManagementPolicies()) + } + r := managed.NewReconciler(mgr, + resource.ManagedKind(namespacedv1alpha1.DatabaseGroupVersionKind), + reconcilerOptions..., + ) + return ctrl.NewControllerManagedBy(mgr). + Named(name). + For(&namespacedv1alpha1.Database{}). + WithOptions(controller.Options{ + MaxConcurrentReconciles: maxConcurrency, + }). + Complete(r) +} + +type connector struct { + kube client.Client + usage resource.Tracker + newDB func(creds map[string][]byte, database string, sslmode string) xsql.DB +} + +var _ managed.TypedExternalConnector[resource.Managed] = &connector{} + +func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.TypedExternalClient[resource.Managed], error) { + cr, ok := mg.(*namespacedv1alpha1.Database) + if !ok { + return nil, errors.New(errNotDatabase) + } + + if err := c.usage.Track(ctx, mg); err != nil { + return nil, errors.Wrap(err, errTrackPCUsage) + } + + // ProviderConfigReference could theoretically be nil, but in practice the + // DefaultProviderConfig initializer will set it before we get here. + providerInfo, err := provider.GetProviderConfig(ctx, c.kube, cr) + if err != nil { + return nil, err + } + + return &external{db: c.newDB(providerInfo.SecretData, providerInfo.DefaultDatabase, clients.ToString(providerInfo.SSLMode))}, nil +} + +type external struct{ db xsql.DB } + +var _ managed.TypedExternalClient[resource.Managed] = &external{} + +func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { + cr, ok := mg.(*namespacedv1alpha1.Database) + if !ok { + return managed.ExternalObservation{}, errors.New(errNotDatabase) + } + + // If the database exists, it will have all of these properties. + observed := namespacedv1alpha1.DatabaseParameters{ + Owner: new(string), + Encoding: new(string), + LCCollate: new(string), + LCCType: new(string), + AllowConnections: new(bool), + ConnectionLimit: new(int), + IsTemplate: new(bool), + Tablespace: new(string), + } + + query := "SELECT " + + "pg_catalog.pg_get_userbyid(db.datdba), " + + "pg_catalog.pg_encoding_to_char(db.encoding), " + + "db.datcollate, " + + "db.datctype, " + + "db.datallowconn, " + + "db.datconnlimit, " + + "db.datistemplate, " + + "ts.spcname " + + "FROM pg_database AS db, pg_tablespace AS ts " + + "WHERE db.datname=$1 AND db.dattablespace = ts.oid" + + err := c.db.Scan(ctx, xsql.Query{String: query, Parameters: []interface{}{meta.GetExternalName(cr)}}, + observed.Owner, + observed.Encoding, + observed.LCCollate, + observed.LCCType, + observed.AllowConnections, + observed.ConnectionLimit, + observed.IsTemplate, + observed.Tablespace, + ) + if xsql.IsNoRows(err) { + return managed.ExternalObservation{ResourceExists: false}, nil + } + if err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, errSelectDB) + } + + cr.SetConditions(xpv1.Available()) + + return managed.ExternalObservation{ + ResourceExists: true, + + // NOTE(negz): The ordering is important here. We want to late init any + // values that weren't supplied before we determine if an update is + // required. + ResourceLateInitialized: lateInit(observed, &cr.Spec.ForProvider), + ResourceUpToDate: upToDate(observed, cr.Spec.ForProvider), + }, nil +} + +func (c *external) Create(ctx context.Context, mg resource.Managed) (managed.ExternalCreation, error) { //nolint:gocyclo + // NOTE(negz): This is only a tiny bit over our cyclomatic complexity limit, + // and more readable than if we refactored it to avoid the linter error. + + cr, ok := mg.(*namespacedv1alpha1.Database) + if !ok { + return managed.ExternalCreation{}, errors.New(errNotDatabase) + } + + var b strings.Builder + b.WriteString("CREATE DATABASE ") + b.WriteString(pq.QuoteIdentifier(meta.GetExternalName(cr))) + + if cr.Spec.ForProvider.Owner != nil { + b.WriteString(" OWNER ") + b.WriteString(pq.QuoteIdentifier(*cr.Spec.ForProvider.Owner)) + } + if cr.Spec.ForProvider.Template != nil { + b.WriteString(" TEMPLATE ") + b.WriteString(quoteIfIdentifier(*cr.Spec.ForProvider.Template)) + } + if cr.Spec.ForProvider.Encoding != nil { + b.WriteString(" ENCODING ") + b.WriteString(quoteIfLiteral(*cr.Spec.ForProvider.Encoding)) + } + if cr.Spec.ForProvider.LCCollate != nil { + b.WriteString(" LC_COLLATE ") + b.WriteString(quoteIfLiteral(*cr.Spec.ForProvider.LCCollate)) + } + if cr.Spec.ForProvider.LCCType != nil { + b.WriteString(" LC_CTYPE ") + b.WriteString(quoteIfLiteral(*cr.Spec.ForProvider.LCCType)) + } + if cr.Spec.ForProvider.Tablespace != nil { + b.WriteString(" TABLESPACE ") + b.WriteString(quoteIfIdentifier(*cr.Spec.ForProvider.Tablespace)) + } + if cr.Spec.ForProvider.AllowConnections != nil { + b.WriteString(fmt.Sprintf(" ALLOW_CONNECTIONS %t", *cr.Spec.ForProvider.AllowConnections)) + } + if cr.Spec.ForProvider.ConnectionLimit != nil { + b.WriteString(fmt.Sprintf(" CONNECTION LIMIT %d", *cr.Spec.ForProvider.ConnectionLimit)) + } + if cr.Spec.ForProvider.IsTemplate != nil { + b.WriteString(fmt.Sprintf(" IS_TEMPLATE %t", *cr.Spec.ForProvider.IsTemplate)) + } + + return managed.ExternalCreation{}, errors.Wrap(c.db.Exec(ctx, xsql.Query{String: b.String()}), errCreateDB) +} + +func (c *external) Update(ctx context.Context, mg resource.Managed) (managed.ExternalUpdate, error) { //nolint:gocyclo + // NOTE(negz): This is only a tiny bit over our cyclomatic complexity limit, + // and more readable than if we refactored it to avoid the linter error. + + cr, ok := mg.(*namespacedv1alpha1.Database) + if !ok { + return managed.ExternalUpdate{}, errors.New(errNotDatabase) + } + + if cr.Spec.ForProvider.Owner != nil { + query := xsql.Query{String: fmt.Sprintf("ALTER DATABASE %s OWNER TO %s", + pq.QuoteIdentifier(meta.GetExternalName(cr)), + pq.QuoteIdentifier(*cr.Spec.ForProvider.Owner))} + if err := c.db.Exec(ctx, query); err != nil { + return managed.ExternalUpdate{}, errors.Wrap(err, errAlterDBOwner) + } + } + + if cr.Spec.ForProvider.ConnectionLimit != nil { + query := xsql.Query{String: fmt.Sprintf("ALTER DATABASE %s CONNECTION LIMIT = %d", + pq.QuoteIdentifier(meta.GetExternalName(cr)), + *cr.Spec.ForProvider.ConnectionLimit)} + if err := c.db.Exec(ctx, query); err != nil { + return managed.ExternalUpdate{}, errors.Wrap(err, errAlterDBConnLimit) + } + } + + if cr.Spec.ForProvider.AllowConnections != nil { + query := xsql.Query{String: fmt.Sprintf("ALTER DATABASE %s ALLOW_CONNECTIONS %t", + pq.QuoteIdentifier(meta.GetExternalName(cr)), + *cr.Spec.ForProvider.AllowConnections)} + if err := c.db.Exec(ctx, query); err != nil { + return managed.ExternalUpdate{}, errors.Wrap(err, errAlterDBAllowConns) + } + } + + if cr.Spec.ForProvider.IsTemplate != nil { + query := xsql.Query{String: fmt.Sprintf("ALTER DATABASE %s IS_TEMPLATE %t", + pq.QuoteIdentifier(meta.GetExternalName(cr)), + *cr.Spec.ForProvider.IsTemplate)} + if err := c.db.Exec(ctx, query); err != nil { + return managed.ExternalUpdate{}, errors.Wrap(err, errAlterDBIsTmpl) + } + } + + return managed.ExternalUpdate{}, nil +} + +func (c *external) Disconnect(ctx context.Context) error { + return nil +} + +func (c *external) Delete(ctx context.Context, mg resource.Managed) (managed.ExternalDelete, error) { + cr, ok := mg.(*namespacedv1alpha1.Database) + if !ok { + return managed.ExternalDelete{}, errors.New(errNotDatabase) + } + + err := c.db.Exec(ctx, xsql.Query{String: "DROP DATABASE IF EXISTS " + pq.QuoteIdentifier(meta.GetExternalName(cr))}) + return managed.ExternalDelete{}, errors.Wrap(err, errDropDB) +} + +func upToDate(observed, desired namespacedv1alpha1.DatabaseParameters) bool { + // Template is only used at create time. + return cmp.Equal(desired, observed, cmpopts.IgnoreFields(namespacedv1alpha1.DatabaseParameters{}, "Template")) +} + +func lateInit(observed namespacedv1alpha1.DatabaseParameters, desired *namespacedv1alpha1.DatabaseParameters) bool { + li := false + + if desired.Owner == nil { + desired.Owner = observed.Owner + li = true + } + if desired.Encoding == nil { + desired.Encoding = observed.Encoding + li = true + } + if desired.LCCollate == nil { + desired.LCCollate = observed.LCCollate + li = true + } + if desired.LCCType == nil { + desired.LCCType = observed.LCCType + li = true + } + if desired.AllowConnections == nil { + desired.AllowConnections = observed.AllowConnections + li = true + } + if desired.ConnectionLimit == nil { + desired.ConnectionLimit = observed.ConnectionLimit + li = true + } + if desired.IsTemplate == nil { + desired.IsTemplate = observed.IsTemplate + li = true + } + if desired.Tablespace == nil { + desired.Tablespace = observed.Tablespace + li = true + } + + return li +} + +func quoteIfIdentifier(name string) string { + if name == "DEFAULT" { + return name + } + return pq.QuoteIdentifier(name) +} + +func quoteIfLiteral(literal string) string { + if literal == "DEFAULT" { + return literal + } + return pq.QuoteLiteral(literal) +} diff --git a/pkg/controller/namespaced/postgresql/database/reconciler_test.go b/pkg/controller/namespaced/postgresql/database/reconciler_test.go new file mode 100644 index 00000000..5115e64d --- /dev/null +++ b/pkg/controller/namespaced/postgresql/database/reconciler_test.go @@ -0,0 +1,632 @@ +/* +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 database + +import ( + "context" + "database/sql" + "testing" + + "github.com/crossplane-contrib/provider-sql/apis/namespaced/postgresql/v1alpha1" + "github.com/google/go-cmp/cmp" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/crossplane/crossplane-runtime/v2/apis/common" + xpv2 "github.com/crossplane/crossplane-runtime/v2/apis/common/v2" + "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/crossplane-contrib/provider-sql/pkg/clients/xsql" + provErrors "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/errors" +) + +type mockDB struct { + MockExec func(ctx context.Context, q xsql.Query) error + MockExecTx func(ctx context.Context, ql []xsql.Query) error + MockScan func(ctx context.Context, q xsql.Query, dest ...interface{}) error + MockGetConnectionDetails func(username, password string) managed.ConnectionDetails +} + +func (m mockDB) Exec(ctx context.Context, q xsql.Query) error { + return m.MockExec(ctx, q) +} +func (m mockDB) ExecTx(ctx context.Context, ql []xsql.Query) error { + return m.MockExecTx(ctx, ql) +} +func (m mockDB) Scan(ctx context.Context, q xsql.Query, dest ...interface{}) error { + return m.MockScan(ctx, q, dest...) +} +func (m mockDB) Query(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return &sql.Rows{}, nil +} +func (m mockDB) GetConnectionDetails(username, password string) managed.ConnectionDetails { + return m.MockGetConnectionDetails(username, password) +} + +func TestConnect(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + kube client.Client + usage resource.Tracker + newDB func(creds map[string][]byte, database string, sslmode string) xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + cases := map[string]struct { + reason string + fields fields + args args + want error + }{ + "ErrNotDatabase": { + reason: "An error should be returned if the managed resource is not a *Database", + args: args{ + mg: nil, + }, + want: errors.New(errNotDatabase), + }, + "ErrTrackProviderConfigUsage": { + reason: "An error should be returned if we can't track our ProviderConfig usage", + fields: fields{ + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return errBoom }), + }, + args: args{ + mg: &v1alpha1.Database{}, + }, + want: errors.Wrap(errBoom, errTrackPCUsage), + }, + "InvalideProviderConfigKind": { + reason: "An error should be returned if our ProviderConfig kind is invalid", + fields: fields{ + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Database{ + Spec: v1alpha1.DatabaseSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{Kind: "Invalid"}, + }, + }, + }, + }, + want: provErrors.InvalidProviderConfigKindError("Invalid"), + }, + "ErrGetProviderConfig": { + reason: "An error should be returned if we can't get our ProviderConfig", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(errBoom), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Database{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.DatabaseSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetProviderConfigError(errBoom), + }, + "ErrGetClusterProviderConfig": { + reason: "An error should be returned if we can't get our ClusterProviderConfig", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(errBoom), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Database{ + Spec: v1alpha1.DatabaseSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ClusterProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetClusterProviderConfigError(errBoom), + }, + "ErrMissingConnectionSecret": { + reason: "An error should be returned if our ProviderConfig doesn't specify a connection secret", + fields: fields{ + kube: &test.MockClient{ + // We call get to populate the Role struct, then again + // to populate the (empty) ProviderConfig struct, resulting + // in a ProviderConfig with a nil connection secret. + MockGet: test.NewMockGetFn(nil), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Database{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.DatabaseSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.MissingSecretRefError(), + }, + "ErrGetConnectionSecret": { + reason: "An error should be returned if we can't get our ProviderConfig's connection secret", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + switch o := obj.(type) { + case *v1alpha1.ProviderConfig: + o.Spec.Credentials.ConnectionSecretRef = common.LocalSecretReference{Name: "example"} + case *corev1.Secret: + return errBoom + } + return nil + }), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Database{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.DatabaseSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetSecretError(errBoom), + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := &connector{kube: tc.fields.kube, usage: tc.fields.usage, newDB: tc.fields.newDB} + _, err := e.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") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + o managed.ExternalObservation + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotDatabase": { + reason: "An error should be returned if the managed resource is not a *Database", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotDatabase), + }, + }, + "ErrNoDatabase": { + reason: "We should return ResourceExists: false when no database is found", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return sql.ErrNoRows }, + }, + }, + args: args{ + mg: &v1alpha1.Database{}, + }, + want: want{ + o: managed.ExternalObservation{ResourceExists: false}, + }, + }, + "ErrSelectDatabase": { + reason: "We should return any errors encountered while trying to select the database", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return errBoom }, + }, + }, + args: args{ + mg: &v1alpha1.Database{}, + }, + want: want{ + err: errors.Wrap(errBoom, errSelectDB), + }, + }, + "Success": { + reason: "We should return no error if we can successfully select our database", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.Database{}, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + ResourceLateInitialized: true, + }, + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + 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) + } + }) + } +} + +func TestCreate(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + c managed.ExternalCreation + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotDatabase": { + reason: "An error should be returned if the managed resource is not a *Database", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotDatabase), + }, + }, + "ErrExec": { + reason: "Any errors encountered while creating the database should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return errBoom }, + }, + }, + args: args{ + mg: &v1alpha1.Database{}, + }, + want: want{ + err: errors.Wrap(errBoom, errCreateDB), + }, + }, + "Success": { + reason: "No error should be returned when we successfully create a database", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.Database{ + Spec: v1alpha1.DatabaseSpec{ + ForProvider: v1alpha1.DatabaseParameters{ + Owner: new(string), + Template: new(string), + Encoding: new(string), + LCCollate: new(string), + LCCType: new(string), + Tablespace: new(string), + AllowConnections: new(bool), + ConnectionLimit: new(int), + IsTemplate: new(bool), + }, + }, + }, + }, + want: want{ + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + 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) + } + }) + } +} + +func TestUpdate(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + u managed.ExternalUpdate + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotDatabase": { + reason: "An error should be returned if the managed resource is not a *Database", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotDatabase), + }, + }, + "ErrAlterOwner": { + reason: "Errors altering the owner of a database should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + return errBoom + }, + }, + }, + args: args{ + mg: &v1alpha1.Database{ + Spec: v1alpha1.DatabaseSpec{ + ForProvider: v1alpha1.DatabaseParameters{ + Owner: new(string), + }, + }, + }, + }, + want: want{ + err: errors.Wrap(errBoom, errAlterDBOwner), + }, + }, + "ErrAlterConnLimit": { + reason: "Errors altering the connection limit of a database should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + return errBoom + }, + }, + }, + args: args{ + mg: &v1alpha1.Database{ + Spec: v1alpha1.DatabaseSpec{ + ForProvider: v1alpha1.DatabaseParameters{ + ConnectionLimit: new(int), + }, + }, + }, + }, + want: want{ + err: errors.Wrap(errBoom, errAlterDBConnLimit), + }, + }, + "ErrAlterAllowConnections": { + reason: "Errors altering whether a database allows connections should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + return errBoom + }, + }, + }, + args: args{ + mg: &v1alpha1.Database{ + Spec: v1alpha1.DatabaseSpec{ + ForProvider: v1alpha1.DatabaseParameters{ + AllowConnections: new(bool), + }, + }, + }, + }, + want: want{ + err: errors.Wrap(errBoom, errAlterDBAllowConns), + }, + }, + "ErrAlterIsTemplate": { + reason: "Errors altering whether a database is a template should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + return errBoom + }, + }, + }, + args: args{ + mg: &v1alpha1.Database{ + Spec: v1alpha1.DatabaseSpec{ + ForProvider: v1alpha1.DatabaseParameters{ + IsTemplate: new(bool), + }, + }, + }, + }, + want: want{ + err: errors.Wrap(errBoom, errAlterDBIsTmpl), + }, + }, + "Success": { + reason: "No error should be returned when we successfully update a database", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.Database{ + Spec: v1alpha1.DatabaseSpec{ + ForProvider: v1alpha1.DatabaseParameters{ + Owner: new(string), + AllowConnections: new(bool), + ConnectionLimit: new(int), + IsTemplate: new(bool), + }, + }, + }, + }, + want: want{ + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + got, err := e.Update(tc.args.ctx, tc.args.mg) + if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { + t.Errorf("\n%s\ne.Update(...): -want error, +got error:\n%s\n", tc.reason, diff) + } + if diff := cmp.Diff(tc.want.u, got); diff != "" { + t.Errorf("\n%s\ne.Update(...): -want, +got:\n%s\n", tc.reason, diff) + } + }) + } +} + +func TestDelete(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + cases := map[string]struct { + reason string + fields fields + args args + want error + }{ + "ErrNotDatabase": { + reason: "An error should be returned if the managed resource is not a *Database", + args: args{ + mg: nil, + }, + want: errors.New(errNotDatabase), + }, + "ErrDropDB": { + reason: "Errors dropping a database should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + return errBoom + }, + }, + }, + args: args{ + mg: &v1alpha1.Database{}, + }, + want: errors.Wrap(errBoom, errDropDB), + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + _, 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/pkg/controller/namespaced/postgresql/extension/reconciler.go b/pkg/controller/namespaced/postgresql/extension/reconciler.go new file mode 100644 index 00000000..109afd53 --- /dev/null +++ b/pkg/controller/namespaced/postgresql/extension/reconciler.go @@ -0,0 +1,230 @@ +/* +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 extension + +import ( + "context" + "strings" + + "github.com/lib/pq" + "github.com/pkg/errors" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/feature" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + namespacedv1alpha1 "github.com/crossplane-contrib/provider-sql/apis/namespaced/postgresql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/pkg/clients" + "github.com/crossplane-contrib/provider-sql/pkg/clients/postgresql" + "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/postgresql/provider" +) + +const ( + errTrackPCUsage = "cannot track ProviderConfig usage" + + errNotExtension = "managed resource is not a Extension custom resource" + errSelectExtension = "cannot select extension" + errCreateExtension = "cannot create extension" + errDropExtension = "cannot drop extension" + + maxConcurrency = 5 +) + +// TODO(nateinaction): This looks wrong, can tracker creation be improved? +type tracker struct { + tracker *resource.ProviderConfigUsageTracker +} + +var _ resource.Tracker = &tracker{} + +func (t *tracker) Track(ctx context.Context, mg resource.Managed) error { + return t.tracker.Track(ctx, mg.(resource.ModernManaged)) +} + +// Setup adds a controller that reconciles Database managed resources. +func Setup(mgr ctrl.Manager, o xpcontroller.Options) error { + name := managed.ControllerName(namespacedv1alpha1.ExtensionGroupKind) + + t := resource.NewProviderConfigUsageTracker(mgr.GetClient(), &namespacedv1alpha1.ProviderConfigUsage{}) + trk := &tracker{tracker: t} + + reconcilerOptions := []managed.ReconcilerOption{ + managed.WithTypedExternalConnector(&connector{kube: mgr.GetClient(), usage: trk, newDB: postgresql.New}), + managed.WithLogger(o.Logger.WithValues("controller", name)), + managed.WithPollInterval(o.PollInterval), + managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), + } + if o.Features.Enabled(feature.EnableBetaManagementPolicies) { + reconcilerOptions = append(reconcilerOptions, managed.WithManagementPolicies()) + } + r := managed.NewReconciler(mgr, + resource.ManagedKind(namespacedv1alpha1.ExtensionGroupVersionKind), + reconcilerOptions..., + ) + return ctrl.NewControllerManagedBy(mgr). + Named(name). + For(&namespacedv1alpha1.Extension{}). + WithOptions(controller.Options{ + MaxConcurrentReconciles: maxConcurrency, + }). + Complete(r) +} + +type connector struct { + kube client.Client + usage resource.Tracker + newDB func(creds map[string][]byte, database string, sslmode string) xsql.DB +} + +var _ managed.TypedExternalConnector[resource.Managed] = &connector{} + +func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.TypedExternalClient[resource.Managed], error) { + cr, ok := mg.(*namespacedv1alpha1.Extension) + if !ok { + return nil, errors.New(errNotExtension) + } + + if err := c.usage.Track(ctx, mg); err != nil { + return nil, errors.Wrap(err, errTrackPCUsage) + } + + // ProviderConfigReference could theoretically be nil, but in practice the + // DefaultProviderConfig initializer will set it before we get here. + providerInfo, err := provider.GetProviderConfig(ctx, c.kube, cr) + if err != nil { + return nil, err + } + + // We do not want to create an extension on the default DB + // if the user was expecting a database name to be resolved. + if cr.Spec.ForProvider.Database != nil { + return &external{db: c.newDB(providerInfo.SecretData, *cr.Spec.ForProvider.Database, clients.ToString(providerInfo.SSLMode))}, nil + } + + return &external{db: c.newDB(providerInfo.SecretData, providerInfo.DefaultDatabase, clients.ToString(providerInfo.SSLMode))}, nil +} + +type external struct{ db xsql.DB } + +var _ managed.TypedExternalClient[resource.Managed] = &external{} + +func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { + cr, ok := mg.(*namespacedv1alpha1.Extension) + if !ok { + return managed.ExternalObservation{}, errors.New(errNotExtension) + } + + // If the Extension exists, it will have all of these properties. + observed := namespacedv1alpha1.ExtensionParameters{ + Version: new(string), + } + + query := "SELECT " + + "extversion " + + "FROM pg_extension " + + "WHERE extname = $1" + + err := c.db.Scan(ctx, xsql.Query{ + String: query, + Parameters: []interface{}{cr.Spec.ForProvider.Extension}, + }, + observed.Version, + ) + + // If the database we try to connect on does not exist then + // there cannot be an extension on that database either. + if xsql.IsNoRows(err) || postgresql.IsInvalidCatalog(err) { + return managed.ExternalObservation{ResourceExists: false}, nil + } + if err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, errSelectExtension) + } + + cr.SetConditions(xpv1.Available()) + + return managed.ExternalObservation{ + ResourceExists: true, + ResourceLateInitialized: lateInit(observed, &cr.Spec.ForProvider), + ResourceUpToDate: upToDate(observed, cr.Spec.ForProvider), + }, nil +} + +func (c *external) Create(ctx context.Context, mg resource.Managed) (managed.ExternalCreation, error) { //nolint:gocyclo + cr, ok := mg.(*namespacedv1alpha1.Extension) + if !ok { + return managed.ExternalCreation{}, errors.New(errNotExtension) + } + + var b strings.Builder + b.WriteString("CREATE EXTENSION IF NOT EXISTS ") + b.WriteString(pq.QuoteIdentifier(cr.Spec.ForProvider.Extension)) + + if cr.Spec.ForProvider.Version != nil { + b.WriteString(" WITH VERSION ") + b.WriteString(pq.QuoteIdentifier(*cr.Spec.ForProvider.Version)) + } + + return managed.ExternalCreation{}, errors.Wrap(c.db.Exec(ctx, xsql.Query{String: b.String()}), errCreateExtension) +} + +func (c *external) Update(_ context.Context, mg resource.Managed) (managed.ExternalUpdate, error) { //nolint:gocyclo + _, ok := mg.(*namespacedv1alpha1.Extension) + if !ok { + return managed.ExternalUpdate{}, errors.New(errNotExtension) + } + + return managed.ExternalUpdate{}, nil +} + +func (c *external) Disconnect(ctx context.Context) error { + return nil +} + +func (c *external) Delete(ctx context.Context, mg resource.Managed) (managed.ExternalDelete, error) { + cr, ok := mg.(*namespacedv1alpha1.Extension) + if !ok { + return managed.ExternalDelete{}, errors.New(errNotExtension) + } + + err := c.db.Exec(ctx, xsql.Query{String: "DROP EXTENSION IF EXISTS " + pq.QuoteIdentifier(cr.Spec.ForProvider.Extension)}) + return managed.ExternalDelete{}, errors.Wrap(err, errDropExtension) +} + +func upToDate(observed, desired namespacedv1alpha1.ExtensionParameters) bool { + if desired.Version == nil || (observed.Version != nil && *desired.Version == *observed.Version) { + return true + } + return false +} + +func lateInit(observed namespacedv1alpha1.ExtensionParameters, desired *namespacedv1alpha1.ExtensionParameters) bool { + li := false + + if desired.Version == nil && observed.Version != nil { + desired.Version = observed.Version + li = true + } + + return li +} diff --git a/pkg/controller/namespaced/postgresql/extension/reconciler_test.go b/pkg/controller/namespaced/postgresql/extension/reconciler_test.go new file mode 100644 index 00000000..61f4cfea --- /dev/null +++ b/pkg/controller/namespaced/postgresql/extension/reconciler_test.go @@ -0,0 +1,569 @@ +/* +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 extension + +import ( + "context" + "database/sql" + "testing" + + "github.com/crossplane-contrib/provider-sql/apis/namespaced/postgresql/v1alpha1" + "github.com/google/go-cmp/cmp" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/crossplane/crossplane-runtime/v2/apis/common" + xpv2 "github.com/crossplane/crossplane-runtime/v2/apis/common/v2" + "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/crossplane-contrib/provider-sql/pkg/clients/xsql" + provErrors "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/errors" +) + +type mockDB struct { + MockExec func(ctx context.Context, q xsql.Query) error + MockExecTx func(ctx context.Context, ql []xsql.Query) error + MockScan func(ctx context.Context, q xsql.Query, dest ...interface{}) error + MockGetConnectionDetails func(username, password string) managed.ConnectionDetails +} + +func (m mockDB) Exec(ctx context.Context, q xsql.Query) error { + return m.MockExec(ctx, q) +} +func (m mockDB) ExecTx(ctx context.Context, ql []xsql.Query) error { + return m.MockExecTx(ctx, ql) +} +func (m mockDB) Scan(ctx context.Context, q xsql.Query, dest ...interface{}) error { + return m.MockScan(ctx, q, dest...) +} +func (m mockDB) Query(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return &sql.Rows{}, nil +} +func (m mockDB) GetConnectionDetails(username, password string) managed.ConnectionDetails { + return m.MockGetConnectionDetails(username, password) +} + +func TestConnect(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + kube client.Client + usage resource.Tracker + newDB func(creds map[string][]byte, database string, sslmode string) xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + cases := map[string]struct { + reason string + fields fields + args args + want error + }{ + "ErrNotExtension": { + reason: "An error should be returned if the managed resource is not a Extension", + args: args{ + mg: nil, + }, + want: errors.New(errNotExtension), + }, + "ErrTrackProviderConfigUsage": { + reason: "An error should be returned if we can't track our ProviderConfig usage", + fields: fields{ + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return errBoom }), + }, + args: args{ + mg: &v1alpha1.Extension{}, + }, + want: errors.Wrap(errBoom, errTrackPCUsage), + }, + "InvalideProviderConfigKind": { + reason: "An error should be returned if our ProviderConfig kind is invalid", + fields: fields{ + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Extension{ + Spec: v1alpha1.ExtensionSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{Kind: "Invalid"}, + }, + }, + }, + }, + want: provErrors.InvalidProviderConfigKindError("Invalid"), + }, + "ErrGetProviderConfig": { + reason: "An error should be returned if we can't get our ProviderConfig", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(errBoom), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Extension{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.ExtensionSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetProviderConfigError(errBoom), + }, + "ErrGetClusterProviderConfig": { + reason: "An error should be returned if we can't get our ClusterProviderConfig", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(errBoom), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Extension{ + Spec: v1alpha1.ExtensionSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ClusterProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetClusterProviderConfigError(errBoom), + }, + "ErrMissingConnectionSecret": { + reason: "An error should be returned if our ProviderConfig doesn't specify a connection secret", + fields: fields{ + kube: &test.MockClient{ + // We call get to populate the Role struct, then again + // to populate the (empty) ProviderConfig struct, resulting + // in a ProviderConfig with a nil connection secret. + MockGet: test.NewMockGetFn(nil), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Extension{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.ExtensionSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.MissingSecretRefError(), + }, + "ErrGetConnectionSecret": { + reason: "An error should be returned if we can't get our ProviderConfig's connection secret", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + switch o := obj.(type) { + case *v1alpha1.ProviderConfig: + o.Spec.Credentials.ConnectionSecretRef = common.LocalSecretReference{Name: "example"} + case *corev1.Secret: + return errBoom + } + return nil + }), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Extension{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.ExtensionSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetSecretError(errBoom), + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := &connector{kube: tc.fields.kube, usage: tc.fields.usage, newDB: tc.fields.newDB} + _, err := e.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") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + o managed.ExternalObservation + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotExtension": { + reason: "An error should be returned if the managed resource is not a Extension", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotExtension), + }, + }, + "ErrNoExtension": { + reason: "We should return ResourceExists: false when no extension is found", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return sql.ErrNoRows }, + }, + }, + args: args{ + mg: &v1alpha1.Extension{}, + }, + want: want{ + o: managed.ExternalObservation{ResourceExists: false}, + }, + }, + "ErrSelectExtension": { + reason: "We should return any errors encountered while trying to select the extension", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return errBoom }, + }, + }, + args: args{ + mg: &v1alpha1.Extension{ + Spec: v1alpha1.ExtensionSpec{ + ForProvider: v1alpha1.ExtensionParameters{ + Version: new(string), + }, + }, + }, + }, + want: want{ + err: errors.Wrap(errBoom, errSelectExtension), + }, + }, + "Success": { + reason: "We should return no error if we can successfully select our extension", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.Extension{ + Spec: v1alpha1.ExtensionSpec{ + ForProvider: v1alpha1.ExtensionParameters{ + Version: new(string), + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + ResourceLateInitialized: false, + }, + err: nil, + }, + }, + "SuccessLateInit": { + reason: "No error should be returned via lateInit when version is provided", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { + bv := dest[0].(*string) + *bv = "blah" + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Extension{ + Spec: v1alpha1.ExtensionSpec{ + ForProvider: v1alpha1.ExtensionParameters{}, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + ResourceLateInitialized: true, + }, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + 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) + } + }) + } +} + +func TestCreate(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + c managed.ExternalCreation + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotExtension": { + reason: "An error should be returned if the managed resource is not a Extension", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotExtension), + }, + }, + "ErrExec": { + reason: "Any errors encountered while creating the extension should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return errBoom }, + }, + }, + args: args{ + mg: &v1alpha1.Extension{}, + }, + want: want{ + err: errors.Wrap(errBoom, errCreateExtension), + }, + }, + "Success": { + reason: "No error should be returned when we successfully create a extension", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.Extension{ + Spec: v1alpha1.ExtensionSpec{ + ForProvider: v1alpha1.ExtensionParameters{ + Version: new(string), + }, + }, + }, + }, + want: want{ + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + 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) + } + }) + } +} + +func TestUpdate(t *testing.T) { + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + u managed.ExternalUpdate + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotExtension": { + reason: "An error should be returned if the managed resource is not a Extension", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotExtension), + }, + }, + "Success": { + reason: "No error should be returned when we successfully update a extension", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.Extension{ + Spec: v1alpha1.ExtensionSpec{ + ForProvider: v1alpha1.ExtensionParameters{ + Version: new(string), + }, + }, + }, + }, + want: want{ + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + got, err := e.Update(tc.args.ctx, tc.args.mg) + if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { + t.Errorf("\n%s\ne.Update(...): -want error, +got error:\n%s\n", tc.reason, diff) + } + if diff := cmp.Diff(tc.want.u, got); diff != "" { + t.Errorf("\n%s\ne.Update(...): -want, +got:\n%s\n", tc.reason, diff) + } + }) + } +} + +func TestDelete(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + cases := map[string]struct { + reason string + fields fields + args args + want error + }{ + "ErrNotExtension": { + reason: "An error should be returned if the managed resource is not a Extension", + args: args{ + mg: nil, + }, + want: errors.New(errNotExtension), + }, + "ErrDropExtension": { + reason: "Errors dropping a extension should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + return errBoom + }, + }, + }, + args: args{ + mg: &v1alpha1.Extension{}, + }, + want: errors.Wrap(errBoom, errDropExtension), + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + _, 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/pkg/controller/namespaced/postgresql/grant/reconciler.go b/pkg/controller/namespaced/postgresql/grant/reconciler.go new file mode 100644 index 00000000..39b0bcb4 --- /dev/null +++ b/pkg/controller/namespaced/postgresql/grant/reconciler.go @@ -0,0 +1,405 @@ +/* +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 grant + +import ( + "context" + "fmt" + "strings" + + "github.com/lib/pq" + "github.com/pkg/errors" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/feature" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + namespacedv1alpha1 "github.com/crossplane-contrib/provider-sql/apis/namespaced/postgresql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/pkg/clients" + "github.com/crossplane-contrib/provider-sql/pkg/clients/postgresql" + "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/postgresql/provider" +) + +const ( + errTrackPCUsage = "cannot track ProviderConfig usage" + + errNotGrant = "managed resource is not a Grant custom resource" + errSelectGrant = "cannot select grant" + errCreateGrant = "cannot create grant" + errRevokeGrant = "cannot revoke grant" + errNoRole = "role not passed or could not be resolved" + errNoDatabase = "database not passed or could not be resolved" + errNoPrivileges = "privileges not passed" + errUnknownGrant = "cannot identify grant type based on passed params" + + errInvalidParams = "invalid parameters for grant type %s" + + errMemberOfWithDatabaseOrPrivileges = "cannot set privileges or database in the same grant as memberOf" + + maxConcurrency = 5 +) + +// TODO(nateinaction): This looks wrong, can tracker creation be improved? +type tracker struct { + tracker *resource.ProviderConfigUsageTracker +} + +var _ resource.Tracker = &tracker{} + +func (t *tracker) Track(ctx context.Context, mg resource.Managed) error { + return t.tracker.Track(ctx, mg.(resource.ModernManaged)) +} + +// Setup adds a controller that reconciles Database managed resources. +func Setup(mgr ctrl.Manager, o xpcontroller.Options) error { + name := managed.ControllerName(namespacedv1alpha1.GrantGroupKind) + + t := resource.NewProviderConfigUsageTracker(mgr.GetClient(), &namespacedv1alpha1.ProviderConfigUsage{}) + trk := &tracker{tracker: t} + + reconcilerOptions := []managed.ReconcilerOption{ + managed.WithTypedExternalConnector(&connector{kube: mgr.GetClient(), usage: trk, newDB: postgresql.New}), + managed.WithLogger(o.Logger.WithValues("controller", name)), + managed.WithPollInterval(o.PollInterval), + managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), + } + if o.Features.Enabled(feature.EnableBetaManagementPolicies) { + reconcilerOptions = append(reconcilerOptions, managed.WithManagementPolicies()) + } + r := managed.NewReconciler(mgr, + resource.ManagedKind(namespacedv1alpha1.GrantGroupVersionKind), + reconcilerOptions..., + ) + return ctrl.NewControllerManagedBy(mgr). + Named(name). + For(&namespacedv1alpha1.Grant{}). + WithOptions(controller.Options{ + MaxConcurrentReconciles: maxConcurrency, + }). + Complete(r) +} + +type connector struct { + kube client.Client + usage resource.Tracker + newDB func(creds map[string][]byte, database string, sslmode string) xsql.DB +} + +var _ managed.TypedExternalConnector[resource.Managed] = &connector{} + +func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.TypedExternalClient[resource.Managed], error) { + cr, ok := mg.(*namespacedv1alpha1.Grant) + if !ok { + return nil, errors.New(errNotGrant) + } + + if err := c.usage.Track(ctx, mg); err != nil { + return nil, errors.Wrap(err, errTrackPCUsage) + } + + // ProviderConfigReference could theoretically be nil, but in practice the + // DefaultProviderConfig initializer will set it before we get here. + providerInfo, err := provider.GetProviderConfig(ctx, c.kube, cr) + if err != nil { + return nil, err + } + + return &external{ + db: c.newDB(providerInfo.SecretData, providerInfo.DefaultDatabase, clients.ToString(providerInfo.SSLMode)), + kube: c.kube, + }, nil +} + +type external struct { + db xsql.DB + kube client.Client +} + +var _ managed.TypedExternalClient[resource.Managed] = &external{} + +type grantType string + +const ( + roleMember grantType = "ROLE_MEMBER" + roleDatabase grantType = "ROLE_DATABASE" +) + +func identifyGrantType(gp namespacedv1alpha1.GrantParameters) (grantType, error) { + pc := len(gp.Privileges) + + // If memberOf is specified, this is ROLE_MEMBER + // NOTE: If any of these are set, even if the lookup by ref or selector fails, + // then this is still a roleMember grant type. + if gp.MemberOfRef != nil || gp.MemberOfSelector != nil || gp.MemberOf != nil { + if gp.Database != nil || pc > 0 { + return "", errors.New(errMemberOfWithDatabaseOrPrivileges) + } + return roleMember, nil + } + + if gp.Database == nil { + return "", errors.New(errNoDatabase) + } + + if pc < 1 { + return "", errors.New(errNoPrivileges) + } + + // This is ROLE_DATABASE + return roleDatabase, nil +} + +func selectGrantQuery(gp namespacedv1alpha1.GrantParameters, q *xsql.Query) error { + gt, err := identifyGrantType(gp) + if err != nil { + return err + } + + switch gt { + case roleMember: + ao := gp.WithOption != nil && *gp.WithOption == namespacedv1alpha1.GrantOptionAdmin + + // Always returns a row with a true or false value + // A simpler query would use ::regrol to cast the + // roleid and member oids to their role names, but + // if this is used with a nonexistent role name it will + // throw an error rather than return false. + q.String = "SELECT EXISTS(SELECT 1 FROM pg_auth_members m " + + "INNER JOIN pg_roles mo ON m.roleid = mo.oid " + + "INNER JOIN pg_roles r ON m.member = r.oid " + + "WHERE r.rolname=$1 AND mo.rolname=$2 AND " + + "m.admin_option = $3)" + + q.Parameters = []interface{}{ + gp.Role, + gp.MemberOf, + ao, + } + return nil + case roleDatabase: + gro := gp.WithOption != nil && *gp.WithOption == namespacedv1alpha1.GrantOptionGrant + + ep := gp.Privileges.ExpandPrivileges() + sp := ep.ToStringSlice() + // Join grantee. Filter by database name and grantee name. + // Finally, perform a permission comparison against expected + // permissions. + q.String = "SELECT EXISTS(SELECT 1 " + + "FROM pg_database db, " + + "aclexplode(datacl) as acl " + + "INNER JOIN pg_roles s ON acl.grantee = s.oid " + + // Filter by database, role and grantable setting + "WHERE db.datname=$1 " + + "AND s.rolname=$2 " + + "AND acl.is_grantable=$3 " + + "GROUP BY db.datname, s.rolname, acl.is_grantable " + + // Check privileges match. Convoluted right-hand-side is necessary to + // ensure identical sort order of the input permissions. + "HAVING array_agg(acl.privilege_type ORDER BY privilege_type ASC) " + + "= (SELECT array(SELECT unnest($4::text[]) as perms ORDER BY perms ASC)))" + + q.Parameters = []interface{}{ + gp.Database, + gp.Role, + gro, + pq.Array(sp), + } + return nil + } + return errors.New(errUnknownGrant) +} + +func withOption(option *namespacedv1alpha1.GrantOption) string { + if option != nil { + return fmt.Sprintf("WITH %s OPTION", string(*option)) + } + return "" +} + +func createGrantQueries(gp namespacedv1alpha1.GrantParameters, ql *[]xsql.Query) error { // nolint: gocyclo + gt, err := identifyGrantType(gp) + if err != nil { + return err + } + + ro := pq.QuoteIdentifier(*gp.Role) + + switch gt { + case roleMember: + if gp.MemberOf == nil || gp.Role == nil { + return errors.Errorf(errInvalidParams, roleMember) + } + + mo := pq.QuoteIdentifier(*gp.MemberOf) + + *ql = append(*ql, + xsql.Query{String: fmt.Sprintf("REVOKE %s FROM %s", mo, ro)}, + xsql.Query{String: fmt.Sprintf("GRANT %s TO %s %s", mo, ro, + withOption(gp.WithOption), + )}, + ) + return nil + case roleDatabase: + if gp.Database == nil || gp.Role == nil || len(gp.Privileges) < 1 { + return errors.Errorf(errInvalidParams, roleDatabase) + } + + db := pq.QuoteIdentifier(*gp.Database) + sp := strings.Join(gp.Privileges.ToStringSlice(), ",") + + *ql = append(*ql, + // REVOKE ANY MATCHING EXISTING PERMISSIONS + xsql.Query{String: fmt.Sprintf("REVOKE %s ON DATABASE %s FROM %s", + sp, + db, + ro, + )}, + + // GRANT REQUESTED PERMISSIONS + xsql.Query{String: fmt.Sprintf("GRANT %s ON DATABASE %s TO %s %s", + sp, + db, + ro, + withOption(gp.WithOption), + )}, + ) + if gp.RevokePublicOnDb != nil && *gp.RevokePublicOnDb { + *ql = append(*ql, + // REVOKE FROM PUBLIC + xsql.Query{String: fmt.Sprintf("REVOKE ALL ON DATABASE %s FROM PUBLIC", + db, + )}, + ) + } + return nil + } + return errors.New(errUnknownGrant) +} + +func deleteGrantQuery(gp namespacedv1alpha1.GrantParameters, q *xsql.Query) error { + gt, err := identifyGrantType(gp) + if err != nil { + return err + } + + ro := pq.QuoteIdentifier(*gp.Role) + + switch gt { + case roleMember: + q.String = fmt.Sprintf("REVOKE %s FROM %s", + pq.QuoteIdentifier(*gp.MemberOf), + ro, + ) + return nil + case roleDatabase: + q.String = fmt.Sprintf("REVOKE %s ON DATABASE %s FROM %s", + strings.Join(gp.Privileges.ToStringSlice(), ","), + pq.QuoteIdentifier(*gp.Database), + ro, + ) + return nil + } + return errors.New(errUnknownGrant) +} + +func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { + cr, ok := mg.(*namespacedv1alpha1.Grant) + if !ok { + return managed.ExternalObservation{}, errors.New(errNotGrant) + } + + if cr.Spec.ForProvider.Role == nil { + return managed.ExternalObservation{}, errors.New(errNoRole) + } + + gp := cr.Spec.ForProvider + var query xsql.Query + if err := selectGrantQuery(gp, &query); err != nil { + return managed.ExternalObservation{}, err + } + + exists := false + + if err := c.db.Scan(ctx, query, &exists); err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, errSelectGrant) + } + + if !exists { + return managed.ExternalObservation{ResourceExists: false}, nil + } + + // Grants have no way of being 'not up to date' - if they exist, they are up to date + cr.SetConditions(xpv1.Available()) + + return managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + ResourceLateInitialized: false, + }, nil +} + +func (c *external) Create(ctx context.Context, mg resource.Managed) (managed.ExternalCreation, error) { + cr, ok := mg.(*namespacedv1alpha1.Grant) + if !ok { + return managed.ExternalCreation{}, errors.New(errNotGrant) + } + + var queries []xsql.Query + + cr.SetConditions(xpv1.Creating()) + + if err := createGrantQueries(cr.Spec.ForProvider, &queries); err != nil { + return managed.ExternalCreation{}, errors.Wrap(err, errCreateGrant) + } + + err := c.db.ExecTx(ctx, queries) + return managed.ExternalCreation{}, errors.Wrap(err, errCreateGrant) +} + +func (c *external) Update(ctx context.Context, mg resource.Managed) (managed.ExternalUpdate, error) { + // Update is a no-op, as permissions are fully revoked and then granted in the Create function, + // inside a transaction. + return managed.ExternalUpdate{}, nil +} + +func (c *external) Disconnect(ctx context.Context) error { + return nil +} + +func (c *external) Delete(ctx context.Context, mg resource.Managed) (managed.ExternalDelete, error) { + cr, ok := mg.(*namespacedv1alpha1.Grant) + if !ok { + return managed.ExternalDelete{}, errors.New(errNotGrant) + } + var query xsql.Query + + cr.SetConditions(xpv1.Deleting()) + + err := deleteGrantQuery(cr.Spec.ForProvider, &query) + if err != nil { + return managed.ExternalDelete{}, errors.Wrap(err, errRevokeGrant) + } + + return managed.ExternalDelete{}, errors.Wrap(c.db.Exec(ctx, query), errRevokeGrant) +} diff --git a/pkg/controller/namespaced/postgresql/grant/reconciler_test.go b/pkg/controller/namespaced/postgresql/grant/reconciler_test.go new file mode 100644 index 00000000..6776ca89 --- /dev/null +++ b/pkg/controller/namespaced/postgresql/grant/reconciler_test.go @@ -0,0 +1,686 @@ +/* +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 grant + +import ( + "context" + "database/sql" + "fmt" + "sort" + "testing" + + "github.com/crossplane-contrib/provider-sql/apis/namespaced/postgresql/v1alpha1" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/lib/pq" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/crossplane/crossplane-runtime/v2/apis/common" + xpv2 "github.com/crossplane/crossplane-runtime/v2/apis/common/v2" + "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/crossplane-contrib/provider-sql/pkg/clients/xsql" + provErrors "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/errors" +) + +type mockDB struct { + MockExec func(ctx context.Context, q xsql.Query) error + MockExecTx func(ctx context.Context, ql []xsql.Query) error + MockScan func(ctx context.Context, q xsql.Query, dest ...interface{}) error + MockQuery func(ctx context.Context, q xsql.Query) (*sql.Rows, error) + MockGetConnectionDetails func(username, password string) managed.ConnectionDetails +} + +func (m mockDB) Exec(ctx context.Context, q xsql.Query) error { + return m.MockExec(ctx, q) +} + +func (m mockDB) ExecTx(ctx context.Context, ql []xsql.Query) error { + return m.MockExecTx(ctx, ql) +} + +func (m mockDB) Scan(ctx context.Context, q xsql.Query, dest ...interface{}) error { + return m.MockScan(ctx, q, dest...) +} + +func (m mockDB) Query(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return m.MockQuery(ctx, q) +} + +func (m mockDB) GetConnectionDetails(username, password string) managed.ConnectionDetails { + return m.MockGetConnectionDetails(username, password) +} + +func TestConnect(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + kube client.Client + usage resource.Tracker + newDB func(creds map[string][]byte, database string, sslmode string) xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + cases := map[string]struct { + reason string + fields fields + args args + want error + }{ + "ErrNotGrant": { + reason: "An error should be returned if the managed resource is not a *Grant", + args: args{ + mg: nil, + }, + want: errors.New(errNotGrant), + }, + "ErrTrackProviderConfigUsage": { + reason: "An error should be returned if we can't track our ProviderConfig usage", + fields: fields{ + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return errBoom }), + }, + args: args{ + mg: &v1alpha1.Grant{}, + }, + want: errors.Wrap(errBoom, errTrackPCUsage), + }, + "InvalideProviderConfigKind": { + reason: "An error should be returned if our ProviderConfig kind is invalid", + fields: fields{ + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{Kind: "Invalid"}, + }, + }, + }, + }, + want: provErrors.InvalidProviderConfigKindError("Invalid"), + }, + "ErrGetProviderConfig": { + reason: "An error should be returned if we can't get our ProviderConfig", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(errBoom), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Grant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.GrantSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetProviderConfigError(errBoom), + }, + "ErrGetClusterProviderConfig": { + reason: "An error should be returned if we can't get our ClusterProviderConfig", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(errBoom), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ClusterProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetClusterProviderConfigError(errBoom), + }, + "ErrMissingConnectionSecret": { + reason: "An error should be returned if our ProviderConfig doesn't specify a connection secret", + fields: fields{ + kube: &test.MockClient{ + // We call get to populate the Role struct, then again + // to populate the (empty) ProviderConfig struct, resulting + // in a ProviderConfig with a nil connection secret. + MockGet: test.NewMockGetFn(nil), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Grant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.GrantSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.MissingSecretRefError(), + }, + "ErrGetConnectionSecret": { + reason: "An error should be returned if we can't get our ProviderConfig's connection secret", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + switch o := obj.(type) { + case *v1alpha1.ProviderConfig: + o.Spec.Credentials.ConnectionSecretRef = common.LocalSecretReference{Name: "example"} + case *corev1.Secret: + return errBoom + } + return nil + }), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Grant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.GrantSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetSecretError(errBoom), + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := &connector{kube: tc.fields.kube, usage: tc.fields.usage, newDB: tc.fields.newDB} + _, err := e.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") + goa := v1alpha1.GrantOptionAdmin + gog := v1alpha1.GrantOptionGrant + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + o managed.ExternalObservation + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotGrant": { + reason: "An error should be returned if the managed resource is not a *Grant", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotGrant), + }, + }, + "SuccessNoGrant": { + reason: "We should return ResourceExists: false when no grant is found", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { + // Default value is false, so just return + bv := dest[0].(*bool) + *bv = false + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + Role: ptr.To("test-example"), + Privileges: v1alpha1.GrantPrivileges{"ALL"}, + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ResourceExists: false}, + }, + }, + "AllMapsToExpandedPrivileges": { + reason: "We expand ALL to CREATE, TEMPORARY, CONNECT when checking for existing grants", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { + privileges := q.Parameters[3] + + privs, ok := privileges.(*pq.StringArray) + if !ok { + return fmt.Errorf("expected Scan parameter to be pq.StringArray, got %T", privileges) + } + + // The order is not guaranteed, so sort the slices before comparing + sort.Strings(*privs) + + // Return if there's a diff between the expected and actual privileges + diff := cmp.Diff(&pq.StringArray{"CONNECT", "CREATE", "TEMPORARY"}, privileges) + + bv := dest[0].(*bool) + *bv = diff == "" + + // Extra logging in case this test is going to fail + if diff != "" { + t.Logf("expected empty diff, got: %s", diff) + } + + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + Role: ptr.To("test-example"), + Privileges: v1alpha1.GrantPrivileges{"ALL"}, + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + }, + err: nil, + }, + }, + "ErrSelectGrant": { + reason: "We should return any errors encountered while trying to show the grant", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { + return errBoom + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + Role: ptr.To("test-example"), + Privileges: v1alpha1.GrantPrivileges{"CONNECT", "TEMPORARY"}, + WithOption: &gog, + }, + }, + }, + }, + want: want{ + err: errors.Wrap(errBoom, errSelectGrant), + }, + }, + "SuccessRoleDb": { + reason: "We should return no error if we can find our role-db grant", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { + bv := dest[0].(*bool) + *bv = true + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("testdb"), + Role: ptr.To("testrole"), + Privileges: v1alpha1.GrantPrivileges{"ALL"}, + WithOption: &gog, + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + }, + err: nil, + }, + }, + "SuccessRoleMembership": { + reason: "We should return no error if we can find our role-membership grant", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { + bv := dest[0].(*bool) + *bv = true + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Role: ptr.To("testrole"), + MemberOf: ptr.To("parentrole"), + WithOption: &goa, + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + }, + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + 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) + } + }) + } +} + +func TestCreate(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + c managed.ExternalCreation + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotGrant": { + reason: "An error should be returned if the managed resource is not a *Grant", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotGrant), + }, + }, + "ErrExec": { + reason: "Any errors encountered while creating the grant should be returned", + fields: fields{ + db: &mockDB{ + MockExecTx: func(ctx context.Context, ql []xsql.Query) error { return errBoom }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + Role: ptr.To("test-example"), + Privileges: v1alpha1.GrantPrivileges{"ALL"}, + }, + }, + }, + }, + want: want{ + err: errors.Wrap(errBoom, errCreateGrant), + }, + }, + "Success": { + reason: "No error should be returned when we successfully create a grant", + fields: fields{ + db: &mockDB{ + MockExecTx: func(ctx context.Context, ql []xsql.Query) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + Role: ptr.To("test-example"), + Privileges: v1alpha1.GrantPrivileges{"ALL"}, + }, + }, + }, + }, + want: want{ + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + 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) + } + }) + } +} + +func TestUpdate(t *testing.T) { + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + c managed.ExternalUpdate + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNoOp": { + reason: "Update is a no-op, make sure we dont throw an error *Grant", + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + Role: ptr.To("test-example"), + Privileges: v1alpha1.GrantPrivileges{"ALL"}, + }, + }, + }, + }, + want: want{ + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{ + db: tc.fields.db, + } + got, err := e.Update(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, cmpopts.IgnoreMapEntries(func(key string, _ []byte) bool { return key == "password" })); diff != "" { + t.Errorf("\n%s\ne.Create(...): -want, +got:\n%s\n", tc.reason, diff) + } + }) + } +} + +func TestDelete(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + cases := map[string]struct { + reason string + fields fields + args args + want error + }{ + "ErrNotGrant": { + reason: "An error should be returned if the managed resource is not a *Grant", + args: args{ + mg: nil, + }, + want: errors.New(errNotGrant), + }, + "ErrDropGrant": { + reason: "Errors dropping a grant should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + return errBoom + }, + }, + }, + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + Role: ptr.To("test-example"), + Privileges: v1alpha1.GrantPrivileges{"ALL"}, + }, + }, + }, + }, + want: errors.Wrap(errBoom, errRevokeGrant), + }, + "Success": { + reason: "No error should be returned if the grant was revoked", + args: args{ + mg: &v1alpha1.Grant{ + Spec: v1alpha1.GrantSpec{ + ForProvider: v1alpha1.GrantParameters{ + Database: ptr.To("test-example"), + Role: ptr.To("test-example"), + Privileges: v1alpha1.GrantPrivileges{"ALL"}, + }, + }, + }, + }, + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return nil }, + }, + }, + want: nil, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + _, 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/pkg/controller/namespaced/postgresql/postgresql.go b/pkg/controller/namespaced/postgresql/postgresql.go new file mode 100644 index 00000000..ac511a9f --- /dev/null +++ b/pkg/controller/namespaced/postgresql/postgresql.go @@ -0,0 +1,48 @@ +/* +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 postgresql + +import ( + ctrl "sigs.k8s.io/controller-runtime" + + "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/postgresql/config" + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/postgresql/database" + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/postgresql/extension" + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/postgresql/grant" + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/postgresql/role" + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/postgresql/schema" +) + +// Setup creates all PostgreSQL controllers with the supplied logger and adds +// them to the supplied manager. +func Setup(mgr ctrl.Manager, o controller.Options) error { + for _, setup := range []func(ctrl.Manager, controller.Options) error{ + config.Setup, + database.Setup, + role.Setup, + grant.Setup, + extension.Setup, + schema.Setup, + } { + if err := setup(mgr, o); err != nil { + return err + } + } + return nil +} diff --git a/pkg/controller/namespaced/postgresql/provider/provider.go b/pkg/controller/namespaced/postgresql/provider/provider.go new file mode 100644 index 00000000..79737b3c --- /dev/null +++ b/pkg/controller/namespaced/postgresql/provider/provider.go @@ -0,0 +1,89 @@ +package provider + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + "context" + + "github.com/crossplane-contrib/provider-sql/apis/namespaced/postgresql/v1alpha1" + provErrors "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/errors" + + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type ProviderInfo struct { + ProviderConfigName string + SecretData map[string][]byte + DefaultDatabase string + SSLMode *string +} + +func GetProviderConfig(ctx context.Context, kube client.Client, mg resource.ModernManaged) (ProviderInfo, error) { + var ( + secretKey *client.ObjectKey + defaultDatabase string + sslMode *string + ) + + switch mg.GetProviderConfigReference().Kind { + case v1alpha1.ProviderConfigKind: + providerConfig := &v1alpha1.ProviderConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: mg.GetProviderConfigReference().Name, + Namespace: mg.GetNamespace(), + }, + } + + if err := kube.Get(ctx, client.ObjectKeyFromObject(providerConfig), providerConfig); err != nil { + return ProviderInfo{}, provErrors.GetProviderConfigError(err) + } + + secretKey = &client.ObjectKey{ + Name: providerConfig.Spec.Credentials.ConnectionSecretRef.Name, + Namespace: mg.GetNamespace(), + } + + defaultDatabase = providerConfig.Spec.DefaultDatabase + sslMode = providerConfig.Spec.SSLMode + case v1alpha1.ClusterProviderConfigKind: + clusterProviderConfig := &v1alpha1.ClusterProviderConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: mg.GetProviderConfigReference().Name, + }, + } + + if err := kube.Get(ctx, client.ObjectKeyFromObject(clusterProviderConfig), clusterProviderConfig); err != nil { + return ProviderInfo{}, provErrors.GetClusterProviderConfigError(err) + } + + secretKey = &client.ObjectKey{ + Name: clusterProviderConfig.Spec.Credentials.ConnectionSecretRef.Name, + Namespace: clusterProviderConfig.Spec.Credentials.ConnectionSecretRef.Namespace, + } + + defaultDatabase = clusterProviderConfig.Spec.DefaultDatabase + sslMode = clusterProviderConfig.Spec.SSLMode + default: + return ProviderInfo{}, provErrors.InvalidProviderConfigKindError(mg.GetProviderConfigReference().Kind) + } + + if secretKey.Name == "" || secretKey.Namespace == "" { + return ProviderInfo{}, provErrors.MissingSecretRefError() + } + + s := &corev1.Secret{} + err := kube.Get(ctx, *secretKey, s) + if err != nil { + return ProviderInfo{}, provErrors.GetSecretError(err) + } + + return ProviderInfo{ + ProviderConfigName: mg.GetProviderConfigReference().Name, + SecretData: s.Data, + DefaultDatabase: defaultDatabase, + SSLMode: sslMode, + }, nil +} diff --git a/pkg/controller/namespaced/postgresql/role/reconciler.go b/pkg/controller/namespaced/postgresql/role/reconciler.go new file mode 100644 index 00000000..5e78f02f --- /dev/null +++ b/pkg/controller/namespaced/postgresql/role/reconciler.go @@ -0,0 +1,511 @@ +/* +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 role + +import ( + "context" + "fmt" + "strings" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/lib/pq" + "github.com/pkg/errors" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/feature" + "github.com/crossplane/crossplane-runtime/v2/pkg/meta" + "github.com/crossplane/crossplane-runtime/v2/pkg/password" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + namespacedv1alpha1 "github.com/crossplane-contrib/provider-sql/apis/namespaced/postgresql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/pkg/clients" + "github.com/crossplane-contrib/provider-sql/pkg/clients/postgresql" + "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/postgresql/provider" +) + +const ( + errTrackPCUsage = "cannot track ProviderConfig usage" + + errNotRole = "managed resource is not a Role custom resource" + errSelectRole = "cannot select role" + errCreateRole = "cannot create role" + errDropRole = "cannot drop role" + errUpdateRole = "cannot update role" + errGetPasswordSecretFailed = "cannot get password secret" + errComparePrivileges = "cannot compare desired and observed privileges" + errSetRoleConfigs = "cannot set role configuration parameters" + + maxConcurrency = 5 +) + +// TODO(nateinaction): This looks wrong, can tracker creation be improved? +type tracker struct { + tracker *resource.ProviderConfigUsageTracker +} + +var _ resource.Tracker = &tracker{} + +func (t *tracker) Track(ctx context.Context, mg resource.Managed) error { + return t.tracker.Track(ctx, mg.(resource.ModernManaged)) +} + +// Setup adds a controller that reconciles Database managed resources. +func Setup(mgr ctrl.Manager, o xpcontroller.Options) error { + name := managed.ControllerName(namespacedv1alpha1.RoleGroupKind) + + t := resource.NewProviderConfigUsageTracker(mgr.GetClient(), &namespacedv1alpha1.ProviderConfigUsage{}) + trk := &tracker{tracker: t} + + reconcilerOptions := []managed.ReconcilerOption{ + managed.WithTypedExternalConnector(&connector{kube: mgr.GetClient(), usage: trk, newDB: postgresql.New}), + managed.WithLogger(o.Logger.WithValues("controller", name)), + managed.WithPollInterval(o.PollInterval), + managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), + } + if o.Features.Enabled(feature.EnableBetaManagementPolicies) { + reconcilerOptions = append(reconcilerOptions, managed.WithManagementPolicies()) + } + r := managed.NewReconciler(mgr, + resource.ManagedKind(namespacedv1alpha1.RoleGroupVersionKind), + reconcilerOptions..., + ) + return ctrl.NewControllerManagedBy(mgr). + Named(name). + For(&namespacedv1alpha1.Role{}). + WithOptions(controller.Options{ + MaxConcurrentReconciles: maxConcurrency, + }). + Complete(r) +} + +type connector struct { + kube client.Client + usage resource.Tracker + newDB func(creds map[string][]byte, database string, sslmode string) xsql.DB +} + +var _ managed.TypedExternalConnector[resource.Managed] = &connector{} + +func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.TypedExternalClient[resource.Managed], error) { + cr, ok := mg.(*namespacedv1alpha1.Role) + if !ok { + return nil, errors.New(errNotRole) + } + + if err := c.usage.Track(ctx, mg); err != nil { + return nil, errors.Wrap(err, errTrackPCUsage) + } + + // ProviderConfigReference could theoretically be nil, but in practice the + // DefaultProviderConfig initializer will set it before we get here. + providerInfo, err := provider.GetProviderConfig(ctx, c.kube, cr) + if err != nil { + return nil, err + } + + return &external{ + db: c.newDB(providerInfo.SecretData, providerInfo.DefaultDatabase, clients.ToString(providerInfo.SSLMode)), + kube: c.kube, + }, nil +} + +type external struct { + db xsql.DB + kube client.Client +} + +var _ managed.TypedExternalClient[resource.Managed] = &external{} + +func negateClause(clause string, negate *bool, out *[]string) { + // If clause boolean is not set (nil pointer), do not push a setting. + // This means the postgres default is applied. + if negate == nil { + return + } + + if !(*negate) { + clause = "NO" + clause + } + *out = append(*out, clause) +} + +func privilegesToClauses(p namespacedv1alpha1.RolePrivilege) []string { + // Never copy user inputted data to this string. These values are + // passed directly into the query. + pc := []string{} + + negateClause("SUPERUSER", p.SuperUser, &pc) + negateClause("INHERIT", p.Inherit, &pc) + negateClause("CREATEDB", p.CreateDb, &pc) + negateClause("CREATEROLE", p.CreateRole, &pc) + negateClause("LOGIN", p.Login, &pc) + negateClause("REPLICATION", p.Replication, &pc) + negateClause("BYPASSRLS", p.BypassRls, &pc) + + return pc +} + +func changedPrivs(existing []string, desired []string) ([]string, error) { + out := []string{} + + // Make sure existing observation has at least as many items as + // desired. If it does not, then we cannot safely compare + // privileges. + if len(existing) < len(desired) { + return nil, errors.New(errComparePrivileges) + } + + // The input slices here are outputted by privilegesToClauses above. + // Because these are created by repeated calls to negateClause in the + // same order, we can rely on each clause being in the same array + // position in the 'desired' and 'existing' inputs. + + for i, v := range desired { + if v != existing[i] { + out = append(out, v) + } + } + return out, nil +} + +func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { + cr, ok := mg.(*namespacedv1alpha1.Role) + if !ok { + return managed.ExternalObservation{}, errors.New(errNotRole) + } + + observed := &namespacedv1alpha1.RoleParameters{ + Privileges: namespacedv1alpha1.RolePrivilege{ + SuperUser: new(bool), + Inherit: new(bool), + CreateDb: new(bool), + CreateRole: new(bool), + Login: new(bool), + Replication: new(bool), + BypassRls: new(bool), + }, + } + + query := "SELECT " + + "rolsuper, " + + "rolinherit, " + + "rolcreatedb, " + + "rolcreaterole, " + + "rolcanlogin, " + + "rolreplication, " + + "rolbypassrls, " + + "rolconnlimit, " + + "rolconfig " + + "FROM pg_roles WHERE rolname = $1" + + var rolconfigs []string + err := c.db.Scan(ctx, + xsql.Query{ + String: query, + Parameters: []interface{}{ + meta.GetExternalName(cr), + }, + }, + &observed.Privileges.SuperUser, + &observed.Privileges.Inherit, + &observed.Privileges.CreateDb, + &observed.Privileges.CreateRole, + &observed.Privileges.Login, + &observed.Privileges.Replication, + &observed.Privileges.BypassRls, + &observed.ConnectionLimit, + pq.Array(&rolconfigs), + ) + + if xsql.IsNoRows(err) { + return managed.ExternalObservation{ResourceExists: false}, nil + } + if err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, errSelectRole) + } + if len(rolconfigs) > 0 { + var rc []namespacedv1alpha1.RoleConfigurationParameter + for _, c := range rolconfigs { + kv := strings.Split(c, "=") + rc = append(rc, namespacedv1alpha1.RoleConfigurationParameter{ + Name: kv[0], + Value: kv[1], + }) + } + observed.ConfigurationParameters = &rc + } + cr.Status.AtProvider.ConfigurationParameters = observed.ConfigurationParameters + + _, pwdChanged, err := c.getPassword(ctx, cr) + if err != nil { + return managed.ExternalObservation{}, err + } + + cr.SetConditions(xpv1.Available()) + + // PrivilegesAsClauses is used as role status output + cr.Status.AtProvider.PrivilegesAsClauses = privilegesToClauses(observed.Privileges) + + return managed.ExternalObservation{ + ResourceExists: true, + ResourceLateInitialized: lateInit(observed, &cr.Spec.ForProvider), + ResourceUpToDate: !pwdChanged && upToDate(observed, &cr.Spec.ForProvider), + }, nil +} + +func (c *external) Create(ctx context.Context, mg resource.Managed) (managed.ExternalCreation, error) { + cr, ok := mg.(*namespacedv1alpha1.Role) + if !ok { + return managed.ExternalCreation{}, errors.New(errNotRole) + } + + cr.SetConditions(xpv1.Creating()) + + crn := pq.QuoteIdentifier(meta.GetExternalName(cr)) + privs := privilegesToClauses(cr.Spec.ForProvider.Privileges) + + pw, _, err := c.getPassword(ctx, cr) + if err != nil { + return managed.ExternalCreation{}, err + } + + if pw == "" { + pw, err = password.Generate() + if err != nil { + return managed.ExternalCreation{}, err + } + } + + // NOTE we're not using pq's "Parameters" setting here + // because it does not allow us to pass identifiers. + if err := c.db.Exec(ctx, xsql.Query{ + String: fmt.Sprintf( + "CREATE ROLE %s PASSWORD %s %s", + crn, + pq.QuoteLiteral(pw), + strings.Join(privs, " "), + ), + }); err != nil { + return managed.ExternalCreation{}, errors.Wrap(err, errCreateRole) + } + + // PrivilegesAsClauses is used as role status output + // Update here so that state is reflected to the user prior to the next + // reconciler loop. + cr.Status.AtProvider.PrivilegesAsClauses = privs + if cr.Spec.ForProvider.ConfigurationParameters != nil { + for _, v := range *cr.Spec.ForProvider.ConfigurationParameters { + if err := c.db.Exec(ctx, xsql.Query{ + String: fmt.Sprintf("ALTER ROLE %s set %s=%s", crn, pq.QuoteIdentifier(v.Name), pq.QuoteIdentifier(v.Value)), + }); err != nil { + return managed.ExternalCreation{}, errors.Wrap(err, errSetRoleConfigs) + } + } + cr.Status.AtProvider.ConfigurationParameters = cr.Spec.ForProvider.ConfigurationParameters + } + + return managed.ExternalCreation{ + ConnectionDetails: c.db.GetConnectionDetails(meta.GetExternalName(cr), pw), + }, nil +} + +func (c *external) Update(ctx context.Context, mg resource.Managed) (managed.ExternalUpdate, error) { //nolint:gocyclo + // NOTE(benagricola): This is just a touch over the cyclomatic complexity + // limit, but is unlikely to become more complex unless new role features + // are added. Think about splitting this method up if new functionality + // is desired. + cr, ok := mg.(*namespacedv1alpha1.Role) + if !ok { + return managed.ExternalUpdate{}, errors.New(errNotRole) + } + + pw, pwchanged, err := c.getPassword(ctx, cr) + if err != nil { + return managed.ExternalUpdate{}, err + } + + crn := pq.QuoteIdentifier(meta.GetExternalName(cr)) + + if pwchanged { + if err := c.db.Exec(ctx, xsql.Query{ + String: fmt.Sprintf("ALTER ROLE %s PASSWORD %s", crn, pq.QuoteLiteral(pw)), + }); err != nil { + return managed.ExternalUpdate{}, errors.Wrap(err, errUpdateRole) + } + } + + privs := privilegesToClauses(cr.Spec.ForProvider.Privileges) + cp, err := changedPrivs(cr.Status.AtProvider.PrivilegesAsClauses, privs) + + if err != nil { + return managed.ExternalUpdate{}, errors.Wrap(err, errUpdateRole) + } + + if len(cp) > 0 { + if err := c.db.Exec(ctx, xsql.Query{ + String: fmt.Sprintf("ALTER ROLE %s %s", crn, strings.Join(cp, " ")), + }); err != nil { + return managed.ExternalUpdate{}, errors.Wrap(err, errUpdateRole) + } + } + + // PrivilegesAsClauses is used as role status output + // Update here so that state is reflected to the user prior to the next + // reconciler loop. + cr.Status.AtProvider.PrivilegesAsClauses = privs + + // Checks if current role configuration parameters differs from desired state. + // If difference, reset all parameters and apply desired parameters in a transaction + if cr.Spec.ForProvider.ConfigurationParameters != nil && !cmp.Equal(cr.Status.AtProvider.ConfigurationParameters, cr.Spec.ForProvider.ConfigurationParameters, + cmpopts.SortSlices(func(o, d namespacedv1alpha1.RoleConfigurationParameter) bool { return o.Name < d.Name })) { + q := make([]xsql.Query, 0) + q = append(q, xsql.Query{ + String: fmt.Sprintf("ALTER ROLE %s RESET ALL", crn), + }) + // search_path="$user", public is valid so need to handle that + for _, v := range *cr.Spec.ForProvider.ConfigurationParameters { + sb := strings.Builder{} + values := strings.Split(v.Value, ",") + for i, v := range values { + sb.WriteString(pq.QuoteLiteral(strings.TrimSpace(strings.Trim(v, "'\"")))) + if i < len(values)-1 { + sb.WriteString(",") + } + } + q = append(q, xsql.Query{ + String: fmt.Sprintf("ALTER ROLE %s set %s=%s", crn, pq.QuoteIdentifier(v.Name), sb.String()), + }) + } + if err := c.db.ExecTx(ctx, q); err != nil { + return managed.ExternalUpdate{}, errors.Wrap(err, errUpdateRole) + } + // Update state to reflect the current configuration parameters + cr.Status.AtProvider.ConfigurationParameters = cr.Spec.ForProvider.ConfigurationParameters + } + cl := cr.Spec.ForProvider.ConnectionLimit + if cl != nil { + if err := c.db.Exec(ctx, xsql.Query{ + String: fmt.Sprintf("ALTER ROLE %s CONNECTION LIMIT %d", crn, int64(*cl)), + }); err != nil { + return managed.ExternalUpdate{}, errors.Wrap(err, errUpdateRole) + } + } + + // Only update connection details if password is changed + if pwchanged { + return managed.ExternalUpdate{ + ConnectionDetails: c.db.GetConnectionDetails(meta.GetExternalName(cr), pw), + }, nil + } + return managed.ExternalUpdate{}, nil +} + +func (c *external) Delete(ctx context.Context, mg resource.Managed) (managed.ExternalDelete, error) { + cr, ok := mg.(*namespacedv1alpha1.Role) + if !ok { + return managed.ExternalDelete{}, errors.New(errNotRole) + } + cr.SetConditions(xpv1.Deleting()) + err := c.db.Exec(ctx, xsql.Query{ + String: "DROP ROLE IF EXISTS " + pq.QuoteIdentifier(meta.GetExternalName(cr)), + }) + return managed.ExternalDelete{}, errors.Wrap(err, errDropRole) +} + +func upToDate(observed *namespacedv1alpha1.RoleParameters, desired *namespacedv1alpha1.RoleParameters) bool { + if observed.ConnectionLimit != desired.ConnectionLimit { + return false + } + if observed.Privileges.SuperUser != desired.Privileges.SuperUser { + return false + } + if observed.Privileges.Inherit != desired.Privileges.Inherit { + return false + } + if observed.Privileges.CreateDb != desired.Privileges.CreateDb { + return false + } + if observed.Privileges.CreateRole != desired.Privileges.CreateRole { + return false + } + if observed.Privileges.Login != desired.Privileges.Login { + return false + } + if observed.Privileges.Replication != desired.Privileges.Replication { + return false + } + if observed.Privileges.BypassRls != desired.Privileges.BypassRls { + return false + } + if !cmp.Equal(observed.ConfigurationParameters, desired.ConfigurationParameters, + cmpopts.SortSlices(func(o, d namespacedv1alpha1.RoleConfigurationParameter) bool { return o.Name < d.Name })) { + return false + } + return true +} + +func lateInit(observed *namespacedv1alpha1.RoleParameters, desired *namespacedv1alpha1.RoleParameters) bool { + li := false + + if desired.Privileges.SuperUser == nil { + desired.Privileges.SuperUser = observed.Privileges.SuperUser + li = true + } + if desired.Privileges.Inherit == nil { + desired.Privileges.Inherit = observed.Privileges.Inherit + li = true + } + if desired.Privileges.CreateDb == nil { + desired.Privileges.CreateDb = observed.Privileges.CreateDb + li = true + } + if desired.Privileges.CreateRole == nil { + desired.Privileges.CreateRole = observed.Privileges.CreateRole + li = true + } + if desired.Privileges.Login == nil { + desired.Privileges.Login = observed.Privileges.Login + li = true + } + if desired.Privileges.Replication == nil { + desired.Privileges.Replication = observed.Privileges.Replication + li = true + } + if desired.Privileges.BypassRls == nil { + desired.Privileges.BypassRls = observed.Privileges.BypassRls + li = true + } + if desired.ConnectionLimit == nil { + desired.ConnectionLimit = observed.ConnectionLimit + li = true + } + + return li +} + +func (c *external) Disconnect(ctx context.Context) error { + return nil +} diff --git a/pkg/controller/namespaced/postgresql/role/reconciler_test.go b/pkg/controller/namespaced/postgresql/role/reconciler_test.go new file mode 100644 index 00000000..8492195a --- /dev/null +++ b/pkg/controller/namespaced/postgresql/role/reconciler_test.go @@ -0,0 +1,1201 @@ +/* +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 role + +import ( + "context" + "database/sql" + "fmt" + "testing" + + "github.com/crossplane-contrib/provider-sql/apis/namespaced/postgresql/v1alpha1" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/lib/pq" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/crossplane/crossplane-runtime/v2/apis/common" + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpv2 "github.com/crossplane/crossplane-runtime/v2/apis/common/v2" + "github.com/crossplane/crossplane-runtime/v2/pkg/meta" + "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/crossplane-contrib/provider-sql/pkg/clients/xsql" + provErrors "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/errors" +) + +type mockDB struct { + MockExec func(ctx context.Context, q xsql.Query) error + MockExecTx func(ctx context.Context, ql []xsql.Query) error + MockScan func(ctx context.Context, q xsql.Query, dest ...interface{}) error +} + +func (m mockDB) Exec(ctx context.Context, q xsql.Query) error { + return m.MockExec(ctx, q) +} + +func (m mockDB) ExecTx(ctx context.Context, ql []xsql.Query) error { + return m.MockExecTx(ctx, ql) +} + +func (m mockDB) Scan(ctx context.Context, q xsql.Query, dest ...interface{}) error { + return m.MockScan(ctx, q, dest...) +} + +func (m mockDB) Query(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return &sql.Rows{}, nil +} + +func (m mockDB) GetConnectionDetails(rolename, password string) managed.ConnectionDetails { + return managed.ConnectionDetails{ + xpv1.ResourceCredentialsSecretUserKey: []byte(rolename), + xpv1.ResourceCredentialsSecretPasswordKey: []byte(password), + xpv1.ResourceCredentialsSecretEndpointKey: []byte("localhost"), + xpv1.ResourceCredentialsSecretPortKey: []byte("5432"), + } +} + +func TestConnect(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + kube client.Client + usage resource.Tracker + newDB func(creds map[string][]byte, database string, sslmode string) xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + cases := map[string]struct { + reason string + fields fields + args args + want error + }{ + "ErrNotRole": { + reason: "An error should be returned if the managed resource is not a *Role", + args: args{ + mg: nil, + }, + want: errors.New(errNotRole), + }, + "ErrTrackProviderConfigUsage": { + reason: "An error should be returned if we can't track our ProviderConfig usage", + fields: fields{ + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return errBoom }), + }, + args: args{ + mg: &v1alpha1.Role{}, + }, + want: errors.Wrap(errBoom, errTrackPCUsage), + }, + "InvalideProviderConfigKind": { + reason: "An error should be returned if our ProviderConfig kind is invalid", + fields: fields{ + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Role{ + Spec: v1alpha1.RoleSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{Kind: "Invalid"}, + }, + }, + }, + }, + want: provErrors.InvalidProviderConfigKindError("Invalid"), + }, + "ErrGetProviderConfig": { + reason: "An error should be returned if we can't get our ProviderConfig", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(errBoom), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.RoleSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetProviderConfigError(errBoom), + }, + "ErrGetClusterProviderConfig": { + reason: "An error should be returned if we can't get our ClusterProviderConfig", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(errBoom), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Role{ + Spec: v1alpha1.RoleSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ClusterProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetClusterProviderConfigError(errBoom), + }, + "ErrMissingConnectionSecret": { + reason: "An error should be returned if our ProviderConfig doesn't specify a connection secret", + fields: fields{ + kube: &test.MockClient{ + // We call get to populate the Role struct, then again + // to populate the (empty) ProviderConfig struct, resulting + // in a ProviderConfig with a nil connection secret. + MockGet: test.NewMockGetFn(nil), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.RoleSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.MissingSecretRefError(), + }, + "ErrGetConnectionSecret": { + reason: "An error should be returned if we can't get our ProviderConfig's connection secret", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + switch o := obj.(type) { + case *v1alpha1.ProviderConfig: + o.Spec.Credentials.ConnectionSecretRef = common.LocalSecretReference{Name: "example"} + case *corev1.Secret: + return errBoom + } + return nil + }), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.RoleSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetSecretError(errBoom), + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := &connector{kube: tc.fields.kube, usage: tc.fields.usage, newDB: tc.fields.newDB} + _, err := e.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") + + type fields struct { + db xsql.DB + kube client.Client + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + o managed.ExternalObservation + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotRole": { + reason: "An error should be returned if the managed resource is not a *Role", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotRole), + }, + }, + "ErrNoRole": { + reason: "We should return ResourceExists: false when no role is found", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return sql.ErrNoRows }, + }, + }, + args: args{ + mg: &v1alpha1.Role{}, + }, + want: want{ + o: managed.ExternalObservation{ResourceExists: false}, + }, + }, + "ErrSelectRole": { + reason: "We should return any errors encountered while trying to select the role", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return errBoom }, + }, + }, + args: args{ + mg: &v1alpha1.Role{}, + }, + want: want{ + err: errors.Wrap(errBoom, errSelectRole), + }, + }, + "Success": { + reason: "We should return no error if we can successfully select our role", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.Role{ + Spec: v1alpha1.RoleSpec{ + ForProvider: v1alpha1.RoleParameters{ + Privileges: v1alpha1.RolePrivilege{}, + ConnectionLimit: nil, + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + ResourceLateInitialized: true, + }, + err: nil, + }, + }, + "PasswordChanged": { + reason: "We should return ResourceUpToDate=false if the password changed", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return nil }, + }, + kube: &test.MockClient{ + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + secret := corev1.Secret{ + Data: map[string][]byte{}, + } + secret.Data[xpv1.ResourceCredentialsSecretPasswordKey] = []byte(key.Name) + secret.DeepCopyInto(obj.(*corev1.Secret)) + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Role{ + Spec: v1alpha1.RoleSpec{ + ForProvider: v1alpha1.RoleParameters{ + PasswordSecretRef: &common.LocalSecretKeySelector{ + LocalSecretReference: common.LocalSecretReference{ + Name: "example", + }, + Key: "password", + }, + Privileges: v1alpha1.RolePrivilege{}, + ConnectionLimit: ptr.To(int32(10)), + }, + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + WriteConnectionSecretToReference: &common.LocalSecretReference{ + Name: "connection-secret", + }, + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: false, + ResourceLateInitialized: true, + }, + err: nil, + }, + }, + "ConfigurationParametersChanged": { + reason: "We should return ResourceUpToDate=false if ConfigurationParameter is changed", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.Role{ + Spec: v1alpha1.RoleSpec{ + ForProvider: v1alpha1.RoleParameters{ + ConfigurationParameters: &[]v1alpha1.RoleConfigurationParameter{ + { + Name: "statement_timeout", + Value: "1", + }, + }, + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: false, + ResourceLateInitialized: true, + }, + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{ + db: tc.fields.db, + kube: tc.fields.kube, + } + 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) + } + }) + } +} + +func TestCreate(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + db xsql.DB + kube client.Client + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + c managed.ExternalCreation + err error + } + + cases := map[string]struct { + reason string + comparePw bool + fields fields + args args + want want + }{ + "ErrNotRole": { + reason: "An error should be returned if the managed resource is not a *Role", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotRole), + }, + }, + "ErrExec": { + reason: "Any errors encountered while creating the role should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return errBoom }, + }, + }, + args: args{ + mg: &v1alpha1.Role{}, + }, + want: want{ + err: errors.Wrap(errBoom, errCreateRole), + }, + }, + "Success": { + reason: "No error should be returned when we successfully create a role", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: "example", + }, + }, + Spec: v1alpha1.RoleSpec{ + ForProvider: v1alpha1.RoleParameters{ + Privileges: v1alpha1.RolePrivilege{ + CreateDb: new(bool), + Login: new(bool), + CreateRole: new(bool), + }, + ConfigurationParameters: &[]v1alpha1.RoleConfigurationParameter{ + { + Name: "search_path", + Value: "\"$user\",public", + }, + { + Name: "pgaudit.log", + Value: "All", + }, + }, + }, + }, + }, + }, + want: want{ + err: nil, + c: managed.ExternalCreation{ + ConnectionDetails: managed.ConnectionDetails{ + xpv1.ResourceCredentialsSecretUserKey: []byte("example"), + xpv1.ResourceCredentialsSecretPasswordKey: []byte(""), + xpv1.ResourceCredentialsSecretEndpointKey: []byte("localhost"), + xpv1.ResourceCredentialsSecretPortKey: []byte("5432"), + }, + }, + }, + }, + "RoleWithPasswordRef": { + reason: "The password must be read from the secret", + comparePw: true, + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return nil }, + }, + kube: &test.MockClient{ + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + switch key.Name { + case "example": + secret := corev1.Secret{ + Data: map[string][]byte{}, + } + secret.Data["password-custom"] = []byte("test1234") + secret.DeepCopyInto(obj.(*corev1.Secret)) + return nil + default: + return nil + } + }, + }, + }, + args: args{ + mg: &v1alpha1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: "example", + }, + }, + Spec: v1alpha1.RoleSpec{ + ForProvider: v1alpha1.RoleParameters{ + PasswordSecretRef: &common.LocalSecretKeySelector{ + LocalSecretReference: common.LocalSecretReference{ + Name: "example", + }, + Key: "password-custom", + }, + }, + }, + }, + }, + want: want{ + err: nil, + c: managed.ExternalCreation{ + ConnectionDetails: managed.ConnectionDetails{ + xpv1.ResourceCredentialsSecretUserKey: []byte("example"), + xpv1.ResourceCredentialsSecretPasswordKey: []byte("test1234"), + xpv1.ResourceCredentialsSecretEndpointKey: []byte("localhost"), + xpv1.ResourceCredentialsSecretPortKey: []byte("5432"), + }, + }, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{ + db: tc.fields.db, + kube: tc.fields.kube, + } + 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) + } + var opts []cmp.Option + if !tc.comparePw { + opts = append(opts, cmpopts.IgnoreMapEntries(func(key string, _ []byte) bool { return key == "password" })) + } + if diff := cmp.Diff(tc.want.c, got, opts...); diff != "" { + t.Errorf("\n%s\ne.Create(...): -want, +got:\n%s\n", tc.reason, diff) + } + }) + } +} + +func TestUpdate(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + kube client.Client + } + + type want struct { + c managed.ExternalUpdate + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotRole": { + reason: "An error should be returned if the managed resource is not a *Role", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotRole), + }, + }, + "ErrExec": { + reason: "Any errors encountered while updating the role should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return errBoom }, + }, + }, + args: args{ + mg: &v1alpha1.Role{ + Spec: v1alpha1.RoleSpec{ + ForProvider: v1alpha1.RoleParameters{ + PasswordSecretRef: &common.LocalSecretKeySelector{ + LocalSecretReference: common.LocalSecretReference{ + Name: "connection-secret", + }, + Key: xpv1.ResourceCredentialsSecretPasswordKey, + }, + }, + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + WriteConnectionSecretToReference: &common.LocalSecretReference{ + Name: "password-secret", + }, + }, + }, + }, + kube: &test.MockClient{ + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + secret := corev1.Secret{ + Data: map[string][]byte{}, + } + secret.Data[xpv1.ResourceCredentialsSecretPasswordKey] = []byte(key.Name) + secret.DeepCopyInto(obj.(*corev1.Secret)) + return nil + }, + }, + }, + want: want{ + err: errors.Wrap(errBoom, errUpdateRole), + }, + }, + "Success": { + reason: "No error should be returned when we don't have to update a role", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: "example", + }, + }, + }, + }, + want: want{ + err: nil, + c: managed.ExternalUpdate{}, + }, + }, + "SamePassword": { + reason: "No DB query should be executed if the password didn't change", + fields: fields{ + db: &mockDB{}, + }, + args: args{ + mg: &v1alpha1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: "example", + }, + }, + Spec: v1alpha1.RoleSpec{ + ForProvider: v1alpha1.RoleParameters{ + PasswordSecretRef: &common.LocalSecretKeySelector{ + LocalSecretReference: common.LocalSecretReference{ + Name: "connection-secret", + }, + Key: xpv1.ResourceCredentialsSecretPasswordKey, + }, + }, + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + WriteConnectionSecretToReference: &common.LocalSecretReference{ + Name: "connection-secret", + }, + }, + }, + }, + kube: &test.MockClient{ + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + secret := corev1.Secret{ + Data: map[string][]byte{}, + } + secret.Data[xpv1.ResourceCredentialsSecretPasswordKey] = []byte("samesame") + secret.DeepCopyInto(obj.(*corev1.Secret)) + return nil + }, + }, + }, + want: want{}, + }, + "UpdatePassword": { + reason: "The password must be updated", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: "example", + }, + }, + Spec: v1alpha1.RoleSpec{ + ForProvider: v1alpha1.RoleParameters{ + PasswordSecretRef: &common.LocalSecretKeySelector{ + LocalSecretReference: common.LocalSecretReference{ + Name: "example", + }, + Key: "password-custom", + }, + }, + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + WriteConnectionSecretToReference: &common.LocalSecretReference{ + Name: "connection-secret", + }, + }, + }, + }, + kube: &test.MockClient{ + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + switch key.Name { + case "example": + secret := corev1.Secret{ + Data: map[string][]byte{}, + } + secret.Data["password-custom"] = []byte("newpassword") + secret.DeepCopyInto(obj.(*corev1.Secret)) + return nil + case "connection-secret": + secret := corev1.Secret{ + Data: map[string][]byte{}, + } + secret.Data[xpv1.ResourceCredentialsSecretPasswordKey] = []byte("oldpassword") + secret.DeepCopyInto(obj.(*corev1.Secret)) + return nil + default: + return nil + } + }, + }, + }, + want: want{ + err: nil, + c: managed.ExternalUpdate{ + ConnectionDetails: managed.ConnectionDetails{ + xpv1.ResourceCredentialsSecretUserKey: []byte("example"), + xpv1.ResourceCredentialsSecretPasswordKey: []byte("newpassword"), + xpv1.ResourceCredentialsSecretEndpointKey: []byte("localhost"), + xpv1.ResourceCredentialsSecretPortKey: []byte("5432"), + }, + }, + }, + }, + "NoUpdateUnchangedPrivs": { + reason: "We should only try to set privileges whose values have changed.", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + // Verify that query contains only the identifier that we + // expect, otherwise return a boom. + crn := pq.QuoteIdentifier("example") + if q.String != fmt.Sprintf("ALTER ROLE %s NOINHERIT", crn) { + return errBoom + } + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: "example", + }, + }, + Spec: v1alpha1.RoleSpec{ + ForProvider: v1alpha1.RoleParameters{ + PasswordSecretRef: &common.LocalSecretKeySelector{ + LocalSecretReference: common.LocalSecretReference{ + Name: "connection-secret", + }, + Key: xpv1.ResourceCredentialsSecretPasswordKey, + }, + Privileges: v1alpha1.RolePrivilege{ + Login: ptr.To(true), + Inherit: ptr.To(false), + }, + }, + }, + Status: v1alpha1.RoleStatus{ + AtProvider: v1alpha1.RoleObservation{ + // Observed status vs. Requested status means we should ALTER + // to NOINHERIT. Order is important here, make sure this + // matches the order in privilegesToClauses() + PrivilegesAsClauses: []string{"INHERIT", "LOGIN"}, + }, + }, + }, + kube: &test.MockClient{ + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + secret := corev1.Secret{ + Data: map[string][]byte{}, + } + secret.Data[xpv1.ResourceCredentialsSecretPasswordKey] = []byte("samesame") + secret.DeepCopyInto(obj.(*corev1.Secret)) + return nil + }, + }, + }, + want: want{}, + }, + "NoUpdateQueryNoChangedPrivs": { + reason: "We should not execute an SQL query if privileges are unchanged.", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + return errBoom + }, + }, + }, + args: args{ + mg: &v1alpha1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: "example", + }, + }, + Spec: v1alpha1.RoleSpec{ + ForProvider: v1alpha1.RoleParameters{ + PasswordSecretRef: &common.LocalSecretKeySelector{ + LocalSecretReference: common.LocalSecretReference{ + Name: "connection-secret", + }, + Key: xpv1.ResourceCredentialsSecretPasswordKey, + }, + Privileges: v1alpha1.RolePrivilege{ + Login: ptr.To(true), + Inherit: ptr.To(false), + }, + }, + }, + Status: v1alpha1.RoleStatus{ + AtProvider: v1alpha1.RoleObservation{ + // Observed privileges are the same as requested, + // and in the same order. We should not make any + // query to update privileges. + PrivilegesAsClauses: []string{"NOINHERIT", "LOGIN"}, + }, + }, + }, + kube: &test.MockClient{ + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + secret := corev1.Secret{ + Data: map[string][]byte{}, + } + secret.Data[xpv1.ResourceCredentialsSecretPasswordKey] = []byte("samesame") + secret.DeepCopyInto(obj.(*corev1.Secret)) + return nil + }, + }, + }, + want: want{ + err: nil, + }, + }, + "ErrComparePrivs": { + reason: "We should error if observed privilege list is shorter than desired privilege list", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: "example", + }, + }, + Spec: v1alpha1.RoleSpec{ + ForProvider: v1alpha1.RoleParameters{ + PasswordSecretRef: &common.LocalSecretKeySelector{ + LocalSecretReference: common.LocalSecretReference{ + Name: "connection-secret", + }, + Key: xpv1.ResourceCredentialsSecretPasswordKey, + }, + Privileges: v1alpha1.RolePrivilege{ + Login: ptr.To(true), + Inherit: ptr.To(false), + CreateDb: ptr.To(true), + }, + }, + }, + Status: v1alpha1.RoleStatus{ + AtProvider: v1alpha1.RoleObservation{ + // One privilege field observed but 3 privileges + // to apply. Throw error. + PrivilegesAsClauses: []string{"NOINHERIT"}, + }, + }, + }, + kube: &test.MockClient{ + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + secret := corev1.Secret{ + Data: map[string][]byte{}, + } + secret.Data[xpv1.ResourceCredentialsSecretPasswordKey] = []byte("samesame") + secret.DeepCopyInto(obj.(*corev1.Secret)) + return nil + }, + }, + }, + want: want{ + err: errors.Wrap(errors.New(errComparePrivileges), errUpdateRole), + }, + }, + "UpdateConfigurationParameters": { + reason: "We should set configuration parameters when diff between desired and observed.", + fields: fields{ + db: &mockDB{ + MockExecTx: func(ctx context.Context, q []xsql.Query) error { + crn := pq.QuoteIdentifier("example") + if len(q) != 3 { + return errBoom + } + if q[0].String != fmt.Sprintf("ALTER ROLE %s RESET ALL", crn) { + return errBoom + } + if q[1].String != fmt.Sprintf("ALTER ROLE %s set %s=%s,%s", crn, pq.QuoteIdentifier("search_path"), pq.QuoteLiteral("$user"), pq.QuoteLiteral("public")) { + return errBoom + } + if q[2].String != fmt.Sprintf("ALTER ROLE %s set %s=%s", crn, pq.QuoteIdentifier("pgaudit.log"), pq.QuoteLiteral("All")) { + return errBoom + } + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: "example", + }, + }, + Spec: v1alpha1.RoleSpec{ + ForProvider: v1alpha1.RoleParameters{ + PasswordSecretRef: &common.LocalSecretKeySelector{ + LocalSecretReference: common.LocalSecretReference{ + Name: "connection-secret", + }, + Key: xpv1.ResourceCredentialsSecretPasswordKey, + }, + ConfigurationParameters: &[]v1alpha1.RoleConfigurationParameter{ + { + Name: "search_path", + Value: "\"$user\",public", + }, + { + Name: "pgaudit.log", + Value: "All", + }, + }, + }, + }, + Status: v1alpha1.RoleStatus{ + AtProvider: v1alpha1.RoleObservation{ + ConfigurationParameters: &[]v1alpha1.RoleConfigurationParameter{ + { + Name: "statement_timeout", + Value: "123", + }, + { + Name: "pgaudit.log", + Value: "All", + }, + }, + }, + }, + }, + kube: &test.MockClient{ + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + secret := corev1.Secret{ + Data: map[string][]byte{}, + } + secret.Data[xpv1.ResourceCredentialsSecretPasswordKey] = []byte("samesame") + secret.DeepCopyInto(obj.(*corev1.Secret)) + return nil + }, + }, + }, + want: want{}, + }, + "NoUpdateQueryNoChangedConfigurationParameters": { + reason: "We should not execute an SQL query if configuration parameters are unchanged.", + fields: fields{ + db: &mockDB{ + MockExecTx: func(ctx context.Context, q []xsql.Query) error { + return errBoom + }, + }, + }, + args: args{ + mg: &v1alpha1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: "example", + }, + }, + Spec: v1alpha1.RoleSpec{ + ForProvider: v1alpha1.RoleParameters{ + PasswordSecretRef: &common.LocalSecretKeySelector{ + LocalSecretReference: common.LocalSecretReference{ + Name: "connection-secret", + }, + Key: xpv1.ResourceCredentialsSecretPasswordKey, + }, + ConfigurationParameters: &[]v1alpha1.RoleConfigurationParameter{ + { + Name: "search_path", + Value: "\"$user\",public", + }, + { + Name: "pgaudit.log", + Value: "All", + }, + }, + }, + }, + Status: v1alpha1.RoleStatus{ + AtProvider: v1alpha1.RoleObservation{ + // Observed configuration parameters are the same as requested, + // and in the same order. We should not make any + // query to update configuration parameters. + ConfigurationParameters: &[]v1alpha1.RoleConfigurationParameter{ + { + Name: "search_path", + Value: "\"$user\",public", + }, + { + Name: "pgaudit.log", + Value: "All", + }, + }, + }, + }, + }, + kube: &test.MockClient{ + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + secret := corev1.Secret{ + Data: map[string][]byte{}, + } + secret.Data[xpv1.ResourceCredentialsSecretPasswordKey] = []byte("samesame") + secret.DeepCopyInto(obj.(*corev1.Secret)) + return nil + }, + }, + }, + want: want{ + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{ + db: tc.fields.db, + kube: tc.args.kube, + } + got, err := e.Update(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, cmpopts.IgnoreMapEntries(func(key string, _ []byte) bool { return key == "password" })); diff != "" { + t.Errorf("\n%s\ne.Create(...): -want, +got:\n%s\n", tc.reason, diff) + } + }) + } +} + +func TestDelete(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + cases := map[string]struct { + reason string + fields fields + args args + want error + }{ + "ErrNotRole": { + reason: "An error should be returned if the managed resource is not a *Role", + args: args{ + mg: nil, + }, + want: errors.New(errNotRole), + }, + "ErrDropDB": { + reason: "Errors dropping a role should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + return errBoom + }, + }, + }, + args: args{ + mg: &v1alpha1.Role{}, + }, + want: errors.Wrap(errBoom, errDropRole), + }, + "Success": { + reason: "No error should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Role{}, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + _, 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/pkg/controller/namespaced/postgresql/role/utils.go b/pkg/controller/namespaced/postgresql/role/utils.go new file mode 100644 index 00000000..4eb7cd5e --- /dev/null +++ b/pkg/controller/namespaced/postgresql/role/utils.go @@ -0,0 +1,66 @@ +/* +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 role + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + + "github.com/pkg/errors" + + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + "github.com/crossplane-contrib/provider-sql/apis/namespaced/postgresql/v1alpha1" +) + +func (c *external) getPassword(ctx context.Context, role *v1alpha1.Role) (newPwd string, changed bool, err error) { + if role.Spec.ForProvider.PasswordSecretRef == nil { + return "", false, nil + } + nn := types.NamespacedName{ + Name: role.Spec.ForProvider.PasswordSecretRef.Name, + Namespace: role.Namespace, + } + s := &corev1.Secret{} + if err := c.kube.Get(ctx, nn, s); err != nil { + return "", false, errors.Wrap(err, errGetPasswordSecretFailed) + } + newPwd = string(s.Data[role.Spec.ForProvider.PasswordSecretRef.Key]) + + if role.Spec.WriteConnectionSecretToReference == nil { + return newPwd, false, nil + } + + nn = types.NamespacedName{ + Name: role.Spec.WriteConnectionSecretToReference.Name, + Namespace: role.Namespace, + } + s = &corev1.Secret{} + // the output secret may not exist yet, so we can skip returning an + // error if the error is NotFound + if err := c.kube.Get(ctx, nn, s); resource.IgnoreNotFound(err) != nil { + return "", false, err + } + // if newPwd was set to some value, compare value in output secret with + // newPwd + changed = newPwd != "" && newPwd != string(s.Data[xpv1.ResourceCredentialsSecretPasswordKey]) + + return newPwd, changed, nil +} diff --git a/pkg/controller/namespaced/postgresql/schema/reconciler.go b/pkg/controller/namespaced/postgresql/schema/reconciler.go new file mode 100644 index 00000000..eb36bc19 --- /dev/null +++ b/pkg/controller/namespaced/postgresql/schema/reconciler.go @@ -0,0 +1,277 @@ +/* +Copyright 2024 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 schema + +import ( + "context" + "strings" + + "github.com/lib/pq" + "github.com/pkg/errors" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/feature" + "github.com/crossplane/crossplane-runtime/v2/pkg/meta" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + + namespacedv1alpha1 "github.com/crossplane-contrib/provider-sql/apis/namespaced/postgresql/v1alpha1" + "github.com/crossplane-contrib/provider-sql/pkg/clients" + "github.com/crossplane-contrib/provider-sql/pkg/clients/postgresql" + "github.com/crossplane-contrib/provider-sql/pkg/clients/xsql" + "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/postgresql/provider" +) + +const ( + errTrackPCUsage = "cannot track ProviderConfig usage" + + errNotSchema = "managed resource is not a Schema custom resource" + errSelectSchema = "cannot select schema" + errCreateSchema = "cannot create schema" + errDropSchema = "cannot drop schema" + errNoDatabase = "database must be specified" + errAlterSchema = "cannot alter schema" + + maxConcurrency = 5 +) + +// TODO(nateinaction): This looks wrong, can tracker creation be improved? +type tracker struct { + tracker *resource.ProviderConfigUsageTracker +} + +var _ resource.Tracker = &tracker{} + +func (t *tracker) Track(ctx context.Context, mg resource.Managed) error { + return t.tracker.Track(ctx, mg.(resource.ModernManaged)) +} + +// Setup adds a controller that reconciles Database managed resources. +func Setup(mgr ctrl.Manager, o xpcontroller.Options) error { + name := managed.ControllerName(namespacedv1alpha1.SchemaGroupKind) + + t := resource.NewProviderConfigUsageTracker(mgr.GetClient(), &namespacedv1alpha1.ProviderConfigUsage{}) + trk := &tracker{tracker: t} + + reconcilerOptions := []managed.ReconcilerOption{ + managed.WithTypedExternalConnector(&connector{kube: mgr.GetClient(), usage: trk, newDB: postgresql.New}), + managed.WithLogger(o.Logger.WithValues("controller", name)), + managed.WithPollInterval(o.PollInterval), + managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), + } + if o.Features.Enabled(feature.EnableBetaManagementPolicies) { + reconcilerOptions = append(reconcilerOptions, managed.WithManagementPolicies()) + } + r := managed.NewReconciler(mgr, + resource.ManagedKind(namespacedv1alpha1.SchemaGroupVersionKind), + reconcilerOptions..., + ) + return ctrl.NewControllerManagedBy(mgr). + Named(name). + For(&namespacedv1alpha1.Schema{}). + WithOptions(controller.Options{ + MaxConcurrentReconciles: maxConcurrency, + }). + Complete(r) +} + +var _ managed.TypedExternalConnector[resource.Managed] = &connector{} + +type connector struct { + kube client.Client + usage resource.Tracker + newDB func(creds map[string][]byte, database string, sslmode string) xsql.DB +} + +func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.TypedExternalClient[resource.Managed], error) { + cr, ok := mg.(*namespacedv1alpha1.Schema) + if !ok { + return nil, errors.New(errNotSchema) + } + + if err := c.usage.Track(ctx, mg); err != nil { + return nil, errors.Wrap(err, errTrackPCUsage) + } + + // ProviderConfigReference could theoretically be nil, but in practice the + // DefaultProviderConfig initializer will set it before we get here. + providerInfo, err := provider.GetProviderConfig(ctx, c.kube, cr) + if err != nil { + return nil, err + } + + if cr.Spec.ForProvider.Database == nil { + return nil, errors.New(errNoDatabase) + } + + return &external{db: c.newDB(providerInfo.SecretData, *cr.Spec.ForProvider.Database, clients.ToString(providerInfo.SSLMode))}, nil +} + +var _ managed.TypedExternalClient[resource.Managed] = &external{} + +type external struct{ db xsql.DB } + +func (c *external) Disconnect(ctx context.Context) error { + return nil +} + +func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { + cr, ok := mg.(*namespacedv1alpha1.Schema) + if !ok { + return managed.ExternalObservation{}, errors.New(errNotSchema) + } + + // If the Schema exists, it will have all of these properties. + observed := namespacedv1alpha1.SchemaParameters{ + Role: new(string), + } + + query := "SELECT rolname FROM pg_catalog.pg_namespace JOIN pg_catalog.pg_roles ON (nspowner=pg_roles.oid) where nspname = $1" + + err := c.db.Scan(ctx, xsql.Query{ + String: query, + Parameters: []interface{}{meta.GetExternalName(cr)}, + }, + observed.Role, + ) + + // If the database we try to connect on does not exist then + // there cannot be an schema in that database either. + if xsql.IsNoRows(err) || postgresql.IsInvalidCatalog(err) { + return managed.ExternalObservation{ResourceExists: false}, nil + } + if err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, errSelectSchema) + } + + cr.SetConditions(xpv1.Available()) + + return managed.ExternalObservation{ + ResourceExists: true, + ResourceLateInitialized: lateInit(observed, &cr.Spec.ForProvider), + ResourceUpToDate: upToDate(observed, cr.Spec.ForProvider), + }, nil +} + +func (c *external) Create(ctx context.Context, mg resource.Managed) (managed.ExternalCreation, error) { //nolint:gocyclo + cr, ok := mg.(*namespacedv1alpha1.Schema) + if !ok { + return managed.ExternalCreation{}, errors.New(errNotSchema) + } + + var queries []xsql.Query + + cr.SetConditions(xpv1.Creating()) + + createSchemaQueries(cr.Spec.ForProvider, &queries, meta.GetExternalName(cr)) + + err := c.db.ExecTx(ctx, queries) + return managed.ExternalCreation{}, errors.Wrap(err, errCreateSchema) +} + +func (c *external) Update(ctx context.Context, mg resource.Managed) (managed.ExternalUpdate, error) { //nolint:gocyclo + cr, ok := mg.(*namespacedv1alpha1.Schema) + if !ok { + return managed.ExternalUpdate{}, errors.New(errNotSchema) + } + + if cr.Spec.ForProvider.Role == nil { + return managed.ExternalUpdate{}, nil + } + + var queries []xsql.Query + updateSchemaQueries(cr.Spec.ForProvider, &queries, meta.GetExternalName(cr)) + + err := c.db.ExecTx(ctx, queries) + return managed.ExternalUpdate{}, errors.Wrap(err, errAlterSchema) +} + +func (c *external) Delete(ctx context.Context, mg resource.Managed) (managed.ExternalDelete, error) { + cr, ok := mg.(*namespacedv1alpha1.Schema) + if !ok { + return managed.ExternalDelete{}, errors.New(errNotSchema) + } + + err := c.db.Exec(ctx, xsql.Query{String: "DROP SCHEMA IF EXISTS " + pq.QuoteIdentifier(meta.GetExternalName(cr))}) + return managed.ExternalDelete{}, errors.Wrap(err, errDropSchema) +} + +func upToDate(observed, desired namespacedv1alpha1.SchemaParameters) bool { + if desired.Role == nil || (observed.Role != nil && *desired.Role == *observed.Role) { + return true + } + return false +} + +func lateInit(observed namespacedv1alpha1.SchemaParameters, desired *namespacedv1alpha1.SchemaParameters) bool { + li := false + + if desired.Role == nil && observed.Role != nil { + desired.Role = observed.Role + li = true + } + + return li +} + +func createSchemaQueries(sp namespacedv1alpha1.SchemaParameters, ql *[]xsql.Query, en string) { // nolint: gocyclo + + var b strings.Builder + b.WriteString("CREATE SCHEMA IF NOT EXISTS ") + b.WriteString(pq.QuoteIdentifier(en)) + + if sp.Role != nil { + b.WriteString(" AUTHORIZATION ") + b.WriteString(pq.QuoteIdentifier(*sp.Role)) + b.WriteString(";") + } + + *ql = append(*ql, + xsql.Query{String: b.String()}, + ) + + if sp.RevokePublicOnSchema != nil && *sp.RevokePublicOnSchema { + *ql = append(*ql, + xsql.Query{String: "REVOKE ALL ON SCHEMA PUBLIC FROM PUBLIC;"}, + ) + } + +} + +func updateSchemaQueries(sp namespacedv1alpha1.SchemaParameters, ql *[]xsql.Query, en string) { // nolint: gocyclo + + var b strings.Builder + b.WriteString("ALTER SCHEMA ") + b.WriteString(pq.QuoteIdentifier(en)) + b.WriteString(" OWNER TO ") + b.WriteString(pq.QuoteIdentifier(*sp.Role)) + + *ql = append(*ql, + xsql.Query{String: b.String()}, + ) + + if sp.RevokePublicOnSchema != nil && *sp.RevokePublicOnSchema { + *ql = append(*ql, + xsql.Query{String: "REVOKE ALL ON SCHEMA PUBLIC FROM PUBLIC;"}, + ) + } +} diff --git a/pkg/controller/namespaced/postgresql/schema/reconciler_test.go b/pkg/controller/namespaced/postgresql/schema/reconciler_test.go new file mode 100644 index 00000000..3932173b --- /dev/null +++ b/pkg/controller/namespaced/postgresql/schema/reconciler_test.go @@ -0,0 +1,609 @@ +/* +Copyright 2024 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 schema + +import ( + "context" + "database/sql" + "testing" + + "github.com/crossplane-contrib/provider-sql/apis/namespaced/postgresql/v1alpha1" + "github.com/google/go-cmp/cmp" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/crossplane/crossplane-runtime/v2/apis/common" + xpv2 "github.com/crossplane/crossplane-runtime/v2/apis/common/v2" + "github.com/crossplane/crossplane-runtime/v2/pkg/meta" + "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/crossplane-contrib/provider-sql/pkg/clients/xsql" + provErrors "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/errors" +) + +type mockDB struct { + MockExec func(ctx context.Context, q xsql.Query) error + MockExecTx func(ctx context.Context, ql []xsql.Query) error + MockScan func(ctx context.Context, q xsql.Query, dest ...interface{}) error + MockGetConnectionDetails func(username, password string) managed.ConnectionDetails +} + +func (m mockDB) Exec(ctx context.Context, q xsql.Query) error { + return m.MockExec(ctx, q) +} + +func (m mockDB) ExecTx(ctx context.Context, ql []xsql.Query) error { + return m.MockExecTx(ctx, ql) +} + +func (m mockDB) Scan(ctx context.Context, q xsql.Query, dest ...interface{}) error { + return m.MockScan(ctx, q, dest...) +} + +func (m mockDB) Query(ctx context.Context, q xsql.Query) (*sql.Rows, error) { + return &sql.Rows{}, nil +} + +func (m mockDB) GetConnectionDetails(username, password string) managed.ConnectionDetails { + return m.MockGetConnectionDetails(username, password) +} + +func TestConnect(t *testing.T) { + errBoom := errors.New("boom") + + type fields struct { + kube client.Client + usage resource.Tracker + newDB func(creds map[string][]byte, database string, sslmode string) xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + cr := v1alpha1.Schema{} + meta.SetExternalName(&cr, "cool") + + cases := map[string]struct { + reason string + fields fields + args args + want error + }{ + "ErrNotSchema": { + reason: "An error should be returned if the managed resource is not a Schema", + args: args{ + mg: nil, + }, + want: errors.New(errNotSchema), + }, + "ErrTrackProviderConfigUsage": { + reason: "An error should be returned if we can't track our ProviderConfig usage", + fields: fields{ + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return errBoom }), + }, + args: args{ + mg: &v1alpha1.Schema{}, + }, + want: errors.Wrap(errBoom, errTrackPCUsage), + }, + "InvalideProviderConfigKind": { + reason: "An error should be returned if our ProviderConfig kind is invalid", + fields: fields{ + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Schema{ + Spec: v1alpha1.SchemaSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{Kind: "Invalid"}, + }, + }, + }, + }, + want: provErrors.InvalidProviderConfigKindError("Invalid"), + }, + "ErrGetProviderConfig": { + reason: "An error should be returned if we can't get our ProviderConfig", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(errBoom), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Schema{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.SchemaSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetProviderConfigError(errBoom), + }, + "ErrGetClusterProviderConfig": { + reason: "An error should be returned if we can't get our ClusterProviderConfig", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(errBoom), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Schema{ + Spec: v1alpha1.SchemaSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ClusterProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetClusterProviderConfigError(errBoom), + }, + "ErrMissingConnectionSecret": { + reason: "An error should be returned if our ProviderConfig doesn't specify a connection secret", + fields: fields{ + kube: &test.MockClient{ + // We call get to populate the Role struct, then again + // to populate the (empty) ProviderConfig struct, resulting + // in a ProviderConfig with a nil connection secret. + MockGet: test.NewMockGetFn(nil), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Schema{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.SchemaSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.MissingSecretRefError(), + }, + "ErrGetConnectionSecret": { + reason: "An error should be returned if we can't get our ProviderConfig's connection secret", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + switch o := obj.(type) { + case *v1alpha1.ProviderConfig: + o.Spec.Credentials.ConnectionSecretRef = common.LocalSecretReference{Name: "example"} + case *corev1.Secret: + return errBoom + } + return nil + }), + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + }, + args: args{ + mg: &v1alpha1.Schema{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha1.SchemaSpec{ + ManagedResourceSpec: xpv2.ManagedResourceSpec{ + ProviderConfigReference: &common.ProviderConfigReference{ + Kind: v1alpha1.ProviderConfigKind, + Name: "example", + }, + }, + }, + }, + }, + want: provErrors.GetSecretError(errBoom), + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := &connector{kube: tc.fields.kube, usage: tc.fields.usage, newDB: tc.fields.newDB} + _, err := e.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") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + o managed.ExternalObservation + err error + } + + cr := v1alpha1.Schema{} + meta.SetExternalName(&cr, "cool") + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotScema": { + reason: "An error should be returned if the managed resource is not a Schema", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotSchema), + }, + }, + "ErrNoSchema": { + reason: "We should return ResourceExists: false when no schema is found", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return sql.ErrNoRows }, + }, + }, + args: args{ + mg: &v1alpha1.Schema{}, + }, + want: want{ + o: managed.ExternalObservation{ResourceExists: false}, + }, + }, + "ErrSelectSchema": { + reason: "We should return any errors encountered while trying to select the schema", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { return errBoom }, + }, + }, + args: args{ + mg: &v1alpha1.Schema{ + ObjectMeta: cr.ObjectMeta, + Spec: v1alpha1.SchemaSpec{ + ForProvider: v1alpha1.SchemaParameters{ + Database: ptr.To("db"), + }, + }, + }, + }, + want: want{ + err: errors.Wrap(errBoom, errSelectSchema), + }, + }, + "Success": { + reason: "We should return no error if we can successfully select our schema", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { + bv := dest[0].(*string) + *bv = "role" + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Schema{ + ObjectMeta: cr.ObjectMeta, + Spec: v1alpha1.SchemaSpec{ + ForProvider: v1alpha1.SchemaParameters{ + Database: ptr.To("db"), + Role: ptr.To("role"), + }, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + ResourceLateInitialized: false, + }, + err: nil, + }, + }, + "SuccessLateInit": { + reason: "No error should be returned via lateInit when role is provided", + fields: fields{ + db: mockDB{ + MockScan: func(ctx context.Context, q xsql.Query, dest ...interface{}) error { + bv := dest[0].(*string) + *bv = "blah" + return nil + }, + }, + }, + args: args{ + mg: &v1alpha1.Schema{ + ObjectMeta: cr.ObjectMeta, + Spec: v1alpha1.SchemaSpec{ + ForProvider: v1alpha1.SchemaParameters{}, + }, + }, + }, + want: want{ + o: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + ResourceLateInitialized: true, + }, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + 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) + } + }) + } +} + +func TestCreate(t *testing.T) { + errBoom := errors.New("boom") + + cr := v1alpha1.Schema{} + meta.SetExternalName(&cr, "cool") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + c managed.ExternalCreation + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotSchema": { + reason: "An error should be returned if the managed resource is not a Schema", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotSchema), + }, + }, + "ErrExec": { + reason: "Any errors encountered while creating the schema should be returned", + fields: fields{ + db: &mockDB{ + MockExecTx: func(ctx context.Context, ql []xsql.Query) error { return errBoom }, + }, + }, + args: args{ + mg: &v1alpha1.Schema{ + ObjectMeta: cr.ObjectMeta, + }, + }, + want: want{ + err: errors.Wrap(errBoom, errCreateSchema), + }, + }, + "Success": { + reason: "No error should be returned when we successfully create a extension", + fields: fields{ + db: &mockDB{ + MockExecTx: func(ctx context.Context, ql []xsql.Query) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.Schema{ + ObjectMeta: cr.ObjectMeta, + Spec: v1alpha1.SchemaSpec{ + ForProvider: v1alpha1.SchemaParameters{ + Database: ptr.To("db"), + }, + }, + }, + }, + want: want{ + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + 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) + } + }) + } +} + +func TestUpdate(t *testing.T) { + cr := v1alpha1.Schema{} + meta.SetExternalName(&cr, "cool") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + type want struct { + u managed.ExternalUpdate + err error + } + + cases := map[string]struct { + reason string + fields fields + args args + want want + }{ + "ErrNotSchema": { + reason: "An error should be returned if the managed resource is not a Schema", + args: args{ + mg: nil, + }, + want: want{ + err: errors.New(errNotSchema), + }, + }, + "Success": { + reason: "No error should be returned when we successfully update a schema", + fields: fields{ + db: &mockDB{ + MockExecTx: func(ctx context.Context, ql []xsql.Query) error { return nil }, + }, + }, + args: args{ + mg: &v1alpha1.Schema{ + ObjectMeta: cr.ObjectMeta, + Spec: v1alpha1.SchemaSpec{ + ForProvider: v1alpha1.SchemaParameters{ + Database: ptr.To("db"), + }, + }, + }, + }, + want: want{ + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + got, err := e.Update(tc.args.ctx, tc.args.mg) + if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { + t.Errorf("\n%s\ne.Update(...): -want error, +got error:\n%s\n", tc.reason, diff) + } + if diff := cmp.Diff(tc.want.u, got); diff != "" { + t.Errorf("\n%s\ne.Update(...): -want, +got:\n%s\n", tc.reason, diff) + } + }) + } +} + +func TestDelete(t *testing.T) { + cr := v1alpha1.Schema{} + meta.SetExternalName(&cr, "cool") + + errBoom := errors.New("boom") + + type fields struct { + db xsql.DB + } + + type args struct { + ctx context.Context + mg resource.Managed + } + + cases := map[string]struct { + reason string + fields fields + args args + want error + }{ + "ErrNotSchema": { + reason: "An error should be returned if the managed resource is not a Schema", + args: args{ + mg: nil, + }, + want: errors.New(errNotSchema), + }, + "ErrDropSchema": { + reason: "Errors dropping a schema should be returned", + fields: fields{ + db: &mockDB{ + MockExec: func(ctx context.Context, q xsql.Query) error { + return errBoom + }, + }, + }, + args: args{ + mg: &v1alpha1.Schema{ + ObjectMeta: cr.ObjectMeta, + Spec: v1alpha1.SchemaSpec{ + ForProvider: v1alpha1.SchemaParameters{ + Database: ptr.To("db"), + }, + }, + }, + }, + want: errors.Wrap(errBoom, errDropSchema), + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := external{db: tc.fields.db} + _, 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/pkg/controller/sql.go b/pkg/controller/sql.go index cca80839..2f4c7d51 100644 --- a/pkg/controller/sql.go +++ b/pkg/controller/sql.go @@ -19,20 +19,26 @@ package controller import ( ctrl "sigs.k8s.io/controller-runtime" - "github.com/crossplane/crossplane-runtime/pkg/controller" - - "github.com/crossplane-contrib/provider-sql/pkg/controller/mssql" - "github.com/crossplane-contrib/provider-sql/pkg/controller/mysql" - "github.com/crossplane-contrib/provider-sql/pkg/controller/postgresql" + "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + + clustermssql "github.com/crossplane-contrib/provider-sql/pkg/controller/cluster/mssql" + clustermysql "github.com/crossplane-contrib/provider-sql/pkg/controller/cluster/mysql" + clusterpostgresql "github.com/crossplane-contrib/provider-sql/pkg/controller/cluster/postgresql" + namespacedmssql "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/mssql" + namespacedmysql "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/mysql" + namespacedpostgresql "github.com/crossplane-contrib/provider-sql/pkg/controller/namespaced/postgresql" ) -// Setup creates all PostgreSQL controllers with the supplied logger and adds +// Setup creates all controllers with the supplied logger and adds // them to the supplied manager. func Setup(mgr ctrl.Manager, l controller.Options) error { for _, setup := range []func(ctrl.Manager, controller.Options) error{ - mssql.Setup, - mysql.Setup, - postgresql.Setup, + clustermssql.Setup, + clustermysql.Setup, + clusterpostgresql.Setup, + namespacedmssql.Setup, + namespacedmysql.Setup, + namespacedpostgresql.Setup, } { if err := setup(mgr, l); err != nil { return err