@@ -20,9 +20,11 @@ import (
2020 "bytes"
2121 "context"
2222 "html/template"
23+ "regexp"
2324
2425 . "github.com/onsi/ginkgo/v2"
2526 . "github.com/onsi/gomega"
27+ "gopkg.in/yaml.v2"
2628
2729 appsv1 "k8s.io/api/apps/v1"
2830 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -46,6 +48,9 @@ type CAPIOperatorDeployProviderInput struct {
4648 // CAPIProvidersYAML is the YAML representation of the CAPI providers.
4749 CAPIProvidersYAML [][]byte
4850
51+ // CAPIProvidersOCIYAML is the YAML representation of the CAPI providers with OCI.
52+ CAPIProvidersOCIYAML []OCIProvider
53+
4954 // TemplateData is the data used for templating.
5055 TemplateData TemplateData
5156
@@ -62,11 +67,36 @@ type TemplateData struct {
6267 GCPEncodedCredentials string `env:"CAPG_ENCODED_CREDS"`
6368}
6469
70+ // ProviderTemplateData contains variables used for templating
71+ type ProviderTemplateData struct {
72+ // ProviderVersion is the version of the provider
73+ ProviderVersion string
74+ }
75+
6576type NamespaceName struct {
6677 Name string
6778 Namespace string
6879}
6980
81+ type OCIProvider struct {
82+ Name string
83+ File string
84+ }
85+
86+ // Provider represents a cluster-api provider with version
87+ type Provider struct {
88+ Name string `yaml:"name"`
89+ Type string `yaml:"type"`
90+ URL string `yaml:"url"`
91+ Version string // Parsed from URL
92+ }
93+
94+ // ClusterctlConfig represents the structure of clusterctl.yaml
95+ type ClusterctlConfig struct {
96+ Images map [string ]interface {} `yaml:"images"`
97+ Providers []Provider `yaml:"providers"`
98+ }
99+
70100// CAPIOperatorDeployProvider deploys the CAPI operator providers.
71101// It expects the required input parameters to be non-nil.
72102// It iterates over the CAPIProvidersSecretsYAML and applies them. Then, it applies the CAPI operator providers.
@@ -76,7 +106,11 @@ func CAPIOperatorDeployProvider(ctx context.Context, input CAPIOperatorDeployPro
76106
77107 Expect (ctx ).NotTo (BeNil (), "ctx is required for CAPIOperatorDeployProvider" )
78108 Expect (input .BootstrapClusterProxy ).ToNot (BeNil (), "BootstrapClusterProxy is required for CAPIOperatorDeployProvider" )
79- Expect (input .CAPIProvidersYAML ).ToNot (BeNil (), "CAPIProvidersYAML is required for CAPIOperatorDeployProvider" )
109+ // Ensure at least one provider source is available
110+ if (input .CAPIProvidersYAML == nil || len (input .CAPIProvidersYAML ) == 0 ) &&
111+ (input .CAPIProvidersOCIYAML == nil || len (input .CAPIProvidersOCIYAML ) == 0 ) {
112+ Expect (false ).To (BeTrue (), "Either CAPIProvidersYAML or CAPIProvidersOCIYAML must be provided" )
113+ }
80114
81115 for _ , secret := range input .CAPIProvidersSecretsYAML {
82116 secret := secret
@@ -95,6 +129,27 @@ func CAPIOperatorDeployProvider(ctx context.Context, input CAPIOperatorDeployPro
95129 Expect (turtlesframework .Apply (ctx , input .BootstrapClusterProxy , provider )).To (Succeed (), "Failed to add CAPI operator providers" )
96130 }
97131
132+ for _ , ociProvider := range input .CAPIProvidersOCIYAML {
133+ if ociProvider .Name != "" && ociProvider .File != "" {
134+ By ("Adding CAPI Operator provider from OCI: " + ociProvider .Name )
135+
136+ clusterctl := turtlesframework .GetClusterctl (ctx , turtlesframework.GetClusterctlInput {
137+ GetLister : input .BootstrapClusterProxy .GetClient (),
138+ ConfigMapNamespace : "rancher-turtles-system" ,
139+ ConfigMapName : "clusterctl-config" ,
140+ })
141+
142+ providerVersion := getProviderVersion (clusterctl , ociProvider .Name )
143+ By ("Using provider version " + providerVersion + " provider " + ociProvider .Name )
144+ Expect (providerVersion ).ToNot (BeEmpty (), "Failed to get provider versions from file" )
145+
146+ Expect (turtlesframework .ApplyFromTemplate (ctx , turtlesframework.ApplyFromTemplateInput {
147+ Proxy : input .BootstrapClusterProxy ,
148+ Template : renderProviderTemplate (ociProvider .File , ProviderTemplateData {ProviderVersion : providerVersion }),
149+ })).To (Succeed (), "Failed to apply secret for capi providers" )
150+ }
151+ }
152+
98153 if len (input .WaitForDeployments ) == 0 {
99154 By ("No deployments to wait for" )
100155
@@ -129,3 +184,44 @@ func getFullProviderVariables(operatorTemplate string, data TemplateData) []byte
129184
130185 return renderedTemplate .Bytes ()
131186}
187+
188+ func renderProviderTemplate (operatorTemplateFile string , data ProviderTemplateData ) []byte {
189+ Expect (turtlesframework .Parse (& data )).To (Succeed (), "Failed to parse environment variables" )
190+
191+ t := template .New ("capi-operator" )
192+ t , err := t .Parse (operatorTemplateFile )
193+ Expect (err ).ShouldNot (HaveOccurred (), "Failed to parse template" )
194+
195+ var renderedTemplate bytes.Buffer
196+ err = t .Execute (& renderedTemplate , data )
197+ Expect (err ).NotTo (HaveOccurred (), "Failed to execute template" )
198+
199+ return renderedTemplate .Bytes ()
200+ }
201+
202+ // getProviderVersionsFromFile reads the local config.yaml file and parses provider versions
203+ func getProviderVersion (clusterctlYaml string , name string ) string {
204+ var config ClusterctlConfig
205+ err := yaml .Unmarshal ([]byte (clusterctlYaml ), & config )
206+ Expect (err ).ShouldNot (HaveOccurred (), "Failed to parse clusterctl.yaml content" )
207+
208+ // Extract versions from provider URLs
209+ versionRegex := regexp .MustCompile (`/releases/(v?[0-9]+\.[0-9]+\.[0-9]+(?:-[a-zA-Z0-9.-]+)?)/` )
210+
211+ for _ , provider := range config .Providers {
212+ if provider .Name == name {
213+ return extractVersionFromURL (provider .URL , versionRegex )
214+ }
215+ }
216+
217+ return ""
218+ }
219+
220+ // extractVersionFromURL extracts version from GitHub release URL
221+ func extractVersionFromURL (url string , regex * regexp.Regexp ) string {
222+ matches := regex .FindStringSubmatch (url )
223+ if len (matches ) > 1 {
224+ return matches [1 ]
225+ }
226+ return ""
227+ }
0 commit comments