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 2 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
65 changes: 65 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,67 @@ 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"`

// HashedPassword specifies a hashed password for the user.
// An empty string is not marshalled, because it is not a valid value.
// +optional
HashedPassword string `json:"hashedPassword,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",
},
"hashedPassword": {
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{})
}
53 changes: 53 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(),
}
}
4 changes: 4 additions & 0 deletions pkg/handlers/generic/mutation/users/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Copyright 2023 D2iQ, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

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: ptr.To(userFromVariable.HashedPassword),
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 these edge cases:
// (a) Hashed password is defined, password authentication is not enabled.
// (b) Hashed password is not defined, password authentication is enabled.
//
// We disable password authentication by default.
bootstrapUser.LockPassword = ptr.To(true)
if userFromVariable.HashedPassword != "" {
// We enable password authentication only if a hashed password is defined.
bootstrapUser.LockPassword = ptr.To(true)
}

return bootstrapUser
}
Loading