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

feat: Add user configuration for all providers #4

Merged
merged 13 commits into from
Apr 1, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
64 changes: 64 additions & 0 deletions api/v1alpha1/clusterconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ type GenericClusterConfig struct {

// +optional
Addons *Addons `json:"addons,omitempty"`

// +optional
Users Users `json:"users,omitempty"`
}

func (s GenericClusterConfig) VariableSchema() clusterv1.VariableSchema { //nolint:gocritic,lll // Passed by value for no potential side-effect.
Expand All @@ -116,6 +119,7 @@ func (s GenericClusterConfig) VariableSchema() clusterv1.VariableSchema { //noli
OpenAPIV3Schema,
"imageRegistries": ImageRegistries{}.VariableSchema().OpenAPIV3Schema,
"globalImageRegistryMirror": GlobalImageRegistryMirror{}.VariableSchema().OpenAPIV3Schema,
"users": Users{}.VariableSchema().OpenAPIV3Schema,
},
},
}
Expand Down Expand Up @@ -344,6 +348,66 @@ func (ImageRegistries) VariableSchema() clusterv1.VariableSchema {
}
}

type Users []User

func (Users) VariableSchema() clusterv1.VariableSchema {
return clusterv1.VariableSchema{
OpenAPIV3Schema: clusterv1.JSONSchemaProps{
Description: "Users to add to the machine",
Type: "array",
Items: ptr.To(User{}.VariableSchema().OpenAPIV3Schema),
},
}
}

// User defines the input for a generated user in cloud-init.
type User struct {
// Name specifies the user name
Name string `json:"name"`

// Passwd specifies a hashed password for the user
// +optional
Passwd *string `json:"passwd,omitempty"`

// SSHAuthorizedKeys specifies a list of ssh authorized keys for the user
// +optional
SSHAuthorizedKeys []string `json:"sshAuthorizedKeys,omitempty"`

// Sudo specifies a sudo role for the user
// +optional
Sudo *string `json:"sudo,omitempty"`
}

func (User) VariableSchema() clusterv1.VariableSchema {
return clusterv1.VariableSchema{
OpenAPIV3Schema: clusterv1.JSONSchemaProps{
Type: "object",
Properties: map[string]clusterv1.JSONSchemaProps{
"name": {
Description: "The username",
Type: "string",
},
"passwd": {
Description: "The hashed password for the user",
Type: "string",
},
"sshAuthorizedKeys": {
Description: "A list of SSH authorized keys for this user",
Type: "array",
Items: &clusterv1.JSONSchemaProps{
// No description, because the one for the parent array is enough.
Type: "string",
},
},
"sudo": {
Description: "The sudo rule that applies to this user",
Type: "string",
},
},
},
}
}

func init() {
SchemeBuilder.Register(&ClusterConfig{})
}
58 changes: 58 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.

9 changes: 9 additions & 0 deletions pkg/handlers/aws/mutation/metapatch_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import (
kubernetesimagerepositorytests "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/kubernetesimagerepository/tests"
"github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/mirrors"
globalimageregistrymirrortests "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/mirrors/tests"
"github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/users"
userstests "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/users/tests"
"github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/workerconfig"
)

Expand Down Expand Up @@ -188,4 +190,11 @@ func TestGeneratePatches(t *testing.T) {
v1alpha1.AWSVariableName,
controlplaneloadbalancer.VariableName,
)

userstests.TestGeneratePatches(
t,
metaPatchGeneratorFunc(mgr),
clusterconfig.MetaVariableName,
users.VariableName,
)
}
9 changes: 9 additions & 0 deletions pkg/handlers/docker/mutation/metapatch_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import (
kubernetesimagerepositorytests "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/kubernetesimagerepository/tests"
"github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/mirrors"
globalimageregistrymirrortests "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/mirrors/tests"
"github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/users"
userstests "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/users/tests"
"github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/workerconfig"
)

Expand Down Expand Up @@ -112,4 +114,11 @@ func TestGeneratePatches(t *testing.T) {
clusterconfig.MetaVariableName,
mirrors.GlobalMirrorVariableName,
)

userstests.TestGeneratePatches(
t,
metaPatchGeneratorFunc(mgr),
clusterconfig.MetaVariableName,
users.VariableName,
)
}
2 changes: 2 additions & 0 deletions pkg/handlers/generic/mutation/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/imageregistries/credentials"
"github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/kubernetesimagerepository"
"github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/mirrors"
"github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/users"
)

// MetaMutators returns all generic patch handlers.
Expand All @@ -28,5 +29,6 @@ func MetaMutators(mgr manager.Manager) []mutation.MetaMutator {
credentials.NewPatch(mgr.GetClient()),
mirrors.NewPatch(mgr.GetClient()),
calico.NewPatch(),
users.NewPatch(),
}
}
5 changes: 5 additions & 0 deletions pkg/handlers/generic/mutation/users/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright 2023 D2iQ, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

// +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters,verbs=watch;list;get
package users
142 changes: 142 additions & 0 deletions pkg/handlers/generic/mutation/users/inject.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Copyright 2023 D2iQ, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package users

import (
"context"

apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/utils/ptr"
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"
ctrl "sigs.k8s.io/controller-runtime"
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"

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

const (
// VariableName is the external patch variable name.
VariableName = "users"
)

type usersPatchHandler struct {
variableName string
variableFieldPath []string
}

func NewPatch() *usersPatchHandler {
return newUsersPatchHandler(
clusterconfig.MetaVariableName,
VariableName)
}

func newUsersPatchHandler(
variableName string,
variableFieldPath ...string,
) *usersPatchHandler {
return &usersPatchHandler{
variableName: variableName,
variableFieldPath: variableFieldPath,
}
}

func (h *usersPatchHandler) Mutate(
ctx context.Context,
obj *unstructured.Unstructured,
vars map[string]apiextensionsv1.JSON,
holderRef runtimehooksv1.HolderReference,
_ ctrlclient.ObjectKey,
) error {
log := ctrl.LoggerFrom(ctx, "holderRef", holderRef)

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

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

if err := patches.MutateIfApplicable(
obj, vars, &holderRef, selectors.ControlPlane(), log,
func(obj *controlplanev1.KubeadmControlPlaneTemplate) error {
log.WithValues(
"patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(),
"patchedObjectName", ctrlclient.ObjectKeyFromObject(obj),
).Info("setting users in control plane kubeadm config template")
bootstrapUsers := []bootstrapv1.User{}
for _, userFromVariable := range usersVariable {
bootstrapUsers = append(bootstrapUsers, generateBootstrapUser(userFromVariable))
}
obj.Spec.Template.Spec.KubeadmConfigSpec.Users = bootstrapUsers
return nil
}); err != nil {
return err
}

if err := patches.MutateIfApplicable(
obj, vars, &holderRef, selectors.WorkersKubeadmConfigTemplateSelector(), log,
func(obj *bootstrapv1.KubeadmConfigTemplate) error {
log.WithValues(
"patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(),
"patchedObjectName", ctrlclient.ObjectKeyFromObject(obj),
).Info("setting users in worker node kubeadm config template")
bootstrapUsers := []bootstrapv1.User{}
for _, userFromVariable := range usersVariable {
bootstrapUsers = append(bootstrapUsers, generateBootstrapUser(userFromVariable))
}
obj.Spec.Template.Spec.Users = bootstrapUsers
return nil
}); err != nil {
return err
}

return nil
}

func generateBootstrapUser(userFromVariable v1alpha1.User) bootstrapv1.User {
bootstrapUser := bootstrapv1.User{
Name: userFromVariable.Name,
Passwd: userFromVariable.Passwd,
SSHAuthorizedKeys: userFromVariable.SSHAuthorizedKeys,
Sudo: userFromVariable.Sudo,
}

// LockPassword is not part of our API, because we can derive its value
// for the use cases our API supports.
//
// We do not support the edge cases where a password is defined, but
// password authentication is disabled, or where no password is defined, but
// password authentication is enabled.
//
// We disable password authentication by default.
bootstrapUser.LockPassword = ptr.To[bool](true)
if userFromVariable.Passwd != nil {
// We enable password authentication only if a password is defined.
bootstrapUser.LockPassword = ptr.To[bool](true)
}

return bootstrapUser
}
Loading