Skip to content

Commit 2672f91

Browse files
committed
feat: Add DNS patch
1 parent 5a05bc4 commit 2672f91

File tree

12 files changed

+512
-0
lines changed

12 files changed

+512
-0
lines changed

charts/capi-runtime-extensions/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ A Helm chart for capi-runtime-extensions
3838
| handlers.CalicoCNI.defaultPodSubnet | string | `"192.168.0.0/16"` | |
3939
| handlers.CalicoCNI.defaultTigeraOperatorConfigMap.name | string | `"tigera-operator"` | |
4040
| handlers.CalicoCNI.enabled | bool | `true` | |
41+
| handlers.DNSPatch.enabled | bool | `true` | |
42+
| handlers.DNSVars.enabled | bool | `true` | |
4143
| handlers.ExtraAPIServerCertSANsPatch.enabled | bool | `true` | |
4244
| handlers.ExtraAPIServerCertSANsVars.enabled | bool | `true` | |
4345
| handlers.HTTPProxyPatch.enabled | bool | `true` | |

charts/capi-runtime-extensions/values.schema.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,24 @@
149149
"default": true
150150
}
151151
}
152+
},
153+
"DNSVars": {
154+
"type": "object",
155+
"properties": {
156+
"enabled": {
157+
"type": "boolean",
158+
"default": true
159+
}
160+
}
161+
},
162+
"DNSPatch": {
163+
"type": "object",
164+
"properties": {
165+
"enabled": {
166+
"type": "boolean",
167+
"default": true
168+
}
169+
}
152170
}
153171
}
154172
},

charts/capi-runtime-extensions/values.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ handlers:
2525
enabled: true
2626
ExtraAPIServerCertSANsPatch:
2727
enabled: true
28+
DNSVars:
29+
enabled: true
30+
DNSPatch:
31+
enabled: true
2832

2933
deployment:
3034
replicas: 1

cmd/capi-runtime-extensions/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/d2iq-labs/capi-runtime-extensions/common/pkg/server"
2828
"github.com/d2iq-labs/capi-runtime-extensions/internal/controllermanager"
2929
"github.com/d2iq-labs/capi-runtime-extensions/pkg/handlers/cni/calico"
30+
"github.com/d2iq-labs/capi-runtime-extensions/pkg/handlers/dns"
3031
"github.com/d2iq-labs/capi-runtime-extensions/pkg/handlers/extraapiservercertsans"
3132
"github.com/d2iq-labs/capi-runtime-extensions/pkg/handlers/httpproxy"
3233
"github.com/d2iq-labs/capi-runtime-extensions/pkg/handlers/servicelbgc"
@@ -82,6 +83,8 @@ func main() {
8283
httpproxy.NewPatch(),
8384
extraapiservercertsans.NewVariable(),
8485
extraapiservercertsans.NewPatch(),
86+
dns.NewVariable(),
87+
dns.NewPatch(),
8588
)
8689

8790
// Initialize and parse command line flags.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2023 D2iQ, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package patterns
5+
6+
const (
7+
// See: https://github.com/distribution/reference/blob/v0.5.0/regexp.go#L53
8+
NameSeparator = `(?:[._]|__|[-]+)`
9+
10+
// See: https://github.com/distribution/reference/blob/v0.5.0/regexp.go#L123C18-L123C65
11+
PathComponent = Alphanumeric + `(` + NameSeparator + Alphanumeric + `)*`
12+
13+
// See: https://github.com/distribution/reference/blob/v0.5.0/regexp.go#L130
14+
ImageRegistry = `(` + DNS1123Subdomain + `|` + IPv6 + `)` + OptionalPort + `(/` + PathComponent + `)*`
15+
)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright 2023 D2iQ, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package patterns
5+
6+
const (
7+
// See: https://github.com/distribution/reference/blob/v0.5.0/regexp.go#L44C2-L44C28
8+
Alphanumeric = `[a-z0-9]+`
9+
)

common/pkg/openapi/patterns/net.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2023 D2iQ, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package patterns
5+
6+
const (
7+
// See: https://github.com/distribution/reference/blob/v0.5.0/regexp.go#L91
8+
IPv6 = `\[(?:[a-fA-F0-9:]+)\]`
9+
10+
Port = `:[0-9]+`
11+
12+
OptionalPort = `(` + Port + `)?`
13+
)

docs/content/dns.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
---
2+
title: "DNS"
3+
---
4+
5+
If the API server can be accessed by alternative DNS addresses then setting additional SANs on the API server
6+
certificate is necessary in order for clients to successfully validate the API server certificate.
7+
8+
To enable the API server certificate SANs enable the `dnsvars` and `dnspatch`
9+
external patches on `ClusterClass`.
10+
11+
```yaml
12+
apiVersion: cluster.x-k8s.io/v1beta1
13+
kind: ClusterClass
14+
metadata:
15+
name: <NAME>
16+
spec:
17+
patches:
18+
- name: dns
19+
external:
20+
generateExtension: "dnspatch.<external-config-name>"
21+
discoverVariablesExtension: "dnsvars.<external-config-name>"
22+
```
23+
24+
On the cluster resource then specify desired DNS image repo:
25+
26+
```yaml
27+
apiVersion: cluster.x-k8s.io/v1beta1
28+
kind: Cluster
29+
metadata:
30+
name: <NAME>
31+
spec:
32+
topology:
33+
variables:
34+
- name: dns
35+
value:
36+
imageRepository: a.b.c.example.com
37+
```
38+
39+
Applying this configuration will result in the DNS image repository being correctly set in the
40+
`KubeadmControlPlaneTemplate`.
41+
42+
This hook is enabled by default, and can be explicitly disabled by omitting the `DNSVars`
43+
and `DNSPatch` hook from the `--runtimehooks.enabled-handlers` flag.
44+
45+
If deploying via Helm, then this can be disabled by setting `handlers.DNSVars.enabled=false` and
46+
`handlers.DNSPatch.enabled=false`.

pkg/handlers/dns/inject.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright 2023 D2iQ, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package dns
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/common/pkg/handlers"
21+
"github.com/d2iq-labs/capi-runtime-extensions/pkg/capi/clustertopology/patches"
22+
"github.com/d2iq-labs/capi-runtime-extensions/pkg/capi/clustertopology/patches/selectors"
23+
"github.com/d2iq-labs/capi-runtime-extensions/pkg/capi/clustertopology/variables"
24+
)
25+
26+
const (
27+
// HandlerNamePatch is the name of the inject handler.
28+
HandlerNamePatch = "DNSPatch"
29+
)
30+
31+
type dnsPatchHandler struct {
32+
decoder runtime.Decoder
33+
}
34+
35+
var (
36+
_ handlers.NamedHandler = &dnsPatchHandler{}
37+
_ handlers.GeneratePatchesMutationHandler = &dnsPatchHandler{}
38+
)
39+
40+
func NewPatch() *dnsPatchHandler {
41+
scheme := runtime.NewScheme()
42+
_ = bootstrapv1.AddToScheme(scheme)
43+
_ = controlplanev1.AddToScheme(scheme)
44+
return &dnsPatchHandler{
45+
decoder: serializer.NewCodecFactory(scheme).UniversalDecoder(
46+
controlplanev1.GroupVersion,
47+
bootstrapv1.GroupVersion,
48+
),
49+
}
50+
}
51+
52+
func (h *dnsPatchHandler) Name() string {
53+
return HandlerNamePatch
54+
}
55+
56+
func (h *dnsPatchHandler) GeneratePatches(
57+
ctx context.Context,
58+
req *runtimehooksv1.GeneratePatchesRequest,
59+
resp *runtimehooksv1.GeneratePatchesResponse,
60+
) {
61+
topologymutation.WalkTemplates(
62+
ctx,
63+
h.decoder,
64+
req,
65+
resp,
66+
func(
67+
ctx context.Context,
68+
obj runtime.Object,
69+
vars map[string]apiextensionsv1.JSON,
70+
holderRef runtimehooksv1.HolderReference,
71+
) error {
72+
log := ctrl.LoggerFrom(ctx).WithValues(
73+
"holderRef", holderRef,
74+
)
75+
76+
dnsVar, found, err := variables.Get[DNSVariable](
77+
vars,
78+
VariableName,
79+
)
80+
if err != nil {
81+
return err
82+
}
83+
if !found {
84+
log.V(5).Info("DNS image repo variable not defined")
85+
return nil
86+
}
87+
88+
log = log.WithValues(
89+
"variableName",
90+
VariableName,
91+
"variableValue",
92+
dnsVar,
93+
)
94+
95+
return patches.Generate(
96+
obj, vars, &holderRef, selectors.ControlPlane(), log,
97+
func(obj *controlplanev1.KubeadmControlPlaneTemplate) error {
98+
log.WithValues(
99+
"patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(),
100+
"patchedObjectName", client.ObjectKeyFromObject(obj),
101+
).Info("adding DNS image repo in kubeadm config spec")
102+
103+
if obj.Spec.Template.Spec.KubeadmConfigSpec.ClusterConfiguration == nil {
104+
obj.Spec.Template.Spec.KubeadmConfigSpec.ClusterConfiguration = &bootstrapv1.ClusterConfiguration{}
105+
}
106+
obj.Spec.Template.Spec.KubeadmConfigSpec.ClusterConfiguration.DNS.ImageRepository = dnsVar.ImageRepository
107+
108+
return nil
109+
},
110+
)
111+
},
112+
)
113+
}

pkg/handlers/dns/inject_test.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// Copyright 2023 D2iQ, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package dns
5+
6+
import (
7+
"bytes"
8+
"context"
9+
"encoding/json"
10+
"testing"
11+
12+
. "github.com/onsi/gomega"
13+
. "github.com/onsi/gomega/gstruct"
14+
"gomodules.xyz/jsonpatch/v2"
15+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
16+
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17+
"k8s.io/apimachinery/pkg/runtime"
18+
"k8s.io/apimachinery/pkg/types"
19+
controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1"
20+
runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
21+
)
22+
23+
func TestGeneratePatches(t *testing.T) {
24+
g := NewWithT(t)
25+
h := NewPatch()
26+
req := &runtimehooksv1.GeneratePatchesRequest{}
27+
resp := &runtimehooksv1.GeneratePatchesResponse{}
28+
h.GeneratePatches(context.Background(), req, resp)
29+
g.Expect(resp.Status).To(Equal(runtimehooksv1.ResponseStatusSuccess))
30+
g.Expect(resp.Items).To(BeEmpty())
31+
}
32+
33+
func TestGeneratePatches_KubeadmControlPlaneTemplate(t *testing.T) {
34+
g := NewWithT(t)
35+
h := NewPatch()
36+
req := &runtimehooksv1.GeneratePatchesRequest{
37+
Variables: []runtimehooksv1.Variable{
38+
newVariable(
39+
VariableName,
40+
DNSVariable{
41+
ImageRepository: "a.b.c.example.com",
42+
},
43+
),
44+
},
45+
Items: []runtimehooksv1.GeneratePatchesRequestItem{
46+
requestItem(
47+
"1",
48+
&controlplanev1.KubeadmControlPlaneTemplate{
49+
TypeMeta: v1.TypeMeta{
50+
Kind: "KubeadmControlPlaneTemplate",
51+
APIVersion: controlplanev1.GroupVersion.String(),
52+
},
53+
},
54+
&runtimehooksv1.HolderReference{
55+
Kind: "Cluster",
56+
FieldPath: "spec.controlPlaneRef",
57+
},
58+
),
59+
},
60+
}
61+
resp := &runtimehooksv1.GeneratePatchesResponse{}
62+
h.GeneratePatches(context.Background(), req, resp)
63+
g.Expect(resp.Status).To(Equal(runtimehooksv1.ResponseStatusSuccess))
64+
g.Expect(resp.Items).To(ContainElement(MatchFields(IgnoreExtras, Fields{
65+
"UID": Equal(types.UID("1")),
66+
"PatchType": Equal(runtimehooksv1.JSONPatchType),
67+
"Patch": WithTransform(
68+
func(data []byte) ([]jsonpatch.Operation, error) {
69+
operations := []jsonpatch.Operation{}
70+
if err := json.Unmarshal(data, &operations); err != nil {
71+
return nil, err
72+
}
73+
return operations, nil
74+
},
75+
ConsistOf(MatchAllFields(Fields{
76+
"Operation": Equal("add"),
77+
"Path": Equal("/spec/template/spec/kubeadmConfigSpec/clusterConfiguration"),
78+
"Value": HaveKeyWithValue(
79+
"dns",
80+
HaveKeyWithValue(
81+
"imageRepository",
82+
"a.b.c.example.com",
83+
),
84+
),
85+
})),
86+
),
87+
})))
88+
}
89+
90+
func toJSON(v any) []byte {
91+
data, err := json.Marshal(v)
92+
if err != nil {
93+
panic(err)
94+
}
95+
compacted := &bytes.Buffer{}
96+
if err := json.Compact(compacted, data); err != nil {
97+
panic(err)
98+
}
99+
return compacted.Bytes()
100+
}
101+
102+
// requestItem returns a GeneratePatchesRequestItem with the given uid, variables and object.
103+
func requestItem(
104+
uid string,
105+
object any,
106+
holderRef *runtimehooksv1.HolderReference,
107+
) runtimehooksv1.GeneratePatchesRequestItem {
108+
return runtimehooksv1.GeneratePatchesRequestItem{
109+
UID: types.UID(uid),
110+
Object: runtime.RawExtension{
111+
Raw: toJSON(object),
112+
},
113+
HolderReference: *holderRef,
114+
}
115+
}
116+
117+
// newVariable returns a runtimehooksv1.Variable with the passed name and value.
118+
func newVariable(name string, value any) runtimehooksv1.Variable {
119+
return runtimehooksv1.Variable{
120+
Name: name,
121+
Value: apiextensionsv1.JSON{Raw: toJSON(value)},
122+
}
123+
}

0 commit comments

Comments
 (0)