Skip to content

feat: new Kubernetes image registry patch #149

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 14, 2023
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
23 changes: 23 additions & 0 deletions api/v1alpha1/clusterconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ type ClusterConfigSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file

// +optional
KubernetesImageRegistry *KubernetesImageRegistry `json:"kubernetesImageRegistry,omitempty"`

// +optional
Proxy *HTTPProxy `json:"proxy,omitempty"`

Expand All @@ -38,13 +41,33 @@ func (ClusterConfigSpec) VariableSchema() clusterv1.VariableSchema {
Description: "Cluster configuration",
Type: "object",
Properties: map[string]clusterv1.JSONSchemaProps{
"kubernetesImageRegistry": KubernetesImageRegistry(
"",
).VariableSchema().
OpenAPIV3Schema,
"proxy": HTTPProxy{}.VariableSchema().OpenAPIV3Schema,
"extraAPIServerCertSANs": ExtraAPIServerCertSANs{}.VariableSchema().OpenAPIV3Schema,
},
},
}
}

// KubernetesImageRegistry required for overriding Kubernetes image registry.
type KubernetesImageRegistry string

func (KubernetesImageRegistry) VariableSchema() clusterv1.VariableSchema {
return clusterv1.VariableSchema{
OpenAPIV3Schema: clusterv1.JSONSchemaProps{
Description: "Sets the Kubernetes image registry used for the KubeadmControlPlane.",
Type: "string",
},
}
}

func (v KubernetesImageRegistry) String() string {
return string(v)
}

// HTTPProxy required for providing proxy configuration.
type HTTPProxy struct {
// HTTP proxy.
Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/d2iq-labs/capi-runtime-extensions/pkg/handlers/cni/calico"
"github.com/d2iq-labs/capi-runtime-extensions/pkg/handlers/extraapiservercertsans"
"github.com/d2iq-labs/capi-runtime-extensions/pkg/handlers/httpproxy"
"github.com/d2iq-labs/capi-runtime-extensions/pkg/handlers/kubernetesimageregistry"
"github.com/d2iq-labs/capi-runtime-extensions/pkg/handlers/servicelbgc"
)

Expand Down Expand Up @@ -125,6 +126,9 @@ func main() {

auditpolicy.NewPatch(),

kubernetesimageregistry.NewVariable(),
kubernetesimageregistry.NewPatch(kubernetesimageregistry.VariableName),

clusterconfig.NewVariable(),
mutation.NewMetaGeneratePatchesHandler(
"clusterConfigPatch",
Expand All @@ -134,6 +138,10 @@ func main() {
extraapiservercertsans.VariableName,
),
auditpolicy.NewPatch(),
kubernetesimageregistry.NewPatch(
clusterconfig.VariableName,
kubernetesimageregistry.VariableName,
),
),
)
if err := mgr.Add(runtimeWebhookServer); err != nil {
Expand Down
1 change: 1 addition & 0 deletions docs/content/cluster-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ spec:
variables:
- name: clusterConfig
value:
kubernetesImageRegistry: "my-registry.io/my-org/my-repo"
extraAPIServerCertSANs:
- a.b.c.example.com
- d.e.f.example.com
Expand Down
39 changes: 39 additions & 0 deletions docs/content/kubernetes-image-registry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
title: "Kubernete Image Registry"
---

Override the container image registry used when pulling Kubernetes images.

To enable this handler set the `imageregistrypatch` and `imageregistryvars` external patches on `ClusterClass`.

```yaml
apiVersion: cluster.x-k8s.io/v1beta1
kind: ClusterClass
metadata:
name: <NAME>
spec:
patches:
- name: image-registry
external:
generateExtension: "imageregistrypatch.capi-runtime-extensions"
discoverVariablesExtension: "imageregistryvars.capi-runtime-extensions"
```

On the cluster resource then specify desired Kubernetes image registry value:

```yaml
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
name: <NAME>
spec:
topology:
variables:
- name: kubernetesImageRegistry
value: "my-registry.io/my-org/my-repo"
```

Applying this configuration will result in the following value being set:

- KubeadmControlPlaneTemplate:
- `/spec/template/spec/kubeadmConfigSpec/clusterConfiguration/imageRepository: my-registry.io/my-org/my-repo`
125 changes: 125 additions & 0 deletions pkg/handlers/kubernetesimageregistry/inject.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright 2023 D2iQ, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package kubernetesimageregistry

import (
"context"
_ "embed"

apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1"
runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
"sigs.k8s.io/cluster-api/exp/runtime/topologymutation"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/d2iq-labs/capi-runtime-extensions/api/v1alpha1"
"github.com/d2iq-labs/capi-runtime-extensions/common/pkg/capi/clustertopology/handlers"
"github.com/d2iq-labs/capi-runtime-extensions/common/pkg/capi/clustertopology/handlers/mutation"
"github.com/d2iq-labs/capi-runtime-extensions/common/pkg/capi/clustertopology/patches"
"github.com/d2iq-labs/capi-runtime-extensions/common/pkg/capi/clustertopology/patches/selectors"
"github.com/d2iq-labs/capi-runtime-extensions/common/pkg/capi/clustertopology/variables"
)

const (
// HandlerNamePatch is the name of the inject handler.
HandlerNamePatch = "ImageRegistryPatch"
)

type imageRegistryPatchHandler struct {
decoder runtime.Decoder
variableName string
variableFieldPath []string
}

var (
_ handlers.Named = &imageRegistryPatchHandler{}
_ mutation.GeneratePatches = &imageRegistryPatchHandler{}
)

func NewPatch(
variableName string,
variableFieldPath ...string,
) *imageRegistryPatchHandler {
scheme := runtime.NewScheme()
_ = bootstrapv1.AddToScheme(scheme)
_ = controlplanev1.AddToScheme(scheme)
return &imageRegistryPatchHandler{
decoder: serializer.NewCodecFactory(scheme).UniversalDecoder(
controlplanev1.GroupVersion,
bootstrapv1.GroupVersion,
),
variableName: variableName,
variableFieldPath: variableFieldPath,
}
}

func (h *imageRegistryPatchHandler) Name() string {
return HandlerNamePatch
}

func (h *imageRegistryPatchHandler) GeneratePatches(
ctx context.Context,
req *runtimehooksv1.GeneratePatchesRequest,
resp *runtimehooksv1.GeneratePatchesResponse,
) {
topologymutation.WalkTemplates(
ctx,
h.decoder,
req,
resp,
func(
ctx context.Context,
obj runtime.Object,
vars map[string]apiextensionsv1.JSON,
holderRef runtimehooksv1.HolderReference,
) error {
log := ctrl.LoggerFrom(ctx).WithValues(
"holderRef", holderRef,
)

imageRegistryVar, found, err := variables.Get[v1alpha1.KubernetesImageRegistry](
vars,
h.variableName,
h.variableFieldPath...,
)
if err != nil {
return err
}
if !found {
log.V(5).Info("kubernetesImageRegistry variable not defined")
return nil
}

log = log.WithValues(
"variableName",
h.variableName,
"variableFieldPath",
h.variableFieldPath,
"variableValue",
imageRegistryVar,
)

return patches.Generate(
obj, vars, &holderRef, selectors.ControlPlane(), log,
func(obj *controlplanev1.KubeadmControlPlaneTemplate) error {
log.WithValues(
"patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(),
"patchedObjectName", client.ObjectKeyFromObject(obj),
).Info("setting imageRepository in kubeadm config spec")

if obj.Spec.Template.Spec.KubeadmConfigSpec.ClusterConfiguration == nil {
obj.Spec.Template.Spec.KubeadmConfigSpec.ClusterConfiguration = &bootstrapv1.ClusterConfiguration{}
}
obj.Spec.Template.Spec.KubeadmConfigSpec.ClusterConfiguration.ImageRepository = imageRegistryVar.String()

return nil
},
)
},
)
}
43 changes: 43 additions & 0 deletions pkg/handlers/kubernetesimageregistry/inject_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2023 D2iQ, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package kubernetesimageregistry

import (
"testing"

. "github.com/onsi/gomega"
runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"

"github.com/d2iq-labs/capi-runtime-extensions/api/v1alpha1"
"github.com/d2iq-labs/capi-runtime-extensions/common/pkg/capi/clustertopology/handlers/mutation"
"github.com/d2iq-labs/capi-runtime-extensions/common/pkg/testutils/capitest"
)

func TestGeneratePatches(t *testing.T) {
capitest.ValidateGeneratePatches(
t,
func() mutation.GeneratePatches { return NewPatch(VariableName) },
capitest.PatchTestDef{
Name: "unset variable",
},
capitest.PatchTestDef{
Name: "kubernetesImageRegistry set",
Vars: []runtimehooksv1.Variable{
capitest.VariableWithValue(
VariableName,
v1alpha1.KubernetesImageRegistry("my-registry.io/my-org/my-repo"),
),
},
RequestItem: capitest.NewKubeadmControlPlaneTemplateRequestItem(),
ExpectedPatchMatchers: []capitest.JSONPatchMatcher{{
Operation: "add",
Path: "/spec/template/spec/kubeadmConfigSpec/clusterConfiguration",
ValueMatcher: HaveKeyWithValue(
"imageRepository",
"my-registry.io/my-org/my-repo",
),
}},
},
)
}
51 changes: 51 additions & 0 deletions pkg/handlers/kubernetesimageregistry/variables.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2023 D2iQ, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package kubernetesimageregistry

import (
"context"

clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"

"github.com/d2iq-labs/capi-runtime-extensions/api/v1alpha1"
"github.com/d2iq-labs/capi-runtime-extensions/common/pkg/capi/clustertopology/handlers"
"github.com/d2iq-labs/capi-runtime-extensions/common/pkg/capi/clustertopology/handlers/mutation"
)

var (
_ handlers.Named = &imageRegistryVariableHandler{}
_ mutation.DiscoverVariables = &imageRegistryVariableHandler{}
)

const (
// VariableName is http proxy external patch variable name.
VariableName = "kubernetesImageRegistry"

// HandlerNameVariable is the name of the variable handler.
HandlerNameVariable = "ImageRegistryVars"
)

func NewVariable() *imageRegistryVariableHandler {
return &imageRegistryVariableHandler{}
}

type imageRegistryVariableHandler struct{}

func (h *imageRegistryVariableHandler) Name() string {
return HandlerNameVariable
}

func (h *imageRegistryVariableHandler) DiscoverVariables(
ctx context.Context,
_ *runtimehooksv1.DiscoverVariablesRequest,
resp *runtimehooksv1.DiscoverVariablesResponse,
) {
resp.Variables = append(resp.Variables, clusterv1.ClusterClassVariable{
Name: VariableName,
Required: false,
Schema: v1alpha1.KubernetesImageRegistry("").VariableSchema(),
})
resp.SetStatus(runtimehooksv1.ResponseStatusSuccess)
}
26 changes: 26 additions & 0 deletions pkg/handlers/kubernetesimageregistry/variables_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2023 D2iQ, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package kubernetesimageregistry

import (
"testing"

"k8s.io/utils/ptr"

"github.com/d2iq-labs/capi-runtime-extensions/api/v1alpha1"
"github.com/d2iq-labs/capi-runtime-extensions/common/pkg/testutils/capitest"
)

func TestVariableValidation(t *testing.T) {
capitest.ValidateDiscoverVariables(
t,
VariableName,
ptr.To(v1alpha1.KubernetesImageRegistry("").VariableSchema()),
NewVariable,
capitest.VariableTestDef{
Name: "set",
Vals: "my-registry.io/my-org/my-repo",
},
)
}