Skip to content

Commit 2f8a915

Browse files
author
alexander-demicev
committed
Split phases.go into smaller files
Signed-off-by: alexander-demicev <alexandr.demicev@suse.com>
1 parent 25642df commit 2f8a915

File tree

5 files changed

+860
-741
lines changed

5 files changed

+860
-741
lines changed

internal/controller/phase_fetch.go

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/*
2+
Copyright 2026 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package controller
18+
19+
import (
20+
"bytes"
21+
"context"
22+
"fmt"
23+
24+
corev1 "k8s.io/api/core/v1"
25+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
27+
apijson "k8s.io/apimachinery/pkg/util/json"
28+
"k8s.io/client-go/kubernetes/scheme"
29+
operatorv1 "sigs.k8s.io/cluster-api-operator/api/v1alpha2"
30+
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository"
31+
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/yamlprocessor"
32+
ctrl "sigs.k8s.io/controller-runtime"
33+
"sigs.k8s.io/controller-runtime/pkg/client"
34+
)
35+
36+
// Fetch fetches the provider components from the repository and processes all yaml manifests.
37+
func (p *PhaseReconciler) Fetch(ctx context.Context) (*Result, error) {
38+
log := ctrl.LoggerFrom(ctx)
39+
log.Info("Fetching provider")
40+
41+
// Fetch the provider components yaml file from the provided repository GitHub/GitLab/ConfigMap.
42+
componentsFile, err := p.repo.GetFile(ctx, p.options.Version, p.repo.ComponentsPath())
43+
if err != nil {
44+
err = fmt.Errorf("failed to read %q from provider's repository %q: %w", p.repo.ComponentsPath(), p.providerConfig.ManifestLabel(), err)
45+
46+
return &Result{}, wrapPhaseError(err, operatorv1.ComponentsFetchErrorReason, operatorv1.ProviderInstalledCondition)
47+
}
48+
49+
// Check if components exceed the resource size.
50+
p.needsCompression = needToCompress(componentsFile)
51+
52+
// Generate a set of new objects using the clusterctl library. NewComponents() will do the yaml processing,
53+
// like ensure all the provider components are in proper namespace, replace variables, etc. See the clusterctl
54+
// documentation for more details.
55+
p.components, err = repository.NewComponents(repository.ComponentsInput{
56+
Provider: p.providerConfig,
57+
ConfigClient: p.configClient,
58+
Processor: yamlprocessor.NewSimpleProcessor(),
59+
RawYaml: componentsFile,
60+
Options: p.options,
61+
})
62+
if err != nil {
63+
return &Result{}, wrapPhaseError(err, operatorv1.ComponentsFetchErrorReason, operatorv1.ProviderInstalledCondition)
64+
}
65+
66+
// ProviderSpec provides fields for customizing the provider deployment options.
67+
// We can use clusterctl library to apply this customizations.
68+
if err := repository.AlterComponents(p.components, customizeObjectsFn(p.provider)); err != nil {
69+
return &Result{}, wrapPhaseError(err, operatorv1.ComponentsCustomizationErrorReason, operatorv1.ProviderInstalledCondition)
70+
}
71+
72+
// Apply patches to the provider components if specified.
73+
if err := repository.AlterComponents(p.components, applyPatches(ctx, p.provider)); err != nil {
74+
return &Result{}, wrapPhaseError(err, operatorv1.ComponentsPatchErrorReason, operatorv1.ProviderInstalledCondition)
75+
}
76+
77+
// Apply image overrides to the provider manifests.
78+
if err := repository.AlterComponents(p.components, imageOverrides(p.components.ManifestLabel(), p.overridesClient)); err != nil {
79+
return &Result{}, wrapPhaseError(err, operatorv1.ComponentsImageOverrideErrorReason, operatorv1.ProviderInstalledCondition)
80+
}
81+
82+
for _, fn := range p.customAlterComponentsFuncs {
83+
if err := repository.AlterComponents(p.components, fn); err != nil {
84+
return &Result{}, wrapPhaseError(err, operatorv1.ComponentsCustomizationErrorReason, operatorv1.ProviderInstalledCondition)
85+
}
86+
}
87+
88+
return &Result{}, nil
89+
}
90+
91+
// Store stores the provider components in the cache.
92+
func (p *PhaseReconciler) Store(ctx context.Context) (*Result, error) {
93+
log := ctrl.LoggerFrom(ctx)
94+
log.Info("Storing provider in cache")
95+
96+
kinds, _, err := scheme.Scheme.ObjectKinds(&corev1.Secret{})
97+
if err != nil || len(kinds) == 0 {
98+
log.Error(err, "cannot fetch kind of the Secret resource")
99+
err = fmt.Errorf("cannot fetch kind of the Secret resource: %w", err)
100+
101+
return &Result{}, wrapPhaseError(err, operatorv1.ComponentsCustomizationErrorReason, operatorv1.ProviderInstalledCondition)
102+
}
103+
104+
secret := &corev1.Secret{
105+
TypeMeta: metav1.TypeMeta{
106+
Kind: kinds[0].Kind,
107+
APIVersion: kinds[0].GroupVersion().String(),
108+
},
109+
ObjectMeta: metav1.ObjectMeta{
110+
Name: ProviderCacheName(p.provider),
111+
Namespace: p.provider.GetNamespace(),
112+
Annotations: map[string]string{},
113+
},
114+
StringData: map[string]string{},
115+
Data: map[string][]byte{},
116+
}
117+
118+
gvk := p.provider.GetObjectKind().GroupVersionKind()
119+
120+
secret.SetOwnerReferences([]metav1.OwnerReference{
121+
{
122+
APIVersion: gvk.GroupVersion().String(),
123+
Kind: gvk.Kind,
124+
Name: p.provider.GetName(),
125+
UID: p.provider.GetUID(),
126+
},
127+
})
128+
129+
if p.needsCompression {
130+
secret.Annotations[operatorv1.CompressedAnnotation] = "true"
131+
}
132+
133+
manifests, err := apijson.Marshal(addNamespaceIfMissing(p.components.Objs(), p.provider.GetNamespace()))
134+
if err != nil {
135+
return &Result{}, wrapPhaseError(err, operatorv1.ComponentsCustomizationErrorReason, operatorv1.ProviderInstalledCondition)
136+
}
137+
138+
if p.needsCompression {
139+
var buf bytes.Buffer
140+
if err := compressData(&buf, manifests); err != nil {
141+
return &Result{}, wrapPhaseError(err, operatorv1.ComponentsCustomizationErrorReason, operatorv1.ProviderInstalledCondition)
142+
}
143+
144+
secret.Data["cache"] = buf.Bytes()
145+
} else {
146+
secret.StringData["cache"] = string(manifests)
147+
}
148+
149+
if err := p.ctrlClient.Patch(ctx, secret, client.Apply, client.ForceOwnership, client.FieldOwner(cacheOwner)); err != nil {
150+
log.Error(err, "failed to apply cache config map")
151+
152+
return &Result{}, wrapPhaseError(err, operatorv1.ComponentsCustomizationErrorReason, operatorv1.ProviderInstalledCondition)
153+
}
154+
155+
return &Result{}, nil
156+
}
157+
158+
// addNamespaceIfMissing adda a Namespace object if missing (this ensure the targetNamespace will be created).
159+
func addNamespaceIfMissing(objs []unstructured.Unstructured, targetNamespace string) []unstructured.Unstructured {
160+
namespaceObjectFound := false
161+
162+
for _, o := range objs {
163+
// if the object has Kind Namespace, fix the namespace name
164+
if o.GetKind() == namespaceKind {
165+
namespaceObjectFound = true
166+
}
167+
}
168+
169+
// if there isn't an object with Kind Namespace, add it
170+
if !namespaceObjectFound {
171+
objs = append(objs, unstructured.Unstructured{
172+
Object: map[string]interface{}{
173+
"apiVersion": "v1",
174+
"kind": namespaceKind,
175+
"metadata": map[string]interface{}{
176+
"name": targetNamespace,
177+
},
178+
},
179+
})
180+
}
181+
182+
return objs
183+
}
184+
185+
func (p *PhaseReconciler) ReportStatus(_ context.Context) (*Result, error) {
186+
status := p.provider.GetStatus()
187+
status.Contract = &p.contract
188+
installedVersion := p.components.Version()
189+
status.InstalledVersion = &installedVersion
190+
p.provider.SetStatus(status)
191+
192+
return &Result{}, nil
193+
}
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
/*
2+
Copyright 2026 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package controller
18+
19+
import (
20+
"cmp"
21+
"context"
22+
"fmt"
23+
"os"
24+
25+
corev1 "k8s.io/api/core/v1"
26+
"k8s.io/apimachinery/pkg/types"
27+
operatorv1 "sigs.k8s.io/cluster-api-operator/api/v1alpha2"
28+
"sigs.k8s.io/cluster-api-operator/internal/controller/genericprovider"
29+
clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
30+
configclient "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
31+
ctrl "sigs.k8s.io/controller-runtime"
32+
"sigs.k8s.io/controller-runtime/pkg/client"
33+
"sigs.k8s.io/controller-runtime/pkg/log"
34+
)
35+
36+
// initReaderVariables initializes the given reader with configuration variables from the provider's
37+
// Spec.ConfigSecret if it is set.
38+
func initReaderVariables(ctx context.Context, cl client.Client, reader configclient.Reader, provider genericprovider.GenericProvider) error {
39+
log := log.FromContext(ctx)
40+
41+
// Fetch configuration variables from the secret. See API field docs for more info.
42+
if provider.GetSpec().ConfigSecret == nil {
43+
log.Info("No configuration secret was specified")
44+
45+
return nil
46+
}
47+
48+
secret := &corev1.Secret{}
49+
key := types.NamespacedName{Namespace: provider.GetSpec().ConfigSecret.Namespace, Name: provider.GetSpec().ConfigSecret.Name}
50+
51+
if err := cl.Get(ctx, key, secret); err != nil {
52+
log.Error(err, "failed to get referenced secret")
53+
54+
return err
55+
}
56+
57+
for k, v := range secret.Data {
58+
reader.Set(k, string(v))
59+
}
60+
61+
return nil
62+
}
63+
64+
// InitializePhaseReconciler initializes phase reconciler.
65+
func (p *PhaseReconciler) InitializePhaseReconciler(ctx context.Context) (*Result, error) {
66+
path := configPath
67+
if _, err := os.Stat(configPath); os.IsNotExist(err) {
68+
path = ""
69+
} else if err != nil {
70+
return &Result{}, err
71+
}
72+
73+
// Initialize a client for interacting with the clusterctl configuration.
74+
initConfig, err := configclient.New(ctx, path)
75+
if err != nil {
76+
return &Result{}, err
77+
} else if path != "" {
78+
// Set the image and providers override client
79+
p.overridesClient = initConfig
80+
}
81+
82+
overrideProviders := []configclient.Provider{}
83+
84+
if p.overridesClient != nil {
85+
providers, err := p.overridesClient.Providers().List()
86+
if err != nil {
87+
return &Result{}, err
88+
}
89+
90+
overrideProviders = providers
91+
}
92+
93+
reader, err := p.secretReader(ctx, overrideProviders...)
94+
if err != nil {
95+
return &Result{}, err
96+
}
97+
98+
// retrieves all custom providers using `FetchConfig` that aren't the current provider and adds them into MemoryReader.
99+
if err := p.providerLister(ctx, &clusterctlv1.ProviderList{}, loadCustomProvider(reader, p.provider, p.providerTypeMapper)); err != nil {
100+
return &Result{}, err
101+
}
102+
103+
// Load provider's secret and config url.
104+
p.configClient, err = configclient.New(ctx, "", configclient.InjectReader(reader))
105+
if err != nil {
106+
return &Result{}, wrapPhaseError(err, "failed to load the secret reader", operatorv1.ProviderInstalledCondition)
107+
}
108+
109+
// Get returns the configuration for the provider with a given name/type.
110+
// This is done using clusterctl internal API types.
111+
p.providerConfig, err = p.configClient.Providers().Get(p.provider.ProviderName(), p.providerTypeMapper(p.provider))
112+
if err != nil {
113+
return &Result{}, wrapPhaseError(err, operatorv1.UnknownProviderReason, operatorv1.ProviderInstalledCondition)
114+
}
115+
116+
return &Result{}, nil
117+
}
118+
119+
// secretReader use clusterctl MemoryReader structure to store the configuration variables
120+
// that are obtained from a secret and try to set fetch url config.
121+
func (p *PhaseReconciler) secretReader(ctx context.Context, providers ...configclient.Provider) (configclient.Reader, error) {
122+
log := ctrl.LoggerFrom(ctx)
123+
124+
mr := configclient.NewMemoryReader()
125+
126+
if err := mr.Init(ctx, ""); err != nil {
127+
return nil, err
128+
}
129+
130+
// Fetch configuration variables from the secret. See API field docs for more info.
131+
if err := initReaderVariables(ctx, p.ctrlClient, mr, p.provider); err != nil {
132+
return nil, err
133+
}
134+
135+
isCustom := true
136+
137+
for _, provider := range providers {
138+
if _, err := mr.AddProvider(provider.Name(), provider.Type(), provider.URL()); err != nil {
139+
return nil, err
140+
}
141+
142+
if provider.Type() == clusterctlv1.ProviderType(p.provider.GetType()) && provider.Name() == p.provider.ProviderName() {
143+
isCustom = false
144+
}
145+
}
146+
147+
// If provided store fetch config url in memory reader.
148+
if p.provider.GetSpec().FetchConfig != nil {
149+
if p.provider.GetSpec().FetchConfig.URL != "" {
150+
log.Info("Custom fetch configuration url was provided")
151+
return mr.AddProvider(p.provider.ProviderName(), p.providerTypeMapper(p.provider), p.provider.GetSpec().FetchConfig.URL)
152+
}
153+
154+
if p.provider.GetSpec().FetchConfig.Selector != nil {
155+
log.Info("Custom fetch configuration config map was provided")
156+
157+
// To register a new provider from the config map, we need to specify a URL with a valid
158+
// format. However, since we're using data from a local config map, URLs are not needed.
159+
// As a workaround, we add a fake but well-formatted URL.
160+
return mr.AddProvider(p.provider.ProviderName(), p.providerTypeMapper(p.provider), fakeURL)
161+
}
162+
163+
if isCustom && p.provider.GetSpec().FetchConfig.OCI != "" {
164+
return mr.AddProvider(p.provider.ProviderName(), p.providerTypeMapper(p.provider), fakeURL)
165+
}
166+
}
167+
168+
return mr, nil
169+
}
170+
171+
// loadCustomProvider loads the passed provider into the clusterctl configuration via the MemoryReader.
172+
func loadCustomProvider(reader configclient.Reader, current operatorv1.GenericProvider, mapper ProviderTypeMapper) ProviderOperation {
173+
mr, ok := reader.(*configclient.MemoryReader)
174+
currProviderName := current.GetName()
175+
currProviderType := current.GetType()
176+
177+
return func(provider operatorv1.GenericProvider) error {
178+
if !ok {
179+
return fmt.Errorf("unable to load custom provider, invalid reader passed")
180+
}
181+
182+
if provider.GetName() == currProviderName && provider.GetType() == currProviderType || provider.GetSpec().FetchConfig == nil {
183+
return nil
184+
}
185+
186+
_, err := mr.AddProvider(provider.ProviderName(), mapper(provider), cmp.Or(provider.GetSpec().FetchConfig.URL, fakeURL))
187+
188+
return err
189+
}
190+
}

0 commit comments

Comments
 (0)