Skip to content
Merged
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
71 changes: 56 additions & 15 deletions cmd/plugin/cmd/preload.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package cmd
import (
"context"
"fmt"
"net/url"
"os"
"strings"

Expand All @@ -44,7 +45,7 @@ type loadOptions struct {
runtimeExtensionProviders []string
addonProviders []string
targetNamespace string
ociURL string
artifactURL string
kubeconfig string
existing bool
}
Expand All @@ -67,6 +68,9 @@ var loadCmd = &cobra.Command{
Alternatively, for multi-provider OCI artifact, a fully specified name can be used for both metadata and components:

oras push ttl.sh/infrastructure-provider:tag infrastructure-docker-v1.10.0-beta.0-metadata.yaml infrastructure-docker-v1.10.0-beta.0-components.yaml

If you want to use a GitHub or GitLab release as artifact source, you must provide a full URL, including scheme, host, path, version and file name, e.g.: https://github.com/kubernetes-sigs/cluster-api/releases/v1.10.5/core-components.yaml
In this case, the version is set in the URL, and cannot be specified with the provider argument.
`),
Example: Examples(`
# Load CAPI operator manifests from OCI source
Expand All @@ -78,12 +82,21 @@ var loadCmd = &cobra.Command{
# Prepare provider ConfigMap from OCI, from the given infrastructure provider.
capioperator preload --infrastructure=aws -u ttl.sh/infrastructure-provider

# Prepare provider ConfigMap from GitHub release, from the given infrastructure provider.
capioperator preload --infrastructure=aws -u https://github.com/kubernetes-sigs/cluster-api-provider-aws/releases/v2.9.1/infrastructure-components.yaml

# Prepare provider ConfigMap from OCI with a specific version of the given infrastructure provider in the default namespace.
capioperator preload --infrastructure=aws::v2.3.0 -u ttl.sh/infrastructure-provider

# Prepare provider ConfigMap from GitHub release with a specific version of the given infrastructure provider in the default namespace.
capioperator preload --infrastructure=aws -u https://github.com/kubernetes-sigs/cluster-api-provider-aws/releases/v2.3.0/infrastructure-components.yaml

# Prepare provider ConfigMap from OCI with a specific namespace and the latest version of the given infrastructure provider.
capioperator preload --infrastructure=aws:custom-namespace -u ttl.sh/infrastructure-provider

# Prepare provider ConfigMap from GitHub release, with a specific namespace.
capioperator preload --infrastructure=aws:custom-namespace -u https://github.com/kubernetes-sigs/cluster-api-provider-aws/releases/v2.9.1/infrastructure-components.yaml

# Prepare provider ConfigMap from OCI with a specific version and namespace of the given infrastructure provider.
capioperator preload --infrastructure=aws:custom-namespace:v2.3.0 -u ttl.sh/infrastructure-provider

Expand Down Expand Up @@ -119,24 +132,24 @@ func init() {
"Add-on providers and versions (e.g. helm:v0.1.0) to add to the management cluster.")
loadCmd.Flags().StringVarP(&loadOpts.targetNamespace, "target-namespace", "n", "capi-operator-system",
"The target namespace where the operator should be deployed. If unspecified, the 'capi-operator-system' namespace is used.")
loadCmd.Flags().StringVarP(&loadOpts.ociURL, "artifact-url", "u", "",
"The URL of the OCI artifact to collect component manifests from.")
loadCmd.Flags().StringVarP(&loadOpts.artifactURL, "artifact-url", "u", "",
"The URL to OCI artifact or GitHub/GitLab release, to collect component manifests from.")

RootCmd.AddCommand(loadCmd)
}

func runPreLoad() error {
ctx := context.Background()

if loadOpts.ociURL == "" {
if loadOpts.artifactURL == "" {
return fmt.Errorf("missing configMap artifacts url")
}

configMaps := []*corev1.ConfigMap{}

// Load Core Provider.
if loadOpts.coreProvider != "" {
configMap, err := templateConfigMap(ctx, clusterctlv1.CoreProviderType, loadOpts.ociURL, loadOpts.coreProvider, loadOpts.targetNamespace)
configMap, err := templateConfigMap(ctx, clusterctlv1.CoreProviderType, loadOpts.artifactURL, loadOpts.coreProvider, loadOpts.targetNamespace)

if err != nil {
return fmt.Errorf("cannot prepare manifests config map for core provider: %w", err)
Expand All @@ -147,7 +160,7 @@ func runPreLoad() error {

// Load Bootstrap Providers.
for _, bootstrapProvider := range loadOpts.bootstrapProviders {
configMap, err := templateConfigMap(ctx, clusterctlv1.BootstrapProviderType, loadOpts.ociURL, bootstrapProvider, loadOpts.targetNamespace)
configMap, err := templateConfigMap(ctx, clusterctlv1.BootstrapProviderType, loadOpts.artifactURL, bootstrapProvider, loadOpts.targetNamespace)
if err != nil {
return fmt.Errorf("cannot prepare manifests config map for bootstrap provider: %w", err)
}
Expand All @@ -157,7 +170,7 @@ func runPreLoad() error {

// Load Infrastructure Providers.
for _, infrastructureProvider := range loadOpts.infrastructureProviders {
configMap, err := templateConfigMap(ctx, clusterctlv1.InfrastructureProviderType, loadOpts.ociURL, infrastructureProvider, loadOpts.targetNamespace)
configMap, err := templateConfigMap(ctx, clusterctlv1.InfrastructureProviderType, loadOpts.artifactURL, infrastructureProvider, loadOpts.targetNamespace)
if err != nil {
return fmt.Errorf("cannot prepare manifests config map for infrastructure provider: %w", err)
}
Expand All @@ -167,7 +180,7 @@ func runPreLoad() error {

// Load Control Plane Providers.
for _, controlPlaneProvider := range loadOpts.controlPlaneProviders {
configMap, err := templateConfigMap(ctx, clusterctlv1.ControlPlaneProviderType, loadOpts.ociURL, controlPlaneProvider, loadOpts.targetNamespace)
configMap, err := templateConfigMap(ctx, clusterctlv1.ControlPlaneProviderType, loadOpts.artifactURL, controlPlaneProvider, loadOpts.targetNamespace)
if err != nil {
return fmt.Errorf("cannot prepare manifests config map for controlplane provider: %w", err)
}
Expand All @@ -177,7 +190,7 @@ func runPreLoad() error {

// Load Add-on Providers.
for _, addonProvider := range loadOpts.addonProviders {
configMap, err := templateConfigMap(ctx, clusterctlv1.AddonProviderType, loadOpts.ociURL, addonProvider, loadOpts.targetNamespace)
configMap, err := templateConfigMap(ctx, clusterctlv1.AddonProviderType, loadOpts.artifactURL, addonProvider, loadOpts.targetNamespace)
if err != nil {
return fmt.Errorf("cannot prepare manifests config map for addon provider: %w", err)
}
Expand All @@ -187,7 +200,7 @@ func runPreLoad() error {

// Load IPAM Providers.
for _, ipamProvider := range loadOpts.ipamProviders {
configMap, err := templateConfigMap(ctx, clusterctlv1.IPAMProviderType, loadOpts.ociURL, ipamProvider, loadOpts.targetNamespace)
configMap, err := templateConfigMap(ctx, clusterctlv1.IPAMProviderType, loadOpts.artifactURL, ipamProvider, loadOpts.targetNamespace)
if err != nil {
return fmt.Errorf("cannot prepare manifests config map for IPAM provider: %w", err)
}
Expand All @@ -197,7 +210,7 @@ func runPreLoad() error {

// Load Runtime Extension Providers.
for _, runtimeExtension := range loadOpts.runtimeExtensionProviders {
configMap, err := templateConfigMap(ctx, clusterctlv1.RuntimeExtensionProviderType, loadOpts.ociURL, runtimeExtension, loadOpts.targetNamespace)
configMap, err := templateConfigMap(ctx, clusterctlv1.RuntimeExtensionProviderType, loadOpts.artifactURL, runtimeExtension, loadOpts.targetNamespace)
if err != nil {
return fmt.Errorf("cannot prepare manifests config map for runtime extension provider: %w", err)
}
Expand Down Expand Up @@ -289,16 +302,37 @@ func fetchProviders(ctx context.Context, cl client.Client, providerList genericP
return configMaps, nil
}

func templateConfigMap(ctx context.Context, providerType clusterctlv1.ProviderType, url, providerInput, defaultNamespace string) (*corev1.ConfigMap, error) {
func templateConfigMap(ctx context.Context, providerType clusterctlv1.ProviderType, providerURL, providerInput, defaultNamespace string) (*corev1.ConfigMap, error) {
provider, err := templateGenericProvider(providerType, providerInput, defaultNamespace, "", "")
if err != nil {
return nil, err
}

spec := provider.GetSpec()

parsedURL, err := url.Parse(providerURL)
if err != nil {
return nil, fmt.Errorf("invalid artifact URL: %w", err)
}

if util.IsGitHubDomain(parsedURL) || util.IsGitLabDomain(parsedURL) {
// artifact URL referes to a GitHub/GitLab release.
if spec.Version != "" {
return nil, fmt.Errorf("version cannot be set when artifact URL is GitHub or GitLab: it is specified in the URL")
}

spec.FetchConfig = &operatorv1.FetchConfiguration{
URL: providerURL,
}
provider.SetSpec(spec)

return providerConfigMap(ctx, provider)
}

// artifact URL refers to an OCI registry.
spec.FetchConfig = &operatorv1.FetchConfiguration{
OCIConfiguration: operatorv1.OCIConfiguration{
OCI: url,
OCI: providerURL,
},
}
provider.SetSpec(spec)
Expand Down Expand Up @@ -338,9 +372,11 @@ func providerConfigMap(ctx context.Context, provider operatorv1.GenericProvider)
return nil, fmt.Errorf("unable to init memory reader: %w", err)
}

spec := provider.GetSpec()

// If provided store fetch config url in memory reader.
if provider.GetSpec().FetchConfig != nil && provider.GetSpec().FetchConfig.URL != "" {
_, err := mr.AddProvider(provider.ProviderName(), util.ClusterctlProviderType(provider), provider.GetSpec().FetchConfig.URL)
if spec.FetchConfig != nil && spec.FetchConfig.URL != "" {
_, err := mr.AddProvider(provider.ProviderName(), util.ClusterctlProviderType(provider), spec.FetchConfig.URL)
if err != nil {
return nil, fmt.Errorf("cannot add custom url provider: %w", err)
}
Expand All @@ -363,6 +399,11 @@ func providerConfigMap(ctx context.Context, provider operatorv1.GenericProvider)
return nil, fmt.Errorf("cannot create repository: %w", err)
}

if spec.Version == "" {
spec.Version = repo.DefaultVersion()
provider.SetSpec(spec)
}

return providercontroller.RepositoryConfigMap(ctx, provider, repo)
}

Expand Down
18 changes: 9 additions & 9 deletions cmd/plugin/cmd/preload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ type publishProvider struct {
}

type publishOptions struct {
ociURL string
providers []publishProvider
artifactURL string
providers []publishProvider
}

func TestPreloadCommand(t *testing.T) {
Expand All @@ -62,7 +62,7 @@ func TestPreloadCommand(t *testing.T) {
{
name: "builtin core provider with OCI override",
publishOpts: &publishOptions{
ociURL: "ttl.sh/cluster-api-operator-manifests:1m",
artifactURL: "ttl.sh/cluster-api-operator-manifests:1m",
providers: []publishProvider{{
configMapName: "core-cluster-api-v1.10.0-beta.0",
provider: generateGenericProvider(clusterctlv1.CoreProviderType, "cluster-api", "default", "v1.10.0-beta.0", "", ""),
Expand All @@ -77,7 +77,7 @@ func TestPreloadCommand(t *testing.T) {
{
name: "multiple providers with OCI override",
publishOpts: &publishOptions{
ociURL: "ttl.sh/cluster-api-operator-manifests:1m",
artifactURL: "ttl.sh/cluster-api-operator-manifests:1m",
providers: []publishProvider{{
configMapName: "core-cluster-api-v1.10.0-beta.0",
provider: generateGenericProvider(clusterctlv1.CoreProviderType, "cluster-api", "default", "v1.10.0-beta.0", "", ""),
Expand Down Expand Up @@ -123,7 +123,7 @@ func TestPreloadCommand(t *testing.T) {
{
name: "OCI override with incorrect metadata key",
publishOpts: &publishOptions{
ociURL: "ttl.sh/cluster-api-operator-manifests:1m",
artifactURL: "ttl.sh/cluster-api-operator-manifests:1m",
providers: []publishProvider{{
configMapName: "core-cluster-api-v1.10.0-beta.0",
provider: generateGenericProvider(clusterctlv1.InfrastructureProviderType, "metadata-missing", "default", "v1.10.0-beta.0", "", ""),
Expand All @@ -138,7 +138,7 @@ func TestPreloadCommand(t *testing.T) {
{
name: "OCI override with incorrect components key",
publishOpts: &publishOptions{
ociURL: "ttl.sh/cluster-api-operator-manifests:1m",
artifactURL: "ttl.sh/cluster-api-operator-manifests:1m",
providers: []publishProvider{{
configMapName: "core-cluster-api-v1.10.0-beta.0",
provider: generateGenericProvider(clusterctlv1.InfrastructureProviderType, "components-missing", "default", "v1.10.0-beta.0", "", ""),
Expand Down Expand Up @@ -180,21 +180,21 @@ func TestPreloadCommand(t *testing.T) {
g.Expect(err).To(Succeed())

opts := cmp.Or(tt.publishOpts, &publishOptions{})
if tt.publishOpts != nil && opts.ociURL != "" {
if tt.publishOpts != nil && opts.artifactURL != "" {
for _, provider := range opts.providers {
err = os.WriteFile(path.Join(dir, provider.metadataKey), provider.metadataData, 0o777)
g.Expect(err).To(Succeed())
err = os.WriteFile(path.Join(dir, provider.componentsKey), provider.componentsData, 0o777)
g.Expect(err).To(Succeed())
}

g.Expect(publish(ctx, dir, opts.ociURL)).To(Succeed())
g.Expect(publish(ctx, dir, opts.artifactURL)).To(Succeed())

for _, data := range opts.providers {
spec := data.provider.GetSpec()
spec.FetchConfig = &operatorv1.FetchConfiguration{
OCIConfiguration: operatorv1.OCIConfiguration{
OCI: opts.ociURL,
OCI: opts.artifactURL,
},
}
data.provider.SetSpec(spec)
Expand Down
20 changes: 19 additions & 1 deletion docs/book/src/03_topics/03_plugin/02_preload_subcommand.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ kubectl operator preload [flags]
| `--runtime-extension` | | Specifies runtime extension providers and versions (e.g., `my-extension:v0.0.1`). |
| `--addon` | | Specifies add-on providers and versions (e.g., `helm:v0.1.0`). |
| `--target-namespace` | `-n` | Specifies the target namespace where the operator should be deployed. Defaults to `capi-operator-system`. |
| `--artifact-url` | `-u` | Specifies the URL of the OCI artifact containing component manifests. |
| `--artifact-url` | `-u` | Specifies the URL of the OCI artifact or GitHub/GitLab release containing component manifests. |

## Examples

Expand All @@ -46,18 +46,36 @@ kubectl operator preload --infrastructure=aws -u my-registry.example.com/infrast
```
This command fetches the latest available version of the `aws` infrastructure provider from the specified OCI registry and creates a ConfigMap.

### Prepare Provider ConfigMap from GitHub for a Specific Infrastructure Provider
```sh
kubectl operator preload --infrastructure=aws -u https://github.com/kubernetes-sigs/cluster-api-provider-aws/releases/latest/infrastructure-components.yaml
```
This command fetches the latest available version of the `aws` infrastructure provider from the specified GitHub repository and creates a ConfigMap.

### Prepare Provider ConfigMap with a Specific Version
```sh
kubectl operator preload --infrastructure=aws::v2.3.0 -u my-registry.example.com/infrastructure-provider
```
This command loads the AWS infrastructure provider version `v2.3.0` from the OCI registry into the default namespace.

### Prepare Provider ConfigMap from GitHub with a Specific Version
```sh
kubectl operator preload --infrastructure=aws -u https://github.com/kubernetes-sigs/cluster-api-provider-aws/releases/v2.3.0/infrastructure-components.yaml
```
This command loads the AWS infrastructure provider version `v2.3.0` from GitHub release into the default namespace. When using Git release as source for manifests you can only specify the desired version in the URL.

### Prepare Provider ConfigMap with a Custom Namespace
```sh
kubectl operator preload --infrastructure=aws:custom-namespace -u my-registry.example.com/infrastructure-provider
```
This command loads the latest version of the AWS infrastructure provider into the `custom-namespace`.

### Prepare Provider ConfigMap from GitHub with a Custom Namespace
```sh
kubectl operator preload --infrastructure=aws:custom-namespace -u https://github.com/kubernetes-sigs/cluster-api-provider-aws/releases/latest/infrastructure-components.yaml
```
This command loads the latest version of the AWS infrastructure provider from GitHub release into the `custom-namespace`.

### Prepare Provider ConfigMap with a Specific Version and Namespace
```sh
kubectl operator preload --infrastructure=aws:custom-namespace:v2.3.0 -u my-registry.example.com/infrastructure-provider
Expand Down
17 changes: 14 additions & 3 deletions util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ func RepositoryFactory(ctx context.Context, providerConfig configclient.Provider
}

// if the url is a GitHub repository
if rURL.Host == githubDomain {
if IsGitHubDomain(rURL) {
repo, err := repository.NewGitHubRepository(ctx, providerConfig, configVariablesClient)
if err != nil {
return nil, fmt.Errorf("error creating the GitHub repository client: %w", err)
Expand All @@ -159,8 +159,7 @@ func RepositoryFactory(ctx context.Context, providerConfig configclient.Provider
}

// if the url is a GitLab repository starting with gitlab- or gitlab.
gitlabHostRegex := regexp.MustCompile(`^` + regexp.QuoteMeta(gitlabHostPrefix) + `(-.*)?\.`) // ^gitlab(-.*)?\. to match gitlab- or gitlab.
if gitlabHostRegex.MatchString(rURL.Host) && strings.HasPrefix(rURL.Path, gitlabPackagesAPIPrefix) {
if IsGitLabDomain(rURL) {
repo, err := repository.NewGitLabRepository(ctx, providerConfig, configVariablesClient)
if err != nil {
return nil, fmt.Errorf("error creating the GitLab repository client: %w", err)
Expand All @@ -171,3 +170,15 @@ func RepositoryFactory(ctx context.Context, providerConfig configclient.Provider

return nil, fmt.Errorf("invalid provider url. Only GitHub and GitLab are supported for %q schema", rURL.Scheme)
}

// IsGitHubDomain returns true if the URL is a GitHub repository.
func IsGitHubDomain(u *url.URL) bool {
return u.Host == githubDomain
}

// IsGitLabDomain returns true if the URL is a GitLab repository.
func IsGitLabDomain(u *url.URL) bool {
gitlabHostRegex := regexp.MustCompile(`^` + regexp.QuoteMeta(gitlabHostPrefix) + `(-.*)?\.`) // ^gitlab(-.*)?\. to match gitlab- or gitlab.

return gitlabHostRegex.MatchString(u.Host) && strings.HasPrefix(u.Path, gitlabPackagesAPIPrefix)
}