Skip to content
This repository was archived by the owner on Apr 11, 2024. It is now read-only.

Commit 9f56b56

Browse files
committed
feat: Add user configuration for all providers
- Defines a cluster-level variable for defining one or more users - Patches bootstrap templates for control plane and worker node pools with user configuration
1 parent 89f8887 commit 9f56b56

File tree

9 files changed

+431
-0
lines changed

9 files changed

+431
-0
lines changed

api/v1alpha1/clusterconfig_types.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ type GenericClusterConfig struct {
9898

9999
// +optional
100100
Addons *Addons `json:"addons,omitempty"`
101+
102+
// +optional
103+
Users Users `json:"users,omitempty"`
101104
}
102105

103106
func (s GenericClusterConfig) VariableSchema() clusterv1.VariableSchema { //nolint:gocritic,lll // Passed by value for no potential side-effect.
@@ -116,6 +119,7 @@ func (s GenericClusterConfig) VariableSchema() clusterv1.VariableSchema { //noli
116119
OpenAPIV3Schema,
117120
"imageRegistries": ImageRegistries{}.VariableSchema().OpenAPIV3Schema,
118121
"globalImageRegistryMirror": GlobalImageRegistryMirror{}.VariableSchema().OpenAPIV3Schema,
122+
"users": Users{}.VariableSchema().OpenAPIV3Schema,
119123
},
120124
},
121125
}
@@ -344,6 +348,66 @@ func (ImageRegistries) VariableSchema() clusterv1.VariableSchema {
344348
}
345349
}
346350

351+
type Users []User
352+
353+
func (Users) VariableSchema() clusterv1.VariableSchema {
354+
return clusterv1.VariableSchema{
355+
OpenAPIV3Schema: clusterv1.JSONSchemaProps{
356+
Description: "Users to add to the machine",
357+
Type: "array",
358+
Items: ptr.To(User{}.VariableSchema().OpenAPIV3Schema),
359+
},
360+
}
361+
}
362+
363+
// User defines the input for a generated user in cloud-init.
364+
type User struct {
365+
// Name specifies the user name
366+
Name string `json:"name"`
367+
368+
// Passwd specifies a hashed password for the user
369+
// +optional
370+
Passwd *string `json:"passwd,omitempty"`
371+
372+
// SSHAuthorizedKeys specifies a list of ssh authorized keys for the user
373+
// +optional
374+
SSHAuthorizedKeys []string `json:"sshAuthorizedKeys,omitempty"`
375+
376+
// Sudo specifies a sudo role for the user
377+
// +optional
378+
Sudo *string `json:"sudo,omitempty"`
379+
}
380+
381+
func (User) VariableSchema() clusterv1.VariableSchema {
382+
return clusterv1.VariableSchema{
383+
OpenAPIV3Schema: clusterv1.JSONSchemaProps{
384+
Type: "object",
385+
Properties: map[string]clusterv1.JSONSchemaProps{
386+
"name": {
387+
Description: "The username",
388+
Type: "string",
389+
},
390+
"passwd": {
391+
Description: "The hashed password for the user",
392+
Type: "string",
393+
},
394+
"sshAuthorizedKeys": {
395+
Description: "A list of SSH authorized keys for this user",
396+
Type: "array",
397+
Items: &clusterv1.JSONSchemaProps{
398+
// No description, because the one for the parent array is enough.
399+
Type: "string",
400+
},
401+
},
402+
"sudo": {
403+
Description: "The sudo rule that applies to this user",
404+
Type: "string",
405+
},
406+
},
407+
},
408+
}
409+
}
410+
347411
func init() {
348412
SchemeBuilder.Register(&ClusterConfig{})
349413
}

api/v1alpha1/zz_generated.deepcopy.go

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

pkg/handlers/aws/mutation/metapatch_handler_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ import (
3737
kubernetesimagerepositorytests "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/kubernetesimagerepository/tests"
3838
"github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/mirrors"
3939
globalimageregistrymirrortests "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/mirrors/tests"
40+
"github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/users"
41+
userstests "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/users/tests"
4042
"github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/workerconfig"
4143
)
4244

@@ -188,4 +190,11 @@ func TestGeneratePatches(t *testing.T) {
188190
v1alpha1.AWSVariableName,
189191
controlplaneloadbalancer.VariableName,
190192
)
193+
194+
userstests.TestGeneratePatches(
195+
t,
196+
metaPatchGeneratorFunc(mgr),
197+
clusterconfig.MetaVariableName,
198+
users.VariableName,
199+
)
191200
}

pkg/handlers/docker/mutation/metapatch_handler_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import (
2727
kubernetesimagerepositorytests "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/kubernetesimagerepository/tests"
2828
"github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/mirrors"
2929
globalimageregistrymirrortests "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/mirrors/tests"
30+
"github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/users"
31+
userstests "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/users/tests"
3032
"github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/workerconfig"
3133
)
3234

@@ -112,4 +114,11 @@ func TestGeneratePatches(t *testing.T) {
112114
clusterconfig.MetaVariableName,
113115
mirrors.GlobalMirrorVariableName,
114116
)
117+
118+
userstests.TestGeneratePatches(
119+
t,
120+
metaPatchGeneratorFunc(mgr),
121+
clusterconfig.MetaVariableName,
122+
users.VariableName,
123+
)
115124
}

pkg/handlers/generic/mutation/handlers.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/imageregistries/credentials"
1616
"github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/kubernetesimagerepository"
1717
"github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/mirrors"
18+
"github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/users"
1819
)
1920

2021
// MetaMutators returns all generic patch handlers.
@@ -28,5 +29,6 @@ func MetaMutators(mgr manager.Manager) []mutation.MetaMutator {
2829
credentials.NewPatch(mgr.GetClient()),
2930
mirrors.NewPatch(mgr.GetClient()),
3031
calico.NewPatch(),
32+
users.NewPatch(),
3133
}
3234
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Copyright 2023 D2iQ, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters,verbs=watch;list;get
5+
package users
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// Copyright 2023 D2iQ, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package users
5+
6+
import (
7+
"context"
8+
9+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
10+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
11+
"k8s.io/utils/ptr"
12+
bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
13+
controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1"
14+
runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
15+
ctrl "sigs.k8s.io/controller-runtime"
16+
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
17+
18+
"github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/api/v1alpha1"
19+
"github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/patches"
20+
"github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/patches/selectors"
21+
"github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/variables"
22+
"github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/clusterconfig"
23+
)
24+
25+
const (
26+
// VariableName is the external patch variable name.
27+
VariableName = "users"
28+
)
29+
30+
type usersPatchHandler struct {
31+
variableName string
32+
variableFieldPath []string
33+
}
34+
35+
func NewPatch() *usersPatchHandler {
36+
return newUsersPatchHandler(
37+
clusterconfig.MetaVariableName,
38+
VariableName)
39+
}
40+
41+
func newUsersPatchHandler(
42+
variableName string,
43+
variableFieldPath ...string,
44+
) *usersPatchHandler {
45+
return &usersPatchHandler{
46+
variableName: variableName,
47+
variableFieldPath: variableFieldPath,
48+
}
49+
}
50+
51+
func (h *usersPatchHandler) Mutate(
52+
ctx context.Context,
53+
obj *unstructured.Unstructured,
54+
vars map[string]apiextensionsv1.JSON,
55+
holderRef runtimehooksv1.HolderReference,
56+
_ ctrlclient.ObjectKey,
57+
) error {
58+
log := ctrl.LoggerFrom(ctx, "holderRef", holderRef)
59+
60+
usersVariable, found, err := variables.Get[v1alpha1.Users](
61+
vars,
62+
h.variableName,
63+
h.variableFieldPath...,
64+
)
65+
if err != nil {
66+
return err
67+
}
68+
if !found {
69+
log.V(5).Info("users variable not defined")
70+
return nil
71+
}
72+
73+
log = log.WithValues(
74+
"variableName",
75+
h.variableName,
76+
"variableFieldPath",
77+
h.variableFieldPath,
78+
"variableValue",
79+
usersVariable,
80+
)
81+
82+
if err := patches.MutateIfApplicable(
83+
obj, vars, &holderRef, selectors.ControlPlane(), log,
84+
func(obj *controlplanev1.KubeadmControlPlaneTemplate) error {
85+
log.WithValues(
86+
"patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(),
87+
"patchedObjectName", ctrlclient.ObjectKeyFromObject(obj),
88+
).Info("setting users in control plane kubeadm config template")
89+
bootstrapUsers := []bootstrapv1.User{}
90+
for _, userFromVariable := range usersVariable {
91+
bootstrapUsers = append(bootstrapUsers, generateBootstrapUser(userFromVariable))
92+
}
93+
obj.Spec.Template.Spec.KubeadmConfigSpec.Users = bootstrapUsers
94+
return nil
95+
}); err != nil {
96+
return err
97+
}
98+
99+
if err := patches.MutateIfApplicable(
100+
obj, vars, &holderRef, selectors.WorkersKubeadmConfigTemplateSelector(), log,
101+
func(obj *bootstrapv1.KubeadmConfigTemplate) error {
102+
log.WithValues(
103+
"patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(),
104+
"patchedObjectName", ctrlclient.ObjectKeyFromObject(obj),
105+
).Info("setting users in worker node kubeadm config template")
106+
bootstrapUsers := []bootstrapv1.User{}
107+
for _, userFromVariable := range usersVariable {
108+
bootstrapUsers = append(bootstrapUsers, generateBootstrapUser(userFromVariable))
109+
}
110+
obj.Spec.Template.Spec.Users = bootstrapUsers
111+
return nil
112+
}); err != nil {
113+
return err
114+
}
115+
116+
return nil
117+
}
118+
119+
func generateBootstrapUser(userFromVariable v1alpha1.User) bootstrapv1.User {
120+
bootstrapUser := bootstrapv1.User{
121+
Name: userFromVariable.Name,
122+
Passwd: userFromVariable.Passwd,
123+
SSHAuthorizedKeys: userFromVariable.SSHAuthorizedKeys,
124+
Sudo: userFromVariable.Sudo,
125+
}
126+
127+
// LockPassword is not part of our API, because we can derive its value
128+
// for the use cases our API supports.
129+
//
130+
// We do not support the edge cases where a password is defined, but
131+
// password authentication is disabled, or where no password is defined, but
132+
// password authentication is enabled.
133+
//
134+
// We disable password authentication by default.
135+
bootstrapUser.LockPassword = ptr.To[bool](true)
136+
if userFromVariable.Passwd != nil {
137+
// We enable password authentication only if a password is defined.
138+
bootstrapUser.LockPassword = ptr.To[bool](true)
139+
}
140+
141+
return bootstrapUser
142+
}

0 commit comments

Comments
 (0)