Skip to content

[WIP] Use OCI artifacts for CAPI providers #1389

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
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
15 changes: 15 additions & 0 deletions test/e2e/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,27 @@ var (
//go:embed data/capi-operator/capv-provider.yaml
CapvProvider []byte

//go:embed data/capi-operator/capv-provider-oci.yaml
CapvProviderOci []byte

//go:embed data/capi-operator/aws-provider.yaml
AWSProvider []byte

//go:embed data/capi-operator/aws-provider-oci.yaml
AWSProviderOci []byte

//go:embed data/capi-operator/gcp-provider.yaml
GCPProvider []byte

//go:embed data/capi-operator/gcp-provider-oci.yaml
GCPProviderOci []byte

//go:embed data/capi-operator/azure-provider.yaml
AzureProvider []byte

//go:embed data/capi-operator/azure-provider-oci.yaml
AzureProviderOci []byte

//go:embed data/capi-operator/capa-variables.yaml
AWSProviderSecret []byte

Expand Down Expand Up @@ -119,6 +131,9 @@ var (

//go:embed data/gitea/values.yaml
GiteaValues []byte

//go:embed internal/controllers/clusterctl/config.yaml
CAPIClusterctlConfig []byte
)

const (
Expand Down
18 changes: 18 additions & 0 deletions test/e2e/data/capi-operator/aws-provider-oci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
apiVersion: turtles-capi.cattle.io/v1alpha1
kind: CAPIProvider
metadata:
name: aws
namespace: capa-system
spec:
type: infrastructure
name: aws
version: "{{ .ProviderVersion }}"
fetchConfig:
oci: registry.rancher.com/rancher/cluster-api-aws-controller-components:"{{ .ProviderVersion }}"
variables:
EXP_MACHINE_POOL: "true"
EXP_EXTERNAL_RESOURCE_GC: "true"
CAPA_LOGLEVEL: "4"
manager:
syncPeriod: "5m"
12 changes: 12 additions & 0 deletions test/e2e/data/capi-operator/azure-provider-oci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
apiVersion: turtles-capi.cattle.io/v1alpha1
kind: CAPIProvider
metadata:
name: azure
namespace: capz-system
spec:
type: infrastructure
name: azure
version: "{{ .ProviderVersion }}"
fetchConfig:
oci: registry.rancher.com/rancher/cluster-api-azure-controller-components:"{{ .ProviderVersion }}"
2 changes: 1 addition & 1 deletion test/e2e/data/capi-operator/azure-provider.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ metadata:
name: azure
namespace: capz-system
spec:
type: infrastructure
type: infrastructure
15 changes: 15 additions & 0 deletions test/e2e/data/capi-operator/capv-provider-oci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
apiVersion: turtles-capi.cattle.io/v1alpha1
kind: CAPIProvider
metadata:
name: vsphere
namespace: capv-system
spec:
type: infrastructure
name: vsphere
version: "{{ .ProviderVersion }}"
fetchConfig:
oci: registry.rancher.com/rancher/cluster-api-vsphere-controller-components:"{{ .ProviderVersion }}"
variables:
VSPHERE_USERNAME: ""
VSPHERE_PASSWORD: ""
12 changes: 12 additions & 0 deletions test/e2e/data/capi-operator/gcp-provider-oci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
apiVersion: turtles-capi.cattle.io/v1alpha1
kind: CAPIProvider
metadata:
name: gcp
namespace: capg-system
spec:
type: infrastructure
name: gcp
version: "{{ .ProviderVersion }}"
fetchConfig:
oci: registry.rancher.com/rancher/cluster-api-gcp-controller-components:"{{ .ProviderVersion }}"
4 changes: 3 additions & 1 deletion test/e2e/data/capi-operator/gcp-provider.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ metadata:
name: gcp
namespace: capg-system
spec:
type: infrastructure
type: infrastructure
fetchConfig:
oci: registry.rancher.com/rancher/cluster-api-gcp-controller-components:v1.9.0
52 changes: 52 additions & 0 deletions test/e2e/suites/import-gitops-v3/import_gitops_v3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,58 @@ var _ = Describe("[Azure] [AKS] Create and delete CAPI cluster from cluster clas
})
})

var _ = Describe("[Azure] [AKS] Create and delete CAPI cluster from cluster class using provider from OCI artifact", Label(e2e.FullTestLabel), func() {
var topologyNamespace string

BeforeEach(func() {
komega.SetClient(bootstrapClusterProxy.GetClient())
komega.SetContext(ctx)

topologyNamespace = "creategitops-azure-aks"
})

specs.CreateMgmtV3UsingGitOpsSpec(ctx, func() specs.CreateMgmtV3UsingGitOpsSpecInput {
testenv.CAPIOperatorDeployProvider(ctx, testenv.CAPIOperatorDeployProviderInput{
BootstrapClusterProxy: bootstrapClusterProxy,
CAPIProvidersOCIYAML: []testenv.OCIProvider{
Name: "azure",
File: e2e.AzureProviderOci,
},
WaitForDeployments: []testenv.NamespaceName{
{
Name: "capz-controller-manager",
Namespace: "capz-system",
},
},
})

return specs.CreateMgmtV3UsingGitOpsSpecInput{
E2EConfig: e2e.LoadE2EConfig(),
BootstrapClusterProxy: bootstrapClusterProxy,
ClusterTemplate: e2e.CAPIAzureAKSTopology,
ClusterName: "cluster-aks",
ControlPlaneMachineCount: ptr.To(1),
WorkerMachineCount: ptr.To(1),
LabelNamespace: true,
RancherServerURL: hostName,
CAPIClusterCreateWaitName: "wait-capz-create-cluster",
DeleteClusterWaitName: "wait-aks-delete",
CapiClusterOwnerLabel: e2e.CapiClusterOwnerLabel,
CapiClusterOwnerNamespaceLabel: e2e.CapiClusterOwnerNamespaceLabel,
OwnedLabelName: e2e.OwnedLabelName,
TopologyNamespace: topologyNamespace,
AdditionalFleetGitRepos: []turtlesframework.FleetCreateGitRepoInput{
{
Name: "azure-cluster-classes-aks",
Paths: []string{"examples/clusterclasses/azure/aks"},
ClusterProxy: bootstrapClusterProxy,
TargetNamespace: topologyNamespace,
},
},
}
})
})

var _ = Describe("[Azure] [RKE2] - [management.cattle.io/v3] Create and delete CAPI cluster from cluster class", Label(e2e.FullTestLabel, e2e.Rke2TestLabel), func() {
var topologyNamespace string

Expand Down
101 changes: 101 additions & 0 deletions test/testenv/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@ import (
"bytes"
"context"
"html/template"
"os"
"regexp"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"gopkg.in/yaml.v2"

appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/cluster-api/test/framework"
"sigs.k8s.io/cluster-api/test/framework/clusterctl"
Expand All @@ -46,6 +50,9 @@ type CAPIOperatorDeployProviderInput struct {
// CAPIProvidersYAML is the YAML representation of the CAPI providers.
CAPIProvidersYAML [][]byte

// CAPIProvidersOCIYAML is the YAML representation of the CAPI providers with OCI.
CAPIProvidersOCIYAML OCIProvider

// TemplateData is the data used for templating.
TemplateData TemplateData

Expand All @@ -64,11 +71,36 @@ type TemplateData struct {
GCPEncodedCredentials string `env:"CAPG_ENCODED_CREDS"`
}

// ProviderTemplateData contains variables used for templating
type ProviderTemplateData struct {
// ProviderVersion is the version of the provider
ProviderVersion string
}

type NamespaceName struct {
Name string
Namespace string
}

type OCIProvider struct {
Name string
File string
}

// Provider represents a cluster-api provider with version
type Provider struct {
Name string `yaml:"name"`
Type string `yaml:"type"`
URL string `yaml:"url"`
Version string // Parsed from URL
}

// ClusterctlConfig represents the structure of clusterctl.yaml
type ClusterctlConfig struct {
Images map[string]interface{} `yaml:"images"`
Providers []Provider `yaml:"providers"`
}

// CAPIOperatorDeployProvider deploys the CAPI operator providers.
// It expects the required input parameters to be non-nil.
// It iterates over the CAPIProvidersSecretsYAML and applies them. Then, it applies the CAPI operator providers.
Expand Down Expand Up @@ -97,6 +129,19 @@ func CAPIOperatorDeployProvider(ctx context.Context, input CAPIOperatorDeployPro
Expect(turtlesframework.Apply(ctx, input.BootstrapClusterProxy, provider)).To(Succeed(), "Failed to add CAPI operator providers")
}

if input.CAPIProvidersOCIYAML.Name != "" && input.CAPIProvidersOCIYAML.File != "" {
name := input.CAPIProvidersOCIYAML.Name
By("Adding CAPI Operator provider from OCI: " + name)

providerVersion := getProviderVersion(name)
Expect(providerVersion).ToNot(BeEmpty(), "Failed to get provider versions from file")

Expect(turtlesframework.ApplyFromTemplate(ctx, turtlesframework.ApplyFromTemplateInput{
Proxy: input.BootstrapClusterProxy,
Template: renderProviderTemplate(input.CAPIProvidersOCIYAML.File, ProviderTemplateData{ProviderVersion: providerVersion}),
})).To(Succeed(), "Failed to apply secret for capi providers")
}

if len(input.WaitForDeployments) == 0 {
By("No deployments to wait for")

Expand Down Expand Up @@ -131,3 +176,59 @@ func getFullProviderVariables(operatorTemplate string, data TemplateData) []byte

return renderedTemplate.Bytes()
}

func renderProviderTemplate(operatorTemplateFile string, data ProviderTemplateData) []byte {
Expect(turtlesframework.Parse(&data)).To(Succeed(), "Failed to parse environment variables")

t := template.New("capi-operator")
t, err := t.Parse(operatorTemplateFile)
Expect(err).ShouldNot(HaveOccurred(), "Failed to parse template")

var renderedTemplate bytes.Buffer
err = t.Execute(&renderedTemplate, data)
Expect(err).NotTo(HaveOccurred(), "Failed to execute template")

return renderedTemplate.Bytes()
}

// getProviderVersionsFromFile reads the local config.yaml file and parses provider versions
func getProviderVersion(name string) string {
// Read config.yaml file
filePath := "internal/controllers/clusterctl/config.yaml"
yamlContent, err := os.ReadFile(filePath)
Expect(err).ShouldNot(HaveOccurred(), "Failed to read file %s", filePath)

// Parse the ConfigMap YAML structure
var configMap corev1.ConfigMap
err = yaml.Unmarshal(yamlContent, &configMap)
Expect(err).ShouldNot(HaveOccurred(), "Failed to parse ConfigMAap YAML from %s", filePath)

// Extract clusterctl.yaml content from ConfigMap data
clusterctlYaml, exists := configMap.Data["clusterctl.yaml"]
Expect(exists).To(BeTrue(), "clusterctl.yaml key not found in ConfigMap from %s", filePath)

// Parse the clusterctl.yaml content
var config ClusterctlConfig
err = yaml.Unmarshal([]byte(clusterctlYaml), &config)
Expect(err).ShouldNot(HaveOccurred(), "Failed to parse clusterctl.yaml content from %s", filePath)

// Extract versions from provider URLs
versionRegex := regexp.MustCompile(`/releases/v?([0-9]+\.[0-9]+\.[0-9]+(?:-[a-zA-Z0-9.-]+)?)/`)

for _, provider := range config.Providers {
if provider.Name == name {
return extractVersionFromURL(provider.URL, versionRegex)
}
}

return ""
}

// extractVersionFromURL extracts version from GitHub release URL
func extractVersionFromURL(url string, regex *regexp.Regexp) string {
matches := regex.FindStringSubmatch(url)
if len(matches) > 1 {
return matches[1]
}
return ""
}
Loading