Skip to content

Commit 6fa7e9e

Browse files
authored
Merge pull request #948 from sergenyalcin/pc-rp
Add ProviderConfig ReconciliationPolicy API
2 parents 90d9bb7 + 89d1124 commit 6fa7e9e

818 files changed

Lines changed: 25366 additions & 9696 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apis/cluster/v1beta1/types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,15 @@ import (
88
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
99

1010
xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1"
11+
"github.com/crossplane/upjet/v2/apis/configuration/v1alpha1"
1112
)
1213

1314
// A ProviderConfigSpec defines the desired state of a ProviderConfig.
1415
type ProviderConfigSpec struct {
16+
// +optional
17+
// +kubebuilder:validation:XValidation:rule="!has(self.exponentialFailureRateLimiter) || !has(self.exponentialFailureRateLimiter.baseDelay) || has(self.exponentialFailureRateLimiter.maxDelay) || duration(self.exponentialFailureRateLimiter.baseDelay) <= duration('60s')",message="when maxDelay is omitted it defaults to 60s; baseDelay must be <= 60s"
18+
ReconciliationPolicy *v1alpha1.ReconciliationPolicy `json:"reconciliationPolicy,omitempty"`
19+
1520
// Credentials required to authenticate to this provider.
1621
Credentials ProviderCredentials `json:"credentials"`
1722

apis/cluster/v1beta1/zz_generated.deepcopy.go

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apis/namespaced/v1beta1/types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,15 @@ import (
99

1010
xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1"
1111
xpv2 "github.com/crossplane/crossplane-runtime/v2/apis/common/v2"
12+
"github.com/crossplane/upjet/v2/apis/configuration/v1alpha1"
1213
)
1314

1415
// A ProviderConfigSpec defines the desired state of a ProviderConfig.
1516
type ProviderConfigSpec struct {
17+
// +optional
18+
// +kubebuilder:validation:XValidation:rule="!has(self.exponentialFailureRateLimiter) || !has(self.exponentialFailureRateLimiter.baseDelay) || has(self.exponentialFailureRateLimiter.maxDelay) || duration(self.exponentialFailureRateLimiter.baseDelay) <= duration('60s')",message="when maxDelay is omitted it defaults to 60s; baseDelay must be <= 60s"
19+
ReconciliationPolicy *v1alpha1.ReconciliationPolicy `json:"reconciliationPolicy,omitempty"`
20+
1621
// Credentials required to authenticate to this provider.
1722
Credentials ProviderCredentials `json:"credentials"`
1823

apis/namespaced/v1beta1/zz_generated.deepcopy.go

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/registry_cluster.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/pkg/errors"
1717

1818
"github.com/upbound/provider-gcp/v2/config/cluster"
19+
"github.com/upbound/provider-gcp/v2/config/templates"
1920
"github.com/upbound/provider-gcp/v2/hack"
2021
)
2122

@@ -55,6 +56,7 @@ func GetProvider(_ context.Context, sdkProvider *schema.Provider, generationProv
5556
ujconfig.WithMainTemplate(hack.MainTemplate),
5657
ujconfig.WithTerraformProvider(sdkProvider),
5758
ujconfig.WithSchemaTraversers(&ujconfig.SingletonListEmbedder{}),
59+
ujconfig.WithControllerTemplate(templates.ControllerTemplate),
5860
)
5961

6062
bumpVersionsWithEmbeddedLists(pc)

config/registry_namespaced.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/pkg/errors"
1515

1616
"github.com/upbound/provider-gcp/v2/config/namespaced"
17+
"github.com/upbound/provider-gcp/v2/config/templates"
1718
"github.com/upbound/provider-gcp/v2/hack"
1819
)
1920

@@ -53,6 +54,7 @@ func GetNamespacedProvider(_ context.Context, sdkProvider *schema.Provider, gene
5354
ujconfig.WithMainTemplate(hack.MainTemplate),
5455
ujconfig.WithTerraformProvider(sdkProvider),
5556
ujconfig.WithSchemaTraversers(&ujconfig.SingletonListEmbedder{}),
57+
ujconfig.WithControllerTemplate(templates.ControllerTemplate),
5658
)
5759

5860
registerTerraformConversions(pc)
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
{{ .Header }}
2+
3+
{{ .GenStatement }}
4+
5+
package {{ .Package }}
6+
7+
import (
8+
"time"
9+
10+
"github.com/crossplane/crossplane-runtime/v2/pkg/errors"
11+
"github.com/crossplane/crossplane-runtime/v2/pkg/event"
12+
xpfeature "github.com/crossplane/crossplane-runtime/v2/pkg/feature"
13+
"github.com/crossplane/crossplane-runtime/v2/pkg/ratelimiter"
14+
"github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed"
15+
xpresource "github.com/crossplane/crossplane-runtime/v2/pkg/resource"
16+
"github.com/crossplane/crossplane-runtime/v2/pkg/statemetrics"
17+
tjcontroller "github.com/crossplane/upjet/v2/pkg/controller"
18+
"github.com/crossplane/upjet/v2/pkg/controller/handler"
19+
"github.com/crossplane/upjet/v2/pkg/metrics"
20+
"github.com/crossplane/upjet/v2/pkg/reconciler/reconciliationpolicy"
21+
ctrl "sigs.k8s.io/controller-runtime"
22+
23+
"github.com/upbound/provider-gcp/v2/internal/clients"
24+
{{ .Imports }}
25+
)
26+
27+
// SetupGated adds a controller that reconciles {{ .CRD.Kind }} managed resources.
28+
func SetupGated(mgr ctrl.Manager, o tjcontroller.Options) error {
29+
o.Options.Gate.Register(func() {
30+
if err := Setup(mgr, o); err != nil {
31+
mgr.GetLogger().Error(err, "unable to setup reconciler", "gvk", {{ .TypePackageAlias }}{{ .CRD.Kind }}_GroupVersionKind.String())
32+
}
33+
}, {{ .TypePackageAlias }}{{ .CRD.Kind }}_GroupVersionKind)
34+
return nil
35+
}
36+
37+
// Setup adds a controller that reconciles {{ .CRD.Kind }} managed resources.
38+
func Setup(mgr ctrl.Manager, o tjcontroller.Options) error {
39+
name := managed.ControllerName({{ .TypePackageAlias }}{{ .CRD.Kind }}_GroupVersionKind.String())
40+
var initializers managed.InitializerChain
41+
{{- if .Initializers }}
42+
for _, i := range o.Provider.Resources["{{ .ResourceType }}"].InitializerFns {
43+
initializers = append(initializers,i(mgr.GetClient()))
44+
}
45+
{{- end}}
46+
{{- if not .DisableNameInitializer }}
47+
initializers = append(initializers, managed.NewNameAsExternalName(mgr.GetClient()))
48+
{{- end}}
49+
rl := reconciliationpolicy.NewExponentialFailureRateLimiter(time.Second, 60*time.Second)
50+
eventHandler := handler.NewEventHandler(handler.WithDefaultRateLimiter(rl), handler.WithLogger(o.Logger.WithValues("gvk", {{ .TypePackageAlias }}{{ .CRD.Kind }}_GroupVersionKind)))
51+
{{- if .UseAsync }}
52+
ac := tjcontroller.NewAPICallbacks(mgr, xpresource.ManagedKind({{ .TypePackageAlias }}{{ .CRD.Kind }}_GroupVersionKind), tjcontroller.WithEventHandler(eventHandler){{ if or .UseTerraformPluginSDKClient .UseTerraformPluginFrameworkClient }}, tjcontroller.WithStatusUpdates(false){{ end }})
53+
{{- end}}
54+
opts := []managed.ReconcilerOption{
55+
managed.WithExternalConnecter(
56+
{{- if .UseTerraformPluginSDKClient -}}
57+
{{- if .UseAsync }}
58+
tjcontroller.NewTerraformPluginSDKAsyncConnector(mgr.GetClient(), o.OperationTrackerStore, o.SetupFn, o.Provider.Resources["{{ .ResourceType }}"],
59+
tjcontroller.WithTerraformPluginSDKAsyncLogger(o.Logger),
60+
tjcontroller.WithTerraformPluginSDKAsyncConnectorEventHandler(eventHandler),
61+
tjcontroller.WithTerraformPluginSDKAsyncCallbackProvider(ac),
62+
tjcontroller.WithTerraformPluginSDKAsyncMetricRecorder(metrics.NewMetricRecorder({{ .TypePackageAlias }}{{ .CRD.Kind }}_GroupVersionKind, mgr, o.PollInterval)),
63+
{{if .FeaturesPackageAlias -}}
64+
tjcontroller.WithTerraformPluginSDKAsyncManagementPolicies(o.Features.Enabled({{ .FeaturesPackageAlias }}EnableBetaManagementPolicies)),
65+
{{- end }}
66+
)
67+
{{- else -}}
68+
tjcontroller.NewTerraformPluginSDKConnector(mgr.GetClient(), o.SetupFn, o.Provider.Resources["{{ .ResourceType }}"], o.OperationTrackerStore,
69+
tjcontroller.WithTerraformPluginSDKLogger(o.Logger),
70+
tjcontroller.WithTerraformPluginSDKMetricRecorder(metrics.NewMetricRecorder({{ .TypePackageAlias }}{{ .CRD.Kind }}_GroupVersionKind, mgr, o.PollInterval)),
71+
{{if .FeaturesPackageAlias -}}
72+
tjcontroller.WithTerraformPluginSDKManagementPolicies(o.Features.Enabled({{ .FeaturesPackageAlias }}EnableBetaManagementPolicies)),
73+
{{- end }}
74+
)
75+
{{- end -}}
76+
{{- else if .UseTerraformPluginFrameworkClient -}}
77+
{{- if .UseAsync }}
78+
tjcontroller.NewTerraformPluginFrameworkAsyncConnector(mgr.GetClient(), o.OperationTrackerStore, o.SetupFn, o.Provider.Resources["{{ .ResourceType }}"],
79+
tjcontroller.WithTerraformPluginFrameworkAsyncLogger(o.Logger),
80+
tjcontroller.WithTerraformPluginFrameworkAsyncConnectorEventHandler(eventHandler),
81+
tjcontroller.WithTerraformPluginFrameworkAsyncCallbackProvider(ac),
82+
tjcontroller.WithTerraformPluginFrameworkAsyncMetricRecorder(metrics.NewMetricRecorder({{ .TypePackageAlias }}{{ .CRD.Kind }}_GroupVersionKind, mgr, o.PollInterval)),
83+
{{if .FeaturesPackageAlias -}}
84+
tjcontroller.WithTerraformPluginFrameworkAsyncManagementPolicies(o.Features.Enabled({{ .FeaturesPackageAlias }}EnableBetaManagementPolicies)),
85+
{{- end }}
86+
)
87+
{{- else }}
88+
tjcontroller.NewTerraformPluginFrameworkConnector(mgr.GetClient(), o.SetupFn, o.Provider.Resources["{{ .ResourceType }}"], o.OperationTrackerStore,
89+
tjcontroller.WithTerraformPluginFrameworkLogger(o.Logger),
90+
tjcontroller.WithTerraformPluginFrameworkMetricRecorder(metrics.NewMetricRecorder({{ .TypePackageAlias }}{{ .CRD.Kind }}_GroupVersionKind, mgr, o.PollInterval)),
91+
{{if .FeaturesPackageAlias -}}
92+
tjcontroller.WithTerraformPluginFrameworkManagementPolicies(o.Features.Enabled({{ .FeaturesPackageAlias }}EnableBetaManagementPolicies)),
93+
{{- end }}
94+
)
95+
{{- end }}
96+
{{- else -}}
97+
tjcontroller.NewConnector(mgr.GetClient(), o.WorkspaceStore, o.SetupFn, o.Provider.Resources["{{ .ResourceType }}"], tjcontroller.WithLogger(o.Logger), tjcontroller.WithConnectorEventHandler(eventHandler),
98+
{{- if .UseAsync }}
99+
tjcontroller.WithCallbackProvider(ac),
100+
{{- end }}
101+
)
102+
{{- end }},
103+
),
104+
managed.WithLogger(o.Logger.WithValues("controller", name)),
105+
managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))),
106+
managed.WithFinalizer(
107+
reconciliationpolicy.NewFinalizer(
108+
tjcontroller.NewOperationTrackerFinalizer(o.OperationTrackerStore, xpresource.NewAPIFinalizer(mgr.GetClient(), managed.FinalizerName)),
109+
reconciliationpolicy.WithFinalizerRateLimiter(rl),
110+
),
111+
),
112+
managed.WithTimeout(3*time.Minute),
113+
managed.WithInitializers(initializers),
114+
managed.WithPollInterval(o.PollInterval),
115+
}
116+
117+
if o.PollJitter != 0 {
118+
opts = append(opts, managed.WithPollJitterHook(o.PollJitter))
119+
}
120+
{{- if .FeaturesPackageAlias }}
121+
if o.Features.Enabled({{ .FeaturesPackageAlias }}EnableBetaManagementPolicies) {
122+
opts = append(opts, managed.WithManagementPolicies())
123+
}
124+
{{- end}}
125+
if o.MetricOptions != nil {
126+
opts = append(opts, managed.WithMetricRecorder(o.MetricOptions.MRMetrics))
127+
}
128+
if o.Features.Enabled(xpfeature.EnableAlphaChangeLogs) {
129+
opts = append(opts, managed.WithChangeLogger(o.ChangeLogOptions.ChangeLogger))
130+
}
131+
132+
// register webhooks for the kind {{ .TypePackageAlias }}{{ .CRD.Kind }} if they're enabled.
133+
if o.StartWebhooks {
134+
if err := ctrl.NewWebhookManagedBy(mgr, &{{ .TypePackageAlias }}{{ .CRD.Kind }}{}).
135+
Complete(); err != nil {
136+
return errors.Wrap(err, "cannot register webhook for the kind {{ .TypePackageAlias }}{{ .CRD.Kind }}")
137+
}
138+
}
139+
140+
if o.MetricOptions != nil && o.MetricOptions.MRStateMetrics != nil {
141+
stateMetricsRecorder := statemetrics.NewMRStateRecorder(
142+
mgr.GetClient(), o.Logger, o.MetricOptions.MRStateMetrics, &{{ .TypePackageAlias }}{{ .CRD.Kind }}List{}, o.MetricOptions.PollStateMetricInterval,
143+
)
144+
if err := mgr.Add(stateMetricsRecorder); err != nil {
145+
return errors.Wrap(err, "cannot register MR state metrics recorder for kind {{ .TypePackageAlias }}{{ .CRD.Kind }}List")
146+
}
147+
}
148+
149+
r := managed.NewReconciler(mgr, xpresource.ManagedKind({{ .TypePackageAlias }}{{ .CRD.Kind }}_GroupVersionKind), opts...)
150+
co := o.ForControllerRuntime()
151+
co.RateLimiter = rl
152+
153+
return ctrl.NewControllerManagedBy(mgr).
154+
Named(name).
155+
WithOptions(co).
156+
WithEventFilter(xpresource.DesiredStateChanged()).
157+
Watches(&{{ .TypePackageAlias }}{{ .CRD.Kind }}{}, eventHandler).
158+
Complete(
159+
reconciliationpolicy.NewReconciler(
160+
ratelimiter.NewReconciler(name, r, o.GlobalRateLimiter),
161+
mgr,
162+
{{ .TypePackageAlias }}{{ .CRD.Kind }}_GroupVersionKind,
163+
reconciliationpolicy.WithRateLimiter(rl),
164+
reconciliationpolicy.WithSource(clients.ReconciliationPolicy),
165+
),
166+
)
167+
}

config/templates/embed.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// SPDX-FileCopyrightText: 2026 The Crossplane Authors <https://crossplane.io>
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
package templates
6+
7+
import _ "embed"
8+
9+
// ControllerTemplate is the template for the MR controller setup files.
10+
//
11+
//go:embed controller.go.tmpl
12+
var ControllerTemplate string

0 commit comments

Comments
 (0)