Skip to content

Commit fb2f897

Browse files
authored
Merge pull request #894 from salasberryfin/feat-support-gh-releases-in-plugin
✨ feat: support Git release URLs as source for plugin ConfigMap
2 parents 63ce757 + c4aee92 commit fb2f897

File tree

4 files changed

+98
-28
lines changed

4 files changed

+98
-28
lines changed

cmd/plugin/cmd/preload.go

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package cmd
1919
import (
2020
"context"
2121
"fmt"
22+
"net/url"
2223
"os"
2324
"strings"
2425

@@ -44,7 +45,7 @@ type loadOptions struct {
4445
runtimeExtensionProviders []string
4546
addonProviders []string
4647
targetNamespace string
47-
ociURL string
48+
artifactURL string
4849
kubeconfig string
4950
existing bool
5051
}
@@ -67,6 +68,9 @@ var loadCmd = &cobra.Command{
6768
Alternatively, for multi-provider OCI artifact, a fully specified name can be used for both metadata and components:
6869
6970
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
71+
72+
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
73+
In this case, the version is set in the URL, and cannot be specified with the provider argument.
7074
`),
7175
Example: Examples(`
7276
# Load CAPI operator manifests from OCI source
@@ -78,12 +82,21 @@ var loadCmd = &cobra.Command{
7882
# Prepare provider ConfigMap from OCI, from the given infrastructure provider.
7983
capioperator preload --infrastructure=aws -u ttl.sh/infrastructure-provider
8084
85+
# Prepare provider ConfigMap from GitHub release, from the given infrastructure provider.
86+
capioperator preload --infrastructure=aws -u https://github.com/kubernetes-sigs/cluster-api-provider-aws/releases/v2.9.1/infrastructure-components.yaml
87+
8188
# Prepare provider ConfigMap from OCI with a specific version of the given infrastructure provider in the default namespace.
8289
capioperator preload --infrastructure=aws::v2.3.0 -u ttl.sh/infrastructure-provider
8390
91+
# Prepare provider ConfigMap from GitHub release with a specific version of the given infrastructure provider in the default namespace.
92+
capioperator preload --infrastructure=aws -u https://github.com/kubernetes-sigs/cluster-api-provider-aws/releases/v2.3.0/infrastructure-components.yaml
93+
8494
# Prepare provider ConfigMap from OCI with a specific namespace and the latest version of the given infrastructure provider.
8595
capioperator preload --infrastructure=aws:custom-namespace -u ttl.sh/infrastructure-provider
8696
97+
# Prepare provider ConfigMap from GitHub release, with a specific namespace.
98+
capioperator preload --infrastructure=aws:custom-namespace -u https://github.com/kubernetes-sigs/cluster-api-provider-aws/releases/v2.9.1/infrastructure-components.yaml
99+
87100
# Prepare provider ConfigMap from OCI with a specific version and namespace of the given infrastructure provider.
88101
capioperator preload --infrastructure=aws:custom-namespace:v2.3.0 -u ttl.sh/infrastructure-provider
89102
@@ -119,24 +132,24 @@ func init() {
119132
"Add-on providers and versions (e.g. helm:v0.1.0) to add to the management cluster.")
120133
loadCmd.Flags().StringVarP(&loadOpts.targetNamespace, "target-namespace", "n", "capi-operator-system",
121134
"The target namespace where the operator should be deployed. If unspecified, the 'capi-operator-system' namespace is used.")
122-
loadCmd.Flags().StringVarP(&loadOpts.ociURL, "artifact-url", "u", "",
123-
"The URL of the OCI artifact to collect component manifests from.")
135+
loadCmd.Flags().StringVarP(&loadOpts.artifactURL, "artifact-url", "u", "",
136+
"The URL to OCI artifact or GitHub/GitLab release, to collect component manifests from.")
124137

125138
RootCmd.AddCommand(loadCmd)
126139
}
127140

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

131-
if loadOpts.ociURL == "" {
144+
if loadOpts.artifactURL == "" {
132145
return fmt.Errorf("missing configMap artifacts url")
133146
}
134147

135148
configMaps := []*corev1.ConfigMap{}
136149

137150
// Load Core Provider.
138151
if loadOpts.coreProvider != "" {
139-
configMap, err := templateConfigMap(ctx, clusterctlv1.CoreProviderType, loadOpts.ociURL, loadOpts.coreProvider, loadOpts.targetNamespace)
152+
configMap, err := templateConfigMap(ctx, clusterctlv1.CoreProviderType, loadOpts.artifactURL, loadOpts.coreProvider, loadOpts.targetNamespace)
140153

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

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

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

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

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

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

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

292-
func templateConfigMap(ctx context.Context, providerType clusterctlv1.ProviderType, url, providerInput, defaultNamespace string) (*corev1.ConfigMap, error) {
305+
func templateConfigMap(ctx context.Context, providerType clusterctlv1.ProviderType, providerURL, providerInput, defaultNamespace string) (*corev1.ConfigMap, error) {
293306
provider, err := templateGenericProvider(providerType, providerInput, defaultNamespace, "", "")
294307
if err != nil {
295308
return nil, err
296309
}
297310

298311
spec := provider.GetSpec()
312+
313+
parsedURL, err := url.Parse(providerURL)
314+
if err != nil {
315+
return nil, fmt.Errorf("invalid artifact URL: %w", err)
316+
}
317+
318+
if util.IsGitHubDomain(parsedURL) || util.IsGitLabDomain(parsedURL) {
319+
// artifact URL referes to a GitHub/GitLab release.
320+
if spec.Version != "" {
321+
return nil, fmt.Errorf("version cannot be set when artifact URL is GitHub or GitLab: it is specified in the URL")
322+
}
323+
324+
spec.FetchConfig = &operatorv1.FetchConfiguration{
325+
URL: providerURL,
326+
}
327+
provider.SetSpec(spec)
328+
329+
return providerConfigMap(ctx, provider)
330+
}
331+
332+
// artifact URL refers to an OCI registry.
299333
spec.FetchConfig = &operatorv1.FetchConfiguration{
300334
OCIConfiguration: operatorv1.OCIConfiguration{
301-
OCI: url,
335+
OCI: providerURL,
302336
},
303337
}
304338
provider.SetSpec(spec)
@@ -338,9 +372,11 @@ func providerConfigMap(ctx context.Context, provider operatorv1.GenericProvider)
338372
return nil, fmt.Errorf("unable to init memory reader: %w", err)
339373
}
340374

375+
spec := provider.GetSpec()
376+
341377
// If provided store fetch config url in memory reader.
342-
if provider.GetSpec().FetchConfig != nil && provider.GetSpec().FetchConfig.URL != "" {
343-
_, err := mr.AddProvider(provider.ProviderName(), util.ClusterctlProviderType(provider), provider.GetSpec().FetchConfig.URL)
378+
if spec.FetchConfig != nil && spec.FetchConfig.URL != "" {
379+
_, err := mr.AddProvider(provider.ProviderName(), util.ClusterctlProviderType(provider), spec.FetchConfig.URL)
344380
if err != nil {
345381
return nil, fmt.Errorf("cannot add custom url provider: %w", err)
346382
}
@@ -363,6 +399,11 @@ func providerConfigMap(ctx context.Context, provider operatorv1.GenericProvider)
363399
return nil, fmt.Errorf("cannot create repository: %w", err)
364400
}
365401

402+
if spec.Version == "" {
403+
spec.Version = repo.DefaultVersion()
404+
provider.SetSpec(spec)
405+
}
406+
366407
return providercontroller.RepositoryConfigMap(ctx, provider, repo)
367408
}
368409

cmd/plugin/cmd/preload_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ type publishProvider struct {
4242
}
4343

4444
type publishOptions struct {
45-
ociURL string
46-
providers []publishProvider
45+
artifactURL string
46+
providers []publishProvider
4747
}
4848

4949
func TestPreloadCommand(t *testing.T) {
@@ -62,7 +62,7 @@ func TestPreloadCommand(t *testing.T) {
6262
{
6363
name: "builtin core provider with OCI override",
6464
publishOpts: &publishOptions{
65-
ociURL: "ttl.sh/cluster-api-operator-manifests:1m",
65+
artifactURL: "ttl.sh/cluster-api-operator-manifests:1m",
6666
providers: []publishProvider{{
6767
configMapName: "core-cluster-api-v1.10.0-beta.0",
6868
provider: generateGenericProvider(clusterctlv1.CoreProviderType, "cluster-api", "default", "v1.10.0-beta.0", "", ""),
@@ -77,7 +77,7 @@ func TestPreloadCommand(t *testing.T) {
7777
{
7878
name: "multiple providers with OCI override",
7979
publishOpts: &publishOptions{
80-
ociURL: "ttl.sh/cluster-api-operator-manifests:1m",
80+
artifactURL: "ttl.sh/cluster-api-operator-manifests:1m",
8181
providers: []publishProvider{{
8282
configMapName: "core-cluster-api-v1.10.0-beta.0",
8383
provider: generateGenericProvider(clusterctlv1.CoreProviderType, "cluster-api", "default", "v1.10.0-beta.0", "", ""),
@@ -123,7 +123,7 @@ func TestPreloadCommand(t *testing.T) {
123123
{
124124
name: "OCI override with incorrect metadata key",
125125
publishOpts: &publishOptions{
126-
ociURL: "ttl.sh/cluster-api-operator-manifests:1m",
126+
artifactURL: "ttl.sh/cluster-api-operator-manifests:1m",
127127
providers: []publishProvider{{
128128
configMapName: "core-cluster-api-v1.10.0-beta.0",
129129
provider: generateGenericProvider(clusterctlv1.InfrastructureProviderType, "metadata-missing", "default", "v1.10.0-beta.0", "", ""),
@@ -138,7 +138,7 @@ func TestPreloadCommand(t *testing.T) {
138138
{
139139
name: "OCI override with incorrect components key",
140140
publishOpts: &publishOptions{
141-
ociURL: "ttl.sh/cluster-api-operator-manifests:1m",
141+
artifactURL: "ttl.sh/cluster-api-operator-manifests:1m",
142142
providers: []publishProvider{{
143143
configMapName: "core-cluster-api-v1.10.0-beta.0",
144144
provider: generateGenericProvider(clusterctlv1.InfrastructureProviderType, "components-missing", "default", "v1.10.0-beta.0", "", ""),
@@ -180,21 +180,21 @@ func TestPreloadCommand(t *testing.T) {
180180
g.Expect(err).To(Succeed())
181181

182182
opts := cmp.Or(tt.publishOpts, &publishOptions{})
183-
if tt.publishOpts != nil && opts.ociURL != "" {
183+
if tt.publishOpts != nil && opts.artifactURL != "" {
184184
for _, provider := range opts.providers {
185185
err = os.WriteFile(path.Join(dir, provider.metadataKey), provider.metadataData, 0o777)
186186
g.Expect(err).To(Succeed())
187187
err = os.WriteFile(path.Join(dir, provider.componentsKey), provider.componentsData, 0o777)
188188
g.Expect(err).To(Succeed())
189189
}
190190

191-
g.Expect(publish(ctx, dir, opts.ociURL)).To(Succeed())
191+
g.Expect(publish(ctx, dir, opts.artifactURL)).To(Succeed())
192192

193193
for _, data := range opts.providers {
194194
spec := data.provider.GetSpec()
195195
spec.FetchConfig = &operatorv1.FetchConfiguration{
196196
OCIConfiguration: operatorv1.OCIConfiguration{
197-
OCI: opts.ociURL,
197+
OCI: opts.artifactURL,
198198
},
199199
}
200200
data.provider.SetSpec(spec)

docs/book/src/03_topics/03_plugin/02_preload_subcommand.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ kubectl operator preload [flags]
2424
| `--runtime-extension` | | Specifies runtime extension providers and versions (e.g., `my-extension:v0.0.1`). |
2525
| `--addon` | | Specifies add-on providers and versions (e.g., `helm:v0.1.0`). |
2626
| `--target-namespace` | `-n` | Specifies the target namespace where the operator should be deployed. Defaults to `capi-operator-system`. |
27-
| `--artifact-url` | `-u` | Specifies the URL of the OCI artifact containing component manifests. |
27+
| `--artifact-url` | `-u` | Specifies the URL of the OCI artifact or GitHub/GitLab release containing component manifests. |
2828

2929
## Examples
3030

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

49+
### Prepare Provider ConfigMap from GitHub for a Specific Infrastructure Provider
50+
```sh
51+
kubectl operator preload --infrastructure=aws -u https://github.com/kubernetes-sigs/cluster-api-provider-aws/releases/latest/infrastructure-components.yaml
52+
```
53+
This command fetches the latest available version of the `aws` infrastructure provider from the specified GitHub repository and creates a ConfigMap.
54+
4955
### Prepare Provider ConfigMap with a Specific Version
5056
```sh
5157
kubectl operator preload --infrastructure=aws::v2.3.0 -u my-registry.example.com/infrastructure-provider
5258
```
5359
This command loads the AWS infrastructure provider version `v2.3.0` from the OCI registry into the default namespace.
5460

61+
### Prepare Provider ConfigMap from GitHub with a Specific Version
62+
```sh
63+
kubectl operator preload --infrastructure=aws -u https://github.com/kubernetes-sigs/cluster-api-provider-aws/releases/v2.3.0/infrastructure-components.yaml
64+
```
65+
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.
66+
5567
### Prepare Provider ConfigMap with a Custom Namespace
5668
```sh
5769
kubectl operator preload --infrastructure=aws:custom-namespace -u my-registry.example.com/infrastructure-provider
5870
```
5971
This command loads the latest version of the AWS infrastructure provider into the `custom-namespace`.
6072

73+
### Prepare Provider ConfigMap from GitHub with a Custom Namespace
74+
```sh
75+
kubectl operator preload --infrastructure=aws:custom-namespace -u https://github.com/kubernetes-sigs/cluster-api-provider-aws/releases/latest/infrastructure-components.yaml
76+
```
77+
This command loads the latest version of the AWS infrastructure provider from GitHub release into the `custom-namespace`.
78+
6179
### Prepare Provider ConfigMap with a Specific Version and Namespace
6280
```sh
6381
kubectl operator preload --infrastructure=aws:custom-namespace:v2.3.0 -u my-registry.example.com/infrastructure-provider

util/util.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ func RepositoryFactory(ctx context.Context, providerConfig configclient.Provider
149149
}
150150

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

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

172171
return nil, fmt.Errorf("invalid provider url. Only GitHub and GitLab are supported for %q schema", rURL.Scheme)
173172
}
173+
174+
// IsGitHubDomain returns true if the URL is a GitHub repository.
175+
func IsGitHubDomain(u *url.URL) bool {
176+
return u.Host == githubDomain
177+
}
178+
179+
// IsGitLabDomain returns true if the URL is a GitLab repository.
180+
func IsGitLabDomain(u *url.URL) bool {
181+
gitlabHostRegex := regexp.MustCompile(`^` + regexp.QuoteMeta(gitlabHostPrefix) + `(-.*)?\.`) // ^gitlab(-.*)?\. to match gitlab- or gitlab.
182+
183+
return gitlabHostRegex.MatchString(u.Host) && strings.HasPrefix(u.Path, gitlabPackagesAPIPrefix)
184+
}

0 commit comments

Comments
 (0)