Skip to content

Commit dab54de

Browse files
committed
feat: new imageRepository patch
1 parent f2d958b commit dab54de

File tree

12 files changed

+321
-19
lines changed

12 files changed

+321
-19
lines changed

api/v1alpha1/clusterconfig_types.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ type ClusterConfigSpec struct {
2525
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
2626
// Important: Run "make" to regenerate code after modifying this file
2727

28+
// +optional
29+
ImageRepository *ImageRepository `json:"imageRepository,omitempty"`
30+
2831
// +optional
2932
Proxy *HTTPProxy `json:"proxy,omitempty"`
3033

@@ -38,13 +41,30 @@ func (ClusterConfigSpec) VariableSchema() clusterv1.VariableSchema {
3841
Description: "Cluster configuration",
3942
Type: "object",
4043
Properties: map[string]clusterv1.JSONSchemaProps{
44+
"imageRepository": ImageRepository("").VariableSchema().OpenAPIV3Schema,
4145
"proxy": HTTPProxy{}.VariableSchema().OpenAPIV3Schema,
4246
"extraAPIServerCertSANs": ExtraAPIServerCertSANs{}.VariableSchema().OpenAPIV3Schema,
4347
},
4448
},
4549
}
4650
}
4751

52+
// ImageRepository required for overriding Kubernetes image repository.
53+
type ImageRepository string
54+
55+
func (ImageRepository) VariableSchema() clusterv1.VariableSchema {
56+
return clusterv1.VariableSchema{
57+
OpenAPIV3Schema: clusterv1.JSONSchemaProps{
58+
Description: "Sets the imageRepository used for the KubeadmControlPlane.",
59+
Type: "string",
60+
},
61+
}
62+
}
63+
64+
func (v ImageRepository) String() string {
65+
return string(v)
66+
}
67+
4868
// HTTPProxy required for providing proxy configuration.
4969
type HTTPProxy struct {
5070
// HTTP proxy.

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"github.com/d2iq-labs/capi-runtime-extensions/pkg/handlers/cni/calico"
3434
"github.com/d2iq-labs/capi-runtime-extensions/pkg/handlers/extraapiservercertsans"
3535
"github.com/d2iq-labs/capi-runtime-extensions/pkg/handlers/httpproxy"
36+
"github.com/d2iq-labs/capi-runtime-extensions/pkg/handlers/imagerepository"
3637
"github.com/d2iq-labs/capi-runtime-extensions/pkg/handlers/servicelbgc"
3738
)
3839

@@ -146,6 +147,9 @@ func main() {
146147

147148
auditpolicy.NewPatch(),
148149

150+
imagerepository.NewVariable(),
151+
imagerepository.NewPatch(imagerepository.VariableName),
152+
149153
clusterconfig.NewVariable(),
150154
mutation.NewMetaGeneratePatchesHandler(
151155
"clusterConfigPatch",
@@ -155,6 +159,7 @@ func main() {
155159
extraapiservercertsans.VariableName,
156160
),
157161
auditpolicy.NewPatch(),
162+
imagerepository.NewPatch(clusterconfig.VariableName, imagerepository.VariableName),
158163
),
159164
)
160165
if err := mgr.Add(runtimeWebhookServer); err != nil {

docs/content/cluster-config.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ spec:
3535
variables:
3636
- name: clusterConfig
3737
value:
38+
imageRepository: "my-registry.io/my-org/my-repo"
3839
extraAPIServerCertSANs:
3940
- a.b.c.example.com
4041
- d.e.f.example.com

docs/content/image-repository.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
title: "Image Repository"
3+
---
4+
5+
Override the container image repository used when pulling Kubernetes images.
6+
7+
To enable the API server certificate SANs enable the `extraapiservercertsansvars` and `extraapiservercertsanspatch`
8+
external patches on `ClusterClass`.
9+
10+
```yaml
11+
apiVersion: cluster.x-k8s.io/v1beta1
12+
kind: ClusterClass
13+
metadata:
14+
name: <NAME>
15+
spec:
16+
patches:
17+
- name: apiserver-cert-sans
18+
external:
19+
generateExtension: "imagerepositorypatch.capi-runtime-extensions"
20+
discoverVariablesExtension: "imagerepositoryvars.capi-runtime-extensions"
21+
```
22+
23+
On the cluster resource then specify desired certificate SANs values:
24+
25+
```yaml
26+
apiVersion: cluster.x-k8s.io/v1beta1
27+
kind: Cluster
28+
metadata:
29+
name: <NAME>
30+
spec:
31+
topology:
32+
variables:
33+
- name: imageRepository
34+
value: "my-registry.io/my-org/my-repo"
35+
```
36+
37+
Applying this configuration will result in the following value being set:
38+
39+
- KubeadmControlPlaneTemplate:
40+
- `/spec/template/spec/kubeadmConfigSpec/clusterConfiguration/imageRepository: my-registry.io/my-org/my-repo`

examples/capi-quickstart/cluster-class.yaml

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,6 @@ spec:
2323
kind: DockerClusterTemplate
2424
name: quick-start-cluster
2525
patches:
26-
- definitions:
27-
- jsonPatches:
28-
- op: add
29-
path: /spec/template/spec/kubeadmConfigSpec/clusterConfiguration/imageRepository
30-
valueFrom:
31-
variable: imageRepository
32-
selector:
33-
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
34-
kind: KubeadmControlPlaneTemplate
35-
matchResources:
36-
controlPlane: true
37-
description: Sets the imageRepository used for the KubeadmControlPlane.
38-
enabledIf: '{{ ne .imageRepository "" }}'
39-
name: imageRepository
4026
- definitions:
4127
- jsonPatches:
4228
- op: add

examples/capi-quickstart/cluster.yaml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
# Copyright 2023 D2iQ, Inc. All rights reserved.
2-
# SPDX-License-Identifier: Apache-2.0
3-
41
apiVersion: cluster.x-k8s.io/v1beta1
52
kind: Cluster
63
metadata:
@@ -23,8 +20,6 @@ spec:
2320
metadata: {}
2421
replicas: 1
2522
variables:
26-
- name: imageRepository
27-
value: ""
2823
- name: etcdImageTag
2924
value: ""
3025
- name: coreDNSImageTag

make/examples.mk

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
KUBERNETES_VERSION ?= v1.27.5
66

77
# gojq mutations:
8+
# - ClusterClass: Delete imageRepository inline patch
89
# - ClusterClass: Add cluster-config external patch
910
#
1011
# - Cluster: Add CNI label
@@ -20,6 +21,8 @@ examples.sync: kind.create clusterctl.init
2021
--kubernetes-version $(KUBERNETES_VERSION) \
2122
--control-plane-machine-count=1 \
2223
--worker-machine-count=1 | \
24+
gojq --yaml-input --yaml-output \
25+
'. | (select(.kind=="ClusterClass").spec.patches|= del(.[] | select(.name == "imageRepository")))' | \
2326
gojq --yaml-input --yaml-output \
2427
'. | (select(.kind=="ClusterClass").spec.patches|= .+ [{"name": "cluster-config", "external": {"generateExtension": "clusterconfigpatch.capi-runtime-extensions", "discoverVariablesExtension": "clusterconfigvars.capi-runtime-extensions"}}])' | \
2528
gojq --yaml-input --yaml-output \
@@ -33,6 +36,8 @@ examples.sync: kind.create clusterctl.init
3336
--worker-machine-count=1 | \
3437
gojq --yaml-input --yaml-output \
3538
'. | (select(.kind=="Cluster").metadata.labels["capiext.labs.d2iq.io/cni"]|="calico")' | \
39+
gojq --yaml-input --yaml-output \
40+
'. | (select(.kind=="Cluster").spec.topology.variables|= del(.[] | select(.name == "imageRepository")))' | \
3641
gojq --yaml-input --yaml-output \
3742
'. | (select(.kind=="Cluster").spec.topology.variables|= .+ [{"name": "clusterConfig", "value": {}}])' | \
3843
gojq --yaml-input --yaml-output \
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Copyright 2023 D2iQ, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package imagerepository
5+
6+
import (
7+
"context"
8+
_ "embed"
9+
10+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
11+
"k8s.io/apimachinery/pkg/runtime"
12+
"k8s.io/apimachinery/pkg/runtime/serializer"
13+
bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
14+
controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1"
15+
runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
16+
"sigs.k8s.io/cluster-api/exp/runtime/topologymutation"
17+
ctrl "sigs.k8s.io/controller-runtime"
18+
"sigs.k8s.io/controller-runtime/pkg/client"
19+
20+
"github.com/d2iq-labs/capi-runtime-extensions/api/v1alpha1"
21+
"github.com/d2iq-labs/capi-runtime-extensions/common/pkg/capi/clustertopology/handlers"
22+
"github.com/d2iq-labs/capi-runtime-extensions/common/pkg/capi/clustertopology/handlers/mutation"
23+
"github.com/d2iq-labs/capi-runtime-extensions/common/pkg/capi/clustertopology/patches"
24+
"github.com/d2iq-labs/capi-runtime-extensions/common/pkg/capi/clustertopology/patches/selectors"
25+
"github.com/d2iq-labs/capi-runtime-extensions/common/pkg/capi/clustertopology/variables"
26+
)
27+
28+
const (
29+
// HandlerNamePatch is the name of the inject handler.
30+
HandlerNamePatch = "ImageRepositoryPatch"
31+
)
32+
33+
type imageRepositoryPatchHandler struct {
34+
decoder runtime.Decoder
35+
variableName string
36+
variableFieldPath []string
37+
}
38+
39+
var (
40+
_ handlers.Named = &imageRepositoryPatchHandler{}
41+
_ mutation.GeneratePatches = &imageRepositoryPatchHandler{}
42+
)
43+
44+
func NewPatch(
45+
variableName string,
46+
variableFieldPath ...string,
47+
) *imageRepositoryPatchHandler {
48+
scheme := runtime.NewScheme()
49+
_ = bootstrapv1.AddToScheme(scheme)
50+
_ = controlplanev1.AddToScheme(scheme)
51+
return &imageRepositoryPatchHandler{
52+
decoder: serializer.NewCodecFactory(scheme).UniversalDecoder(
53+
controlplanev1.GroupVersion,
54+
bootstrapv1.GroupVersion,
55+
),
56+
variableName: variableName,
57+
variableFieldPath: variableFieldPath,
58+
}
59+
}
60+
61+
func (h *imageRepositoryPatchHandler) Name() string {
62+
return HandlerNamePatch
63+
}
64+
65+
func (h *imageRepositoryPatchHandler) GeneratePatches(
66+
ctx context.Context,
67+
req *runtimehooksv1.GeneratePatchesRequest,
68+
resp *runtimehooksv1.GeneratePatchesResponse,
69+
) {
70+
topologymutation.WalkTemplates(
71+
ctx,
72+
h.decoder,
73+
req,
74+
resp,
75+
func(
76+
ctx context.Context,
77+
obj runtime.Object,
78+
vars map[string]apiextensionsv1.JSON,
79+
holderRef runtimehooksv1.HolderReference,
80+
) error {
81+
log := ctrl.LoggerFrom(ctx).WithValues(
82+
"holderRef", holderRef,
83+
)
84+
85+
imageRepositoryVar, found, err := variables.Get[v1alpha1.ImageRepository](
86+
vars,
87+
h.variableName,
88+
h.variableFieldPath...,
89+
)
90+
if err != nil {
91+
return err
92+
}
93+
if !found {
94+
log.V(5).Info("imageRepository variable not defined")
95+
return nil
96+
}
97+
98+
log = log.WithValues(
99+
"variableName",
100+
h.variableName,
101+
"variableFieldPath",
102+
h.variableFieldPath,
103+
"variableValue",
104+
imageRepositoryVar,
105+
)
106+
107+
return patches.Generate(
108+
obj, vars, &holderRef, selectors.ControlPlane(), log,
109+
func(obj *controlplanev1.KubeadmControlPlaneTemplate) error {
110+
log.WithValues(
111+
"patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(),
112+
"patchedObjectName", client.ObjectKeyFromObject(obj),
113+
).Info("setting imageRepository in kubeadm config spec")
114+
115+
if obj.Spec.Template.Spec.KubeadmConfigSpec.ClusterConfiguration == nil {
116+
obj.Spec.Template.Spec.KubeadmConfigSpec.ClusterConfiguration = &bootstrapv1.ClusterConfiguration{}
117+
}
118+
obj.Spec.Template.Spec.KubeadmConfigSpec.ClusterConfiguration.ImageRepository = imageRepositoryVar.String()
119+
120+
return nil
121+
},
122+
)
123+
},
124+
)
125+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright 2023 D2iQ, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package imagerepository
5+
6+
import (
7+
"testing"
8+
9+
. "github.com/onsi/gomega"
10+
runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
11+
12+
"github.com/d2iq-labs/capi-runtime-extensions/api/v1alpha1"
13+
"github.com/d2iq-labs/capi-runtime-extensions/common/pkg/capi/clustertopology/handlers/mutation"
14+
"github.com/d2iq-labs/capi-runtime-extensions/common/pkg/testutils/capitest"
15+
)
16+
17+
func TestGeneratePatches(t *testing.T) {
18+
capitest.ValidateGeneratePatches(
19+
t,
20+
func() mutation.GeneratePatches { return NewPatch(VariableName) },
21+
capitest.PatchTestDef{
22+
Name: "unset variable",
23+
},
24+
capitest.PatchTestDef{
25+
Name: "imageRepository set",
26+
Vars: []runtimehooksv1.Variable{
27+
capitest.VariableWithValue(
28+
VariableName,
29+
v1alpha1.ImageRepository("my-registry.io/my-org/my-repo"),
30+
),
31+
},
32+
RequestItem: capitest.NewKubeadmControlPlaneTemplateRequestItem(),
33+
ExpectedPatchMatchers: []capitest.JSONPatchMatcher{{
34+
Operation: "add",
35+
Path: "/spec/template/spec/kubeadmConfigSpec/clusterConfiguration",
36+
ValueMatcher: HaveKeyWithValue(
37+
"imageRepository",
38+
"my-registry.io/my-org/my-repo",
39+
),
40+
}},
41+
},
42+
)
43+
}

0 commit comments

Comments
 (0)