Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 53 additions & 1 deletion cluster/local/integration_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,17 @@ setup_crossplane() {
echo
# we replace empty dir with our PVC so that the /cache dir in the kind node
# container is exposed to the crossplane pod
"${HELM}" install crossplane --namespace crossplane-system crossplane-stable/crossplane --version ${chart_version} --wait --set packageCache.pvc=package-cache
# Disable the default ManagedResourceActivationPolicy that activates all resources,
# so we can test safe-start with selective activation policies.
"${HELM}" install crossplane --namespace crossplane-system crossplane-stable/crossplane --version ${chart_version} --wait --set packageCache.pvc=package-cache --set 'provider.defaultActivations={}'
}

setup_provider() {
echo_step "installing provider"

echo_sub_step "applying ManagedResourceActivationPolicy to disable cluster-wide MSSQL"
"${KUBECTL}" apply -f "${projectdir}/examples/activation-policy-no-cluster-mssql.yaml"

local yaml="$( cat <<EOF
apiVersion: pkg.crossplane.io/v1beta1
kind: DeploymentRuntimeConfig
Expand Down Expand Up @@ -220,13 +225,33 @@ EOF

echo_step "waiting for provider to be installed"
"${KUBECTL}" wait "provider.pkg.crossplane.io/${PACKAGE_NAME}" --for=condition=healthy --timeout=60s

echo_step "verifying cluster-wide MSSQL CRDs are NOT installed"
if "${KUBECTL}" get crd databases.mssql.sql.crossplane.io 2>/dev/null; then
echo_error "cluster-wide MSSQL Database CRD should not be installed"
fi
if "${KUBECTL}" get crd grants.mssql.sql.crossplane.io 2>/dev/null; then
echo_error "cluster-wide MSSQL Grant CRD should not be installed"
fi
if "${KUBECTL}" get crd users.mssql.sql.crossplane.io 2>/dev/null; then
echo_error "cluster-wide MSSQL User CRD should not be installed"
fi
echo_step_completed

echo_step "verifying namespaced MSSQL CRDs ARE installed"
"${KUBECTL}" get crd databases.mssql.sql.m.crossplane.io || echo_error "namespaced MSSQL Database CRD should be installed"
"${KUBECTL}" get crd grants.mssql.sql.m.crossplane.io || echo_error "namespaced MSSQL Grant CRD should be installed"
"${KUBECTL}" get crd users.mssql.sql.m.crossplane.io || echo_error "namespaced MSSQL User CRD should be installed"
echo_step_completed
}

cleanup_provider() {
echo_step "uninstalling provider"

"${KUBECTL}" delete provider.pkg.crossplane.io "${PACKAGE_NAME}"
"${KUBECTL}" delete deploymentruntimeconfig.pkg.crossplane.io debug-config
"${KUBECTL}" delete managedresourceactivationpolicy.apiextensions.crossplane.io disable-cluster-mssql --ignore-not-found=true
"${KUBECTL}" delete managedresourceactivationpolicy.apiextensions.crossplane.io enable-cluster-mssql --ignore-not-found=true

echo_step "waiting for provider pods to be deleted"
timeout=60
Expand Down Expand Up @@ -459,6 +484,33 @@ TLS=false API_TYPE="cluster" run_test integration_tests_postgres
TLS=false API_TYPE="namespaced" run_test integration_tests_postgres

# no TLS=false variant - MSSQL uses built-in encryption
# Enable cluster-wide MSSQL CRDs before running cluster MSSQL tests.
# The initial activation policy excludes them to test safe-start.
echo_step "creating activation policy to enable cluster-wide MSSQL"
"${KUBECTL}" apply -f - <<EOF
apiVersion: apiextensions.crossplane.io/v1alpha1
kind: ManagedResourceActivationPolicy
metadata:
name: enable-cluster-mssql
spec:
activate:
- "*.mssql.sql.crossplane.io"
EOF

echo_step "waiting for cluster-wide MSSQL CRDs to be installed"
timeout=60
current=0
step=3
while ! "${KUBECTL}" get crd databases.mssql.sql.crossplane.io 2>/dev/null; do
echo "waiting another $step seconds for MSSQL CRDs..."
current=$((current + step))
if [[ $current -ge $timeout ]]; then
echo_error "timeout of ${timeout}s waiting for MSSQL CRDs"
fi
sleep $step
done
echo_step_completed

TLS=true API_TYPE="cluster" run_test integration_tests_mssql
TLS=true API_TYPE="namespaced" run_test integration_tests_mssql

Expand Down
53 changes: 52 additions & 1 deletion cmd/provider/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package main

import (
"context"
"os"
"path/filepath"

Expand All @@ -25,19 +26,51 @@ import (
_ "github.com/lib/pq"

"github.com/alecthomas/kingpin/v2"
authv1 "k8s.io/api/authorization/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log/zap"

xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller"
"github.com/crossplane/crossplane-runtime/v2/pkg/feature"
"github.com/crossplane/crossplane-runtime/v2/pkg/gate"
"github.com/crossplane/crossplane-runtime/v2/pkg/logging"
"github.com/crossplane/crossplane-runtime/v2/pkg/ratelimiter"
"github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/customresourcesgate"

"github.com/crossplane-contrib/provider-sql/apis"
"github.com/crossplane-contrib/provider-sql/pkg/controller"
)

// canWatchCRD checks if the provider has the necessary RBAC permissions to watch CustomResourceDefinitions.
// This is used to determine if we can use the SafeStart pattern with gated controller initialization.
func canWatchCRD(ctx context.Context, c client.Client) (bool, error) {
for _, verb := range []string{"get", "list", "watch"} {
review := &authv1.SelfSubjectAccessReview{
Spec: authv1.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authv1.ResourceAttributes{
Group: apiextensionsv1.GroupName,
Resource: "customresourcedefinitions",
Verb: verb,
},
},
}

if err := c.Create(ctx, review); err != nil {
return false, err
}

if !review.Status.Allowed {
return false, nil
}
}

return true, nil
}

func main() {
var (
app = kingpin.New(filepath.Base(os.Args[0]), "SQL support for Crossplane.").DefaultEnvars()
Expand Down Expand Up @@ -73,6 +106,7 @@ func main() {
})
kingpin.FatalIfError(err, "Cannot create controller manager")
kingpin.FatalIfError(apis.AddToScheme(mgr.GetScheme()), "Cannot add SQL APIs to scheme")
kingpin.FatalIfError(apiextensionsv1.AddToScheme(mgr.GetScheme()), "Cannot add CRD types to scheme")

o := xpcontroller.Options{
Logger: log,
Expand All @@ -86,6 +120,23 @@ func main() {
log.Info("Beta feature enabled", "flag", feature.EnableBetaManagementPolicies)
}

kingpin.FatalIfError(controller.Setup(mgr, o), "Cannot setup SQL controllers")
// Check if we have permission to watch CRDs for SafeStart support
ctx := context.Background()
canWatch, err := canWatchCRD(ctx, mgr.GetClient())
switch {
case err != nil:
log.Info("Failed to check CRD watch permissions, using immediate controller setup", "error", err)
kingpin.FatalIfError(controller.Setup(mgr, o), "Cannot setup SQL controllers")
case canWatch:
log.Info("SafeStart enabled: using gated controller initialization")
o.Gate = new(gate.Gate[schema.GroupVersionKind])

kingpin.FatalIfError(customresourcesgate.Setup(mgr, o), "Cannot setup CRD gate")
kingpin.FatalIfError(controller.SetupGated(mgr, o), "Cannot setup SQL controllers")
default:
log.Info("SafeStart disabled: insufficient CRD watch permissions, using immediate controller setup")
kingpin.FatalIfError(controller.Setup(mgr, o), "Cannot setup SQL controllers")
}

kingpin.FatalIfError(mgr.Start(ctrl.SetupSignalHandler()), "Cannot start controller manager")
}
14 changes: 14 additions & 0 deletions examples/activation-policy-no-cluster-mssql.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apiVersion: apiextensions.crossplane.io/v1alpha1
kind: ManagedResourceActivationPolicy
metadata:
name: disable-cluster-mssql
spec:
activate:
# Activate all MySQL resources (both cluster and namespaced)
- "*.mysql.sql.crossplane.io"
- "*.mysql.sql.m.crossplane.io"
# Activate all PostgreSQL resources (both cluster and namespaced)
- "*.postgresql.sql.crossplane.io"
- "*.postgresql.sql.m.crossplane.io"
# Activate only namespaced MSSQL resources (cluster-wide MSSQL is excluded)
- "*.mssql.sql.m.crossplane.io"
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
github.com/lib/pq v1.11.2
github.com/pkg/errors v0.9.1
k8s.io/api v0.34.0
k8s.io/apiextensions-apiserver v0.34.0
k8s.io/apimachinery v0.34.0
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
sigs.k8s.io/controller-runtime v0.22.0
Expand Down Expand Up @@ -100,7 +101,6 @@ require (
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.34.0 // indirect
k8s.io/client-go v0.34.0 // indirect
k8s.io/code-generator v0.34.0 // indirect
k8s.io/component-base v0.34.0 // indirect
Expand Down
3 changes: 3 additions & 0 deletions package/crossplane.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ metadata:
friendly-kind-name.meta.crossplane.io/role.postgresql.sql.crossplane.io: Role
friendly-kind-name.meta.crossplane.io/user.mysql.sql.crossplane.io: User
friendly-kind-name.meta.crossplane.io/defaultprivileges.postgresql.sql.crossplane.io: DefaultPrivileges
spec:
capabilities:
- safe-start
6 changes: 6 additions & 0 deletions pkg/controller/cluster/mssql/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,9 @@ func Setup(mgr ctrl.Manager, o controller.Options) error {
providerconfig.WithLogger(o.Logger.WithValues("controller", name)),
providerconfig.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name)))))
}

// SetupGated adds a controller that reconciles ProviderConfigs.
// ProviderConfig resources are always available, so this is equivalent to Setup.
func SetupGated(mgr ctrl.Manager, o controller.Options) error {
return Setup(mgr, o)
}
11 changes: 11 additions & 0 deletions pkg/controller/cluster/mssql/database/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,17 @@ func Setup(mgr ctrl.Manager, o xpcontroller.Options) error {
Complete(r)
}

// SetupGated adds a controller that reconciles Database managed resources
// with gated initialization, waiting for the resource's CRD to be available.
func SetupGated(mgr ctrl.Manager, o xpcontroller.Options) error {
o.Gate.Register(func() {
if err := Setup(mgr, o); err != nil {
mgr.GetLogger().Error(err, "unable to setup controller", "gvk", clusterv1alpha1.DatabaseGroupVersionKind)
}
}, clusterv1alpha1.DatabaseGroupVersionKind)
return nil
}

type connector struct {
kube client.Client
track func(ctx context.Context, mg resource.LegacyManaged) error
Expand Down
11 changes: 11 additions & 0 deletions pkg/controller/cluster/mssql/grant/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,17 @@ func Setup(mgr ctrl.Manager, o xpcontroller.Options) error {
Complete(r)
}

// SetupGated adds a controller that reconciles Grant managed resources
// with gated initialization, waiting for the resource's CRD to be available.
func SetupGated(mgr ctrl.Manager, o xpcontroller.Options) error {
o.Gate.Register(func() {
if err := Setup(mgr, o); err != nil {
mgr.GetLogger().Error(err, "unable to setup controller", "gvk", v1alpha1.GrantGroupVersionKind)
}
}, v1alpha1.GrantGroupVersionKind)
return nil
}

type connector struct {
kube client.Client
track func(ctx context.Context, mg resource.LegacyManaged) error
Expand Down
22 changes: 19 additions & 3 deletions pkg/controller/cluster/mssql/mssql.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ package mssql
import (
ctrl "sigs.k8s.io/controller-runtime"

"github.com/crossplane/crossplane-runtime/v2/pkg/controller"
xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller"

"github.com/crossplane-contrib/provider-sql/pkg/controller/cluster/mssql/config"
"github.com/crossplane-contrib/provider-sql/pkg/controller/cluster/mssql/database"
Expand All @@ -29,8 +29,8 @@ import (

// 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{
func Setup(mgr ctrl.Manager, o xpcontroller.Options) error {
for _, setup := range []func(ctrl.Manager, xpcontroller.Options) error{
config.Setup,
database.Setup,
user.Setup,
Expand All @@ -42,3 +42,19 @@ func Setup(mgr ctrl.Manager, o controller.Options) error {
}
return nil
}

// SetupGated creates all MSSQL controllers with gated initialization,
// waiting for their required CRDs to be available before starting.
func SetupGated(mgr ctrl.Manager, o xpcontroller.Options) error {
for _, setup := range []func(ctrl.Manager, xpcontroller.Options) error{
config.SetupGated,
database.SetupGated,
user.SetupGated,
grant.SetupGated,
} {
if err := setup(mgr, o); err != nil {
return err
}
}
return nil
}
11 changes: 11 additions & 0 deletions pkg/controller/cluster/mssql/user/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,17 @@ func Setup(mgr ctrl.Manager, o xpcontroller.Options) error {
Complete(r)
}

// SetupGated adds a controller that reconciles User managed resources
// with gated initialization, waiting for the resource's CRD to be available.
func SetupGated(mgr ctrl.Manager, o xpcontroller.Options) error {
o.Gate.Register(func() {
if err := Setup(mgr, o); err != nil {
mgr.GetLogger().Error(err, "unable to setup controller", "gvk", v1alpha1.UserGroupVersionKind)
}
}, v1alpha1.UserGroupVersionKind)
return nil
}

type connector struct {
kube client.Client
track func(ctx context.Context, mg resource.LegacyManaged) error
Expand Down
6 changes: 6 additions & 0 deletions pkg/controller/cluster/mysql/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,9 @@ func Setup(mgr ctrl.Manager, o controller.Options) error {
providerconfig.WithLogger(o.Logger.WithValues("controller", name)),
providerconfig.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name)))))
}

// SetupGated adds a controller that reconciles ProviderConfigs.
// ProviderConfig resources are always available, so this is equivalent to Setup.
func SetupGated(mgr ctrl.Manager, o controller.Options) error {
return Setup(mgr, o)
}
11 changes: 11 additions & 0 deletions pkg/controller/cluster/mysql/database/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,17 @@ func Setup(mgr ctrl.Manager, o xpcontroller.Options) error {
Complete(r)
}

// SetupGated adds a controller that reconciles Database managed resources
// with gated initialization, waiting for the resource's CRD to be available.
func SetupGated(mgr ctrl.Manager, o xpcontroller.Options) error {
o.Gate.Register(func() {
if err := Setup(mgr, o); err != nil {
mgr.GetLogger().Error(err, "unable to setup controller", "gvk", v1alpha1.DatabaseGroupVersionKind)
}
}, v1alpha1.DatabaseGroupVersionKind)
return nil
}

type connector struct {
kube client.Client
track func(ctx context.Context, mg resource.LegacyManaged) error
Expand Down
11 changes: 11 additions & 0 deletions pkg/controller/cluster/mysql/grant/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,17 @@ func Setup(mgr ctrl.Manager, o xpcontroller.Options) error {
Complete(r)
}

// SetupGated adds a controller that reconciles Grant managed resources
// with gated initialization, waiting for the resource's CRD to be available.
func SetupGated(mgr ctrl.Manager, o xpcontroller.Options) error {
o.Gate.Register(func() {
if err := Setup(mgr, o); err != nil {
mgr.GetLogger().Error(err, "unable to setup controller", "gvk", v1alpha1.GrantGroupVersionKind)
}
}, v1alpha1.GrantGroupVersionKind)
return nil
}

type connector struct {
kube client.Client
track func(ctx context.Context, mg resource.LegacyManaged) error
Expand Down
Loading
Loading