From 325dd784e38129c78185b9a433bf9ccc0942feb5 Mon Sep 17 00:00:00 2001 From: Richard Case Date: Wed, 23 Mar 2022 15:24:43 +0000 Subject: [PATCH 1/2] wip Signed-off-by: Richard Case --- bootstrap/eks/api/v1beta1/eksconfig_types.go | 9 +++ .../eks/api/v1beta1/zz_generated.deepcopy.go | 15 ++++ .../eks/controllers/eksconfig_controller.go | 17 +++-- bootstrap/eks/internal/userdata/node.go | 32 ++++++++- bootstrap/eks/internal/userdata/node_test.go | 69 +++++++++++++++++++ ...bootstrap.cluster.x-k8s.io_eksconfigs.yaml | 16 +++++ ...p.cluster.x-k8s.io_eksconfigtemplates.yaml | 16 +++++ 7 files changed, 164 insertions(+), 10 deletions(-) diff --git a/bootstrap/eks/api/v1beta1/eksconfig_types.go b/bootstrap/eks/api/v1beta1/eksconfig_types.go index c3e0b4d546..fda08809a3 100644 --- a/bootstrap/eks/api/v1beta1/eksconfig_types.go +++ b/bootstrap/eks/api/v1beta1/eksconfig_types.go @@ -51,6 +51,15 @@ type EKSConfigSpec struct { // the ip family will be set to ipv6. // +optional ServiceIPV6Cidr *string `json:"serviceIPV6Cidr,omitempty"` + // PreBootstrapCommands specifies extra commands to run before bootstrapping nodes to the cluster + // +optional + PreBootstrapCommands []string `json:"preBootstrapCommands,omitempty"` + // PostBootstrapCommands specifies extra commands to run after bootstrapping nodes to the cluster + // +optional + PostBootstrapCommands []string `json:"postBootstrapCommands,omitempty"` + // BootstrapCommandOverride allows you to override the bootstrap command to use for EKS nodes. + // +optional + BootstrapCommandOverride *string `json:"boostrapCommandOverride,omitempty"` } // PauseContainer contains details of pause container. diff --git a/bootstrap/eks/api/v1beta1/zz_generated.deepcopy.go b/bootstrap/eks/api/v1beta1/zz_generated.deepcopy.go index b8342c9a9e..25d50026f6 100644 --- a/bootstrap/eks/api/v1beta1/zz_generated.deepcopy.go +++ b/bootstrap/eks/api/v1beta1/zz_generated.deepcopy.go @@ -130,6 +130,21 @@ func (in *EKSConfigSpec) DeepCopyInto(out *EKSConfigSpec) { *out = new(string) **out = **in } + if in.PreBootstrapCommands != nil { + in, out := &in.PreBootstrapCommands, &out.PreBootstrapCommands + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.PostBootstrapCommands != nil { + in, out := &in.PostBootstrapCommands, &out.PostBootstrapCommands + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.BootstrapCommandOverride != nil { + in, out := &in.BootstrapCommandOverride, &out.BootstrapCommandOverride + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EKSConfigSpec. diff --git a/bootstrap/eks/controllers/eksconfig_controller.go b/bootstrap/eks/controllers/eksconfig_controller.go index e05b17a9cd..6f6faa5b39 100644 --- a/bootstrap/eks/controllers/eksconfig_controller.go +++ b/bootstrap/eks/controllers/eksconfig_controller.go @@ -189,13 +189,16 @@ func (r *EKSConfigReconciler) joinWorker(ctx context.Context, cluster *clusterv1 nodeInput := &userdata.NodeInput{ // AWSManagedControlPlane webhooks default and validate EKSClusterName - ClusterName: controlPlane.Spec.EKSClusterName, - KubeletExtraArgs: config.Spec.KubeletExtraArgs, - ContainerRuntime: config.Spec.ContainerRuntime, - DNSClusterIP: config.Spec.DNSClusterIP, - DockerConfigJSON: config.Spec.DockerConfigJSON, - APIRetryAttempts: config.Spec.APIRetryAttempts, - UseMaxPods: config.Spec.UseMaxPods, + ClusterName: controlPlane.Spec.EKSClusterName, + KubeletExtraArgs: config.Spec.KubeletExtraArgs, + ContainerRuntime: config.Spec.ContainerRuntime, + DNSClusterIP: config.Spec.DNSClusterIP, + DockerConfigJSON: config.Spec.DockerConfigJSON, + APIRetryAttempts: config.Spec.APIRetryAttempts, + UseMaxPods: config.Spec.UseMaxPods, + PreBootstrapCommands: config.Spec.PreBootstrapCommands, + PostBootstrapCommands: config.Spec.PostBootstrapCommands, + BootstrapCommandOverride: config.Spec.BootstrapCommandOverride, } if config.Spec.PauseContainer != nil { nodeInput.PauseContainerAccount = &config.Spec.PauseContainer.AccountNumber diff --git a/bootstrap/eks/internal/userdata/node.go b/bootstrap/eks/internal/userdata/node.go index f40f709d04..8339d01edd 100644 --- a/bootstrap/eks/internal/userdata/node.go +++ b/bootstrap/eks/internal/userdata/node.go @@ -25,8 +25,19 @@ import ( ) const ( + defaultBootstrapCommand = "/etc/eks/bootstrap.sh" + nodeUserData = `#!/bin/bash -/etc/eks/bootstrap.sh {{.ClusterName}} {{- template "args" . }} +set -o errexit; set -o pipefail; set -o nounset; +{{- template "commands" .PreBootstrapCommands }} +{{ .BootstrapCommand }} {{.ClusterName}} {{- template "args" . }} +{{- template "commands" .PostBootstrapCommands }} +` + commandsTemplate = `{{- define "commands" -}} +{{ range . }} +{{.}} +{{- end -}} +{{- end -}} ` ) @@ -43,8 +54,11 @@ type NodeInput struct { UseMaxPods *bool // NOTE: currently the IPFamily/ServiceIPV6Cidr isn't exposed to the user. // TODO (richardcase): remove the above comment when IPV6 / dual stack is implemented. - IPFamily *string - ServiceIPV6Cidr *string + IPFamily *string + ServiceIPV6Cidr *string + PreBootstrapCommands []string + PostBootstrapCommands []string + BootstrapCommandOverride *string } func (ni *NodeInput) DockerConfigJSONEscaped() string { @@ -55,6 +69,14 @@ func (ni *NodeInput) DockerConfigJSONEscaped() string { return shellescape.Quote(*ni.DockerConfigJSON) } +func (ni *NodeInput) BootstrapCommand() string { + if ni.BootstrapCommandOverride != nil && *ni.BootstrapCommandOverride != "" { + return *ni.BootstrapCommandOverride + } + + return defaultBootstrapCommand +} + // NewNode returns the user data string to be used on a node instance. func NewNode(input *NodeInput) ([]byte, error) { tm := template.New("Node") @@ -67,6 +89,10 @@ func NewNode(input *NodeInput) ([]byte, error) { return nil, fmt.Errorf("failed to parse kubeletExtraArgs template: %w", err) } + if _, err := tm.Parse(commandsTemplate); err != nil { + return nil, fmt.Errorf("failed to parse commandsTemplate template: %w", err) + } + t, err := tm.Parse(nodeUserData) if err != nil { return nil, fmt.Errorf("failed to parse Node template: %w", err) diff --git a/bootstrap/eks/internal/userdata/node_test.go b/bootstrap/eks/internal/userdata/node_test.go index 5bf85b4f7c..ebad985e57 100644 --- a/bootstrap/eks/internal/userdata/node_test.go +++ b/bootstrap/eks/internal/userdata/node_test.go @@ -46,6 +46,7 @@ func TestNewNode(t *testing.T) { }, }, expectedBytes: []byte(`#!/bin/bash +set -o errexit; set -o pipefail; set -o nounset; /etc/eks/bootstrap.sh test-cluster `), expectErr: false, @@ -62,6 +63,7 @@ func TestNewNode(t *testing.T) { }, }, expectedBytes: []byte(`#!/bin/bash +set -o errexit; set -o pipefail; set -o nounset; /etc/eks/bootstrap.sh test-cluster --kubelet-extra-args '--node-labels=node-role.undistro.io/infra=true --register-with-taints=dedicated=infra:NoSchedule' `), }, @@ -74,6 +76,7 @@ func TestNewNode(t *testing.T) { }, }, expectedBytes: []byte(`#!/bin/bash +set -o errexit; set -o pipefail; set -o nounset; /etc/eks/bootstrap.sh test-cluster --container-runtime containerd `), }, @@ -90,6 +93,7 @@ func TestNewNode(t *testing.T) { }, }, expectedBytes: []byte(`#!/bin/bash +set -o errexit; set -o pipefail; set -o nounset; /etc/eks/bootstrap.sh test-cluster --kubelet-extra-args '--node-labels=node-role.undistro.io/infra=true --register-with-taints=dedicated=infra:NoSchedule' --container-runtime containerd `), }, @@ -103,6 +107,7 @@ func TestNewNode(t *testing.T) { }, }, expectedBytes: []byte(`#!/bin/bash +set -o errexit; set -o pipefail; set -o nounset; /etc/eks/bootstrap.sh test-cluster --ip-family ipv6 --service-ipv6-cidr fe80:0000:0000:0000:0204:61ff:fe9d:f156/24 `), }, @@ -115,6 +120,7 @@ func TestNewNode(t *testing.T) { }, }, expectedBytes: []byte(`#!/bin/bash +set -o errexit; set -o pipefail; set -o nounset; /etc/eks/bootstrap.sh test-cluster --use-max-pods false `), }, @@ -127,6 +133,7 @@ func TestNewNode(t *testing.T) { }, }, expectedBytes: []byte(`#!/bin/bash +set -o errexit; set -o pipefail; set -o nounset; /etc/eks/bootstrap.sh test-cluster --aws-api-retry-attempts 5 `), }, @@ -140,6 +147,7 @@ func TestNewNode(t *testing.T) { }, }, expectedBytes: []byte(`#!/bin/bash +set -o errexit; set -o pipefail; set -o nounset; /etc/eks/bootstrap.sh test-cluster --pause-container-account 12345678 --pause-container-version v1 `), }, @@ -152,6 +160,7 @@ func TestNewNode(t *testing.T) { }, }, expectedBytes: []byte(`#!/bin/bash +set -o errexit; set -o pipefail; set -o nounset; /etc/eks/bootstrap.sh test-cluster --dns-cluster-ip 192.168.0.1 `), }, @@ -164,7 +173,67 @@ func TestNewNode(t *testing.T) { }, }, expectedBytes: []byte(`#!/bin/bash +set -o errexit; set -o pipefail; set -o nounset; /etc/eks/bootstrap.sh test-cluster --docker-config-json '{"debug":true}' +`), + }, + { + name: "with pre-bootstrap command", + args: args{ + input: &NodeInput{ + ClusterName: "test-cluster", + PreBootstrapCommands: []string{"date", "echo \"testing\""}, + }, + }, + expectedBytes: []byte(`#!/bin/bash +set -o errexit; set -o pipefail; set -o nounset; +date +echo "testing" +/etc/eks/bootstrap.sh test-cluster +`), + }, + { + name: "with post-bootstrap command", + args: args{ + input: &NodeInput{ + ClusterName: "test-cluster", + PostBootstrapCommands: []string{"date", "echo \"testing\""}, + }, + }, + expectedBytes: []byte(`#!/bin/bash +set -o errexit; set -o pipefail; set -o nounset; +/etc/eks/bootstrap.sh test-cluster +date +echo "testing" +`), + }, + { + name: "with pre & post-bootstrap command", + args: args{ + input: &NodeInput{ + ClusterName: "test-cluster", + PreBootstrapCommands: []string{"echo \"testing pre\""}, + PostBootstrapCommands: []string{"echo \"testing post\""}, + }, + }, + expectedBytes: []byte(`#!/bin/bash +set -o errexit; set -o pipefail; set -o nounset; +echo "testing pre" +/etc/eks/bootstrap.sh test-cluster +echo "testing post" +`), + }, + { + name: "with bootstrap override command", + args: args{ + input: &NodeInput{ + ClusterName: "test-cluster", + BootstrapCommandOverride: pointer.String("/custom/mybootstrap.sh"), + }, + }, + expectedBytes: []byte(`#!/bin/bash +set -o errexit; set -o pipefail; set -o nounset; +/custom/mybootstrap.sh test-cluster `), }, } diff --git a/config/crd/bases/bootstrap.cluster.x-k8s.io_eksconfigs.yaml b/config/crd/bases/bootstrap.cluster.x-k8s.io_eksconfigs.yaml index 0208a9c404..56e4dfe08d 100644 --- a/config/crd/bases/bootstrap.cluster.x-k8s.io_eksconfigs.yaml +++ b/config/crd/bases/bootstrap.cluster.x-k8s.io_eksconfigs.yaml @@ -205,6 +205,10 @@ spec: description: APIRetryAttempts is the number of retry attempts for AWS API call. type: integer + boostrapCommandOverride: + description: BootstrapCommandOverride allows you to override the bootstrap + command to use for EKS nodes. + type: string containerRuntime: description: ContainerRuntime specify the container runtime to use when bootstrapping EKS. @@ -243,6 +247,18 @@ spec: description: ServiceIPV6Cidr is the ipv6 cidr range of the cluster. If this is specified then the ip family will be set to ipv6. type: string + postBootstrapCommands: + description: PostBootstrapCommands specifies extra commands to run + after bootstrapping nodes to the cluster + items: + type: string + type: array + preBootstrapCommands: + description: PreBootstrapCommands specifies extra commands to run + before bootstrapping nodes to the cluster + items: + type: string + type: array useMaxPods: description: UseMaxPods sets --max-pods for the kubelet when true. type: boolean diff --git a/config/crd/bases/bootstrap.cluster.x-k8s.io_eksconfigtemplates.yaml b/config/crd/bases/bootstrap.cluster.x-k8s.io_eksconfigtemplates.yaml index 0998175ed8..3bc7a1be9a 100644 --- a/config/crd/bases/bootstrap.cluster.x-k8s.io_eksconfigtemplates.yaml +++ b/config/crd/bases/bootstrap.cluster.x-k8s.io_eksconfigtemplates.yaml @@ -138,6 +138,10 @@ spec: description: APIRetryAttempts is the number of retry attempts for AWS API call. type: integer + boostrapCommandOverride: + description: BootstrapCommandOverride allows you to override + the bootstrap command to use for EKS nodes. + type: string containerRuntime: description: ContainerRuntime specify the container runtime to use when bootstrapping EKS. @@ -179,6 +183,18 @@ spec: cluster. If this is specified then the ip family will be set to ipv6. type: string + postBootstrapCommands: + description: PostBootstrapCommands specifies extra commands + to run after bootstrapping nodes to the cluster + items: + type: string + type: array + preBootstrapCommands: + description: PreBootstrapCommands specifies extra commands + to run before bootstrapping nodes to the cluster + items: + type: string + type: array useMaxPods: description: UseMaxPods sets --max-pods for the kubelet when true. From 9b8b5af59a60bb7f4bae3bb4107acf92ca68d524 Mon Sep 17 00:00:00 2001 From: Cameron McAvoy Date: Tue, 27 Sep 2022 17:54:43 -0500 Subject: [PATCH 2/2] Implement cloud-init for EKSConfigTemplate with yaml cloud-init userdata instead of bash-style script --- bootstrap/eks/api/v1beta1/conversion.go | 76 +++- bootstrap/eks/api/v1beta1/eksconfig_types.go | 9 - .../api/v1beta1/zz_generated.conversion.go | 71 +++- .../eks/api/v1beta1/zz_generated.deepcopy.go | 15 - bootstrap/eks/api/v1beta2/eksconfig_types.go | 222 +++++++++++- .../eks/api/v1beta2/zz_generated.deepcopy.go | 330 ++++++++++++++++++ .../eks/controllers/eksconfig_controller.go | 5 + bootstrap/eks/internal/userdata/commands.go | 26 ++ bootstrap/eks/internal/userdata/disk_setup.go | 35 ++ bootstrap/eks/internal/userdata/files.go | 40 +++ bootstrap/eks/internal/userdata/fs_setup.go | 41 +++ .../eks/internal/userdata/kubelet_args.go | 2 +- bootstrap/eks/internal/userdata/mounts.go | 29 ++ bootstrap/eks/internal/userdata/node.go | 51 ++- bootstrap/eks/internal/userdata/node_test.go | 231 +++++++++--- bootstrap/eks/internal/userdata/ntp.go | 32 ++ bootstrap/eks/internal/userdata/users.go | 60 ++++ bootstrap/eks/internal/userdata/utils.go | 34 ++ ...bootstrap.cluster.x-k8s.io_eksconfigs.yaml | 235 ++++++++++++- ...p.cluster.x-k8s.io_eksconfigtemplates.yaml | 249 ++++++++++++- 20 files changed, 1682 insertions(+), 111 deletions(-) create mode 100644 bootstrap/eks/internal/userdata/commands.go create mode 100644 bootstrap/eks/internal/userdata/disk_setup.go create mode 100644 bootstrap/eks/internal/userdata/files.go create mode 100644 bootstrap/eks/internal/userdata/fs_setup.go create mode 100644 bootstrap/eks/internal/userdata/mounts.go create mode 100644 bootstrap/eks/internal/userdata/ntp.go create mode 100644 bootstrap/eks/internal/userdata/users.go create mode 100644 bootstrap/eks/internal/userdata/utils.go diff --git a/bootstrap/eks/api/v1beta1/conversion.go b/bootstrap/eks/api/v1beta1/conversion.go index 2a6a42ac3a..48762a5b84 100644 --- a/bootstrap/eks/api/v1beta1/conversion.go +++ b/bootstrap/eks/api/v1beta1/conversion.go @@ -17,9 +17,10 @@ limitations under the License. package v1beta1 import ( - "sigs.k8s.io/controller-runtime/pkg/conversion" - + apiconversion "k8s.io/apimachinery/pkg/conversion" "sigs.k8s.io/cluster-api-provider-aws/v2/bootstrap/eks/api/v1beta2" + utilconversion "sigs.k8s.io/cluster-api/util/conversion" + "sigs.k8s.io/controller-runtime/pkg/conversion" ) // ConvertTo converts the v1beta1 EKSConfig receiver to a v1beta2 EKSConfig. @@ -30,6 +31,37 @@ func (r *EKSConfig) ConvertTo(dstRaw conversion.Hub) error { return err } + // Manually restore data. + restored := &v1beta2.EKSConfig{} + if ok, err := utilconversion.UnmarshalData(r, restored); err != nil || !ok { + return err + } + + if restored.Spec.PreBootstrapCommands != nil { + dst.Spec.PreBootstrapCommands = restored.Spec.PreBootstrapCommands + } + if restored.Spec.PostBootstrapCommands != nil { + dst.Spec.PostBootstrapCommands = restored.Spec.PostBootstrapCommands + } + if restored.Spec.BootstrapCommandOverride != nil { + dst.Spec.BootstrapCommandOverride = restored.Spec.BootstrapCommandOverride + } + if restored.Spec.Files != nil { + dst.Spec.Files = restored.Spec.Files + } + if restored.Spec.DiskSetup != nil { + dst.Spec.DiskSetup = restored.Spec.DiskSetup + } + if restored.Spec.Mounts != nil { + dst.Spec.Mounts = restored.Spec.Mounts + } + if restored.Spec.Users != nil { + dst.Spec.Users = restored.Spec.Users + } + if restored.Spec.NTP != nil { + dst.Spec.NTP = restored.Spec.NTP + } + return nil } @@ -41,7 +73,7 @@ func (r *EKSConfig) ConvertFrom(srcRaw conversion.Hub) error { return err } - return nil + return utilconversion.MarshalData(src, r) } // ConvertTo converts the v1beta1 EKSConfigList receiver to a v1beta2 EKSConfigList. @@ -66,6 +98,37 @@ func (r *EKSConfigTemplate) ConvertTo(dstRaw conversion.Hub) error { return err } + // Manually restore data. + restored := &v1beta2.EKSConfigTemplate{} + if ok, err := utilconversion.UnmarshalData(r, restored); err != nil || !ok { + return err + } + + if restored.Spec.Template.Spec.PreBootstrapCommands != nil { + dst.Spec.Template.Spec.PreBootstrapCommands = restored.Spec.Template.Spec.PreBootstrapCommands + } + if restored.Spec.Template.Spec.PostBootstrapCommands != nil { + dst.Spec.Template.Spec.PostBootstrapCommands = restored.Spec.Template.Spec.PostBootstrapCommands + } + if restored.Spec.Template.Spec.BootstrapCommandOverride != nil { + dst.Spec.Template.Spec.BootstrapCommandOverride = restored.Spec.Template.Spec.BootstrapCommandOverride + } + if restored.Spec.Template.Spec.Files != nil { + dst.Spec.Template.Spec.Files = restored.Spec.Template.Spec.Files + } + if restored.Spec.Template.Spec.DiskSetup != nil { + dst.Spec.Template.Spec.DiskSetup = restored.Spec.Template.Spec.DiskSetup + } + if restored.Spec.Template.Spec.Mounts != nil { + dst.Spec.Template.Spec.Mounts = restored.Spec.Template.Spec.Mounts + } + if restored.Spec.Template.Spec.Users != nil { + dst.Spec.Template.Spec.Users = restored.Spec.Template.Spec.Users + } + if restored.Spec.Template.Spec.NTP != nil { + dst.Spec.Template.Spec.NTP = restored.Spec.Template.Spec.NTP + } + return nil } @@ -77,7 +140,7 @@ func (r *EKSConfigTemplate) ConvertFrom(srcRaw conversion.Hub) error { return err } - return nil + return utilconversion.MarshalData(src, r) } // ConvertTo converts the v1beta1 EKSConfigTemplateList receiver to a v1beta2 EKSConfigTemplateList. @@ -93,3 +156,8 @@ func (r *EKSConfigTemplateList) ConvertFrom(srcRaw conversion.Hub) error { return Convert_v1beta2_EKSConfigTemplateList_To_v1beta1_EKSConfigTemplateList(src, r, nil) } + +// Convert_v1beta2_EKSConfigSpec_To_v1beta1_EKSConfigSpec converts a v1beta2 EKSConfigSpec receiver to a v1beta1 EKSConfigSpec. +func Convert_v1beta2_EKSConfigSpec_To_v1beta1_EKSConfigSpec(in *v1beta2.EKSConfigSpec, out *EKSConfigSpec, s apiconversion.Scope) error { + return autoConvert_v1beta2_EKSConfigSpec_To_v1beta1_EKSConfigSpec(in, out, s) +} diff --git a/bootstrap/eks/api/v1beta1/eksconfig_types.go b/bootstrap/eks/api/v1beta1/eksconfig_types.go index fda08809a3..c3e0b4d546 100644 --- a/bootstrap/eks/api/v1beta1/eksconfig_types.go +++ b/bootstrap/eks/api/v1beta1/eksconfig_types.go @@ -51,15 +51,6 @@ type EKSConfigSpec struct { // the ip family will be set to ipv6. // +optional ServiceIPV6Cidr *string `json:"serviceIPV6Cidr,omitempty"` - // PreBootstrapCommands specifies extra commands to run before bootstrapping nodes to the cluster - // +optional - PreBootstrapCommands []string `json:"preBootstrapCommands,omitempty"` - // PostBootstrapCommands specifies extra commands to run after bootstrapping nodes to the cluster - // +optional - PostBootstrapCommands []string `json:"postBootstrapCommands,omitempty"` - // BootstrapCommandOverride allows you to override the bootstrap command to use for EKS nodes. - // +optional - BootstrapCommandOverride *string `json:"boostrapCommandOverride,omitempty"` } // PauseContainer contains details of pause container. diff --git a/bootstrap/eks/api/v1beta1/zz_generated.conversion.go b/bootstrap/eks/api/v1beta1/zz_generated.conversion.go index 714e446bfe..01cd54c0c0 100644 --- a/bootstrap/eks/api/v1beta1/zz_generated.conversion.go +++ b/bootstrap/eks/api/v1beta1/zz_generated.conversion.go @@ -62,11 +62,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1beta2.EKSConfigSpec)(nil), (*EKSConfigSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta2_EKSConfigSpec_To_v1beta1_EKSConfigSpec(a.(*v1beta2.EKSConfigSpec), b.(*EKSConfigSpec), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*EKSConfigStatus)(nil), (*v1beta2.EKSConfigStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_EKSConfigStatus_To_v1beta2_EKSConfigStatus(a.(*EKSConfigStatus), b.(*v1beta2.EKSConfigStatus), scope) }); err != nil { @@ -127,6 +122,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta2.EKSConfigSpec)(nil), (*EKSConfigSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta2_EKSConfigSpec_To_v1beta1_EKSConfigSpec(a.(*v1beta2.EKSConfigSpec), b.(*EKSConfigSpec), scope) + }); err != nil { + return err + } return nil } @@ -164,7 +164,17 @@ func Convert_v1beta2_EKSConfig_To_v1beta1_EKSConfig(in *v1beta2.EKSConfig, out * func autoConvert_v1beta1_EKSConfigList_To_v1beta2_EKSConfigList(in *EKSConfigList, out *v1beta2.EKSConfigList, s conversion.Scope) error { out.ListMeta = in.ListMeta - out.Items = *(*[]v1beta2.EKSConfig)(unsafe.Pointer(&in.Items)) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]v1beta2.EKSConfig, len(*in)) + for i := range *in { + if err := Convert_v1beta1_EKSConfig_To_v1beta2_EKSConfig(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } return nil } @@ -175,7 +185,17 @@ func Convert_v1beta1_EKSConfigList_To_v1beta2_EKSConfigList(in *EKSConfigList, o func autoConvert_v1beta2_EKSConfigList_To_v1beta1_EKSConfigList(in *v1beta2.EKSConfigList, out *EKSConfigList, s conversion.Scope) error { out.ListMeta = in.ListMeta - out.Items = *(*[]EKSConfig)(unsafe.Pointer(&in.Items)) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]EKSConfig, len(*in)) + for i := range *in { + if err := Convert_v1beta2_EKSConfig_To_v1beta1_EKSConfig(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } return nil } @@ -210,14 +230,17 @@ func autoConvert_v1beta2_EKSConfigSpec_To_v1beta1_EKSConfigSpec(in *v1beta2.EKSC out.PauseContainer = (*PauseContainer)(unsafe.Pointer(in.PauseContainer)) out.UseMaxPods = (*bool)(unsafe.Pointer(in.UseMaxPods)) out.ServiceIPV6Cidr = (*string)(unsafe.Pointer(in.ServiceIPV6Cidr)) + // WARNING: in.PreBootstrapCommands requires manual conversion: does not exist in peer-type + // WARNING: in.PostBootstrapCommands requires manual conversion: does not exist in peer-type + // WARNING: in.BootstrapCommandOverride requires manual conversion: does not exist in peer-type + // WARNING: in.Files requires manual conversion: does not exist in peer-type + // WARNING: in.DiskSetup requires manual conversion: does not exist in peer-type + // WARNING: in.Mounts requires manual conversion: does not exist in peer-type + // WARNING: in.Users requires manual conversion: does not exist in peer-type + // WARNING: in.NTP requires manual conversion: does not exist in peer-type return nil } -// Convert_v1beta2_EKSConfigSpec_To_v1beta1_EKSConfigSpec is an autogenerated conversion function. -func Convert_v1beta2_EKSConfigSpec_To_v1beta1_EKSConfigSpec(in *v1beta2.EKSConfigSpec, out *EKSConfigSpec, s conversion.Scope) error { - return autoConvert_v1beta2_EKSConfigSpec_To_v1beta1_EKSConfigSpec(in, out, s) -} - func autoConvert_v1beta1_EKSConfigStatus_To_v1beta2_EKSConfigStatus(in *EKSConfigStatus, out *v1beta2.EKSConfigStatus, s conversion.Scope) error { out.Ready = in.Ready out.DataSecretName = (*string)(unsafe.Pointer(in.DataSecretName)) @@ -276,7 +299,17 @@ func Convert_v1beta2_EKSConfigTemplate_To_v1beta1_EKSConfigTemplate(in *v1beta2. func autoConvert_v1beta1_EKSConfigTemplateList_To_v1beta2_EKSConfigTemplateList(in *EKSConfigTemplateList, out *v1beta2.EKSConfigTemplateList, s conversion.Scope) error { out.ListMeta = in.ListMeta - out.Items = *(*[]v1beta2.EKSConfigTemplate)(unsafe.Pointer(&in.Items)) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]v1beta2.EKSConfigTemplate, len(*in)) + for i := range *in { + if err := Convert_v1beta1_EKSConfigTemplate_To_v1beta2_EKSConfigTemplate(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } return nil } @@ -287,7 +320,17 @@ func Convert_v1beta1_EKSConfigTemplateList_To_v1beta2_EKSConfigTemplateList(in * func autoConvert_v1beta2_EKSConfigTemplateList_To_v1beta1_EKSConfigTemplateList(in *v1beta2.EKSConfigTemplateList, out *EKSConfigTemplateList, s conversion.Scope) error { out.ListMeta = in.ListMeta - out.Items = *(*[]EKSConfigTemplate)(unsafe.Pointer(&in.Items)) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]EKSConfigTemplate, len(*in)) + for i := range *in { + if err := Convert_v1beta2_EKSConfigTemplate_To_v1beta1_EKSConfigTemplate(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } return nil } diff --git a/bootstrap/eks/api/v1beta1/zz_generated.deepcopy.go b/bootstrap/eks/api/v1beta1/zz_generated.deepcopy.go index 25d50026f6..b8342c9a9e 100644 --- a/bootstrap/eks/api/v1beta1/zz_generated.deepcopy.go +++ b/bootstrap/eks/api/v1beta1/zz_generated.deepcopy.go @@ -130,21 +130,6 @@ func (in *EKSConfigSpec) DeepCopyInto(out *EKSConfigSpec) { *out = new(string) **out = **in } - if in.PreBootstrapCommands != nil { - in, out := &in.PreBootstrapCommands, &out.PreBootstrapCommands - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.PostBootstrapCommands != nil { - in, out := &in.PostBootstrapCommands, &out.PostBootstrapCommands - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.BootstrapCommandOverride != nil { - in, out := &in.BootstrapCommandOverride, &out.BootstrapCommandOverride - *out = new(string) - **out = **in - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EKSConfigSpec. diff --git a/bootstrap/eks/api/v1beta2/eksconfig_types.go b/bootstrap/eks/api/v1beta2/eksconfig_types.go index 67ccdc94a0..a2fce8e2cb 100644 --- a/bootstrap/eks/api/v1beta2/eksconfig_types.go +++ b/bootstrap/eks/api/v1beta2/eksconfig_types.go @@ -46,11 +46,34 @@ type EKSConfigSpec struct { // UseMaxPods sets --max-pods for the kubelet when true. // +optional UseMaxPods *bool `json:"useMaxPods,omitempty"` - // ServiceIPV6Cidr is the ipv6 cidr range of the cluster. If this is specified then // the ip family will be set to ipv6. // +optional ServiceIPV6Cidr *string `json:"serviceIPV6Cidr,omitempty"` + // PreBootstrapCommands specifies extra commands to run before bootstrapping nodes to the cluster + // +optional + PreBootstrapCommands []string `json:"preBootstrapCommands,omitempty"` + // PostBootstrapCommands specifies extra commands to run after bootstrapping nodes to the cluster + // +optional + PostBootstrapCommands []string `json:"postBootstrapCommands,omitempty"` + // BootstrapCommandOverride allows you to override the bootstrap command to use for EKS nodes. + // +optional + BootstrapCommandOverride *string `json:"boostrapCommandOverride,omitempty"` + // Files specifies extra files to be passed to user_data upon creation. + // +optional + Files []File `json:"files,omitempty"` + // DiskSetup specifies options for the creation of partition tables and file systems on devices. + // +optional + DiskSetup *DiskSetup `json:"diskSetup,omitempty"` + // Mounts specifies a list of mount points to be setup. + // +optional + Mounts []MountPoints `json:"mounts,omitempty"` + // Users specifies extra users to add + // +optional + Users []User `json:"users,omitempty"` + // NTP specifies NTP configuration + // +optional + NTP *NTP `json:"ntp,omitempty"` } // PauseContainer contains details of pause container. @@ -87,6 +110,203 @@ type EKSConfigStatus struct { Conditions clusterv1.Conditions `json:"conditions,omitempty"` } +// Encoding specifies the cloud-init file encoding. +// +kubebuilder:validation:Enum=base64;gzip;gzip+base64 +type Encoding string + +const ( + // Base64 implies the contents of the file are encoded as base64. + Base64 Encoding = "base64" + // Gzip implies the contents of the file are encoded with gzip. + Gzip Encoding = "gzip" + // GzipBase64 implies the contents of the file are first base64 encoded and then gzip encoded. + GzipBase64 Encoding = "gzip+base64" +) + +// File defines the input for generating write_files in cloud-init. +type File struct { + // Path specifies the full path on disk where to store the file. + Path string `json:"path"` + + // Owner specifies the ownership of the file, e.g. "root:root". + // +optional + Owner string `json:"owner,omitempty"` + + // Permissions specifies the permissions to assign to the file, e.g. "0640". + // +optional + Permissions string `json:"permissions,omitempty"` + + // Encoding specifies the encoding of the file contents. + // +optional + Encoding Encoding `json:"encoding,omitempty"` + + // Append specifies whether to append Content to existing file if Path exists. + // +optional + Append bool `json:"append,omitempty"` + + // Content is the actual content of the file. + // +optional + Content string `json:"content,omitempty"` + + // ContentFrom is a referenced source of content to populate the file. + // +optional + ContentFrom *FileSource `json:"contentFrom,omitempty"` +} + +// FileSource is a union of all possible external source types for file data. +// Only one field may be populated in any given instance. Developers adding new +// sources of data for target systems should add them here. +type FileSource struct { + // Secret represents a secret that should populate this file. + Secret SecretFileSource `json:"secret"` +} + +// SecretFileSource adapts a Secret into a FileSource. +// +// The contents of the target Secret's Data field will be presented +// as files using the keys in the Data field as the file names. +type SecretFileSource struct { + // Name of the secret in the KubeadmBootstrapConfig's namespace to use. + Name string `json:"name"` + + // Key is the key in the secret's data map for this value. + Key string `json:"key"` +} + +// PasswdSource is a union of all possible external source types for passwd data. +// Only one field may be populated in any given instance. Developers adding new +// sources of data for target systems should add them here. +type PasswdSource struct { + // Secret represents a secret that should populate this password. + Secret SecretPasswdSource `json:"secret"` +} + +// SecretPasswdSource adapts a Secret into a PasswdSource. +// +// The contents of the target Secret's Data field will be presented +// as passwd using the keys in the Data field as the file names. +type SecretPasswdSource struct { + // Name of the secret in the KubeadmBootstrapConfig's namespace to use. + Name string `json:"name"` + + // Key is the key in the secret's data map for this value. + Key string `json:"key"` +} + +// User defines the input for a generated user in cloud-init. +type User struct { + // Name specifies the username + Name string `json:"name"` + + // Gecos specifies the gecos to use for the user + // +optional + Gecos *string `json:"gecos,omitempty"` + + // Groups specifies the additional groups for the user + // +optional + Groups *string `json:"groups,omitempty"` + + // HomeDir specifies the home directory to use for the user + // +optional + HomeDir *string `json:"homeDir,omitempty"` + + // Inactive specifies whether to mark the user as inactive + // +optional + Inactive *bool `json:"inactive,omitempty"` + + // Shell specifies the user's shell + // +optional + Shell *string `json:"shell,omitempty"` + + // Passwd specifies a hashed password for the user + // +optional + Passwd *string `json:"passwd,omitempty"` + + // PasswdFrom is a referenced source of passwd to populate the passwd. + // +optional + PasswdFrom *PasswdSource `json:"passwdFrom,omitempty"` + + // PrimaryGroup specifies the primary group for the user + // +optional + PrimaryGroup *string `json:"primaryGroup,omitempty"` + + // LockPassword specifies if password login should be disabled + // +optional + LockPassword *bool `json:"lockPassword,omitempty"` + + // Sudo specifies a sudo role for the user + // +optional + Sudo *string `json:"sudo,omitempty"` + + // SSHAuthorizedKeys specifies a list of ssh authorized keys for the user + // +optional + SSHAuthorizedKeys []string `json:"sshAuthorizedKeys,omitempty"` +} + +// NTP defines input for generated ntp in cloud-init. +type NTP struct { + // Servers specifies which NTP servers to use + // +optional + Servers []string `json:"servers,omitempty"` + + // Enabled specifies whether NTP should be enabled + // +optional + Enabled *bool `json:"enabled,omitempty"` +} + +// DiskSetup defines input for generated disk_setup and fs_setup in cloud-init. +type DiskSetup struct { + // Partitions specifies the list of the partitions to setup. + // +optional + Partitions []Partition `json:"partitions,omitempty"` + + // Filesystems specifies the list of file systems to setup. + // +optional + Filesystems []Filesystem `json:"filesystems,omitempty"` +} + +// Partition defines how to create and layout a partition. +type Partition struct { + // Device is the name of the device. + Device string `json:"device"` + // Layout specifies the device layout. + // If it is true, a single partition will be created for the entire device. + // When layout is false, it means don't partition or ignore existing partitioning. + Layout bool `json:"layout"` + // Overwrite describes whether to skip checks and create the partition if a partition or filesystem is found on the device. + // Use with caution. Default is 'false'. + // +optional + Overwrite *bool `json:"overwrite,omitempty"` + // TableType specifies the tupe of partition table. The following are supported: + // 'mbr': default and setups a MS-DOS partition table + // 'gpt': setups a GPT partition table + // +optional + TableType *string `json:"tableType,omitempty"` +} + +// Filesystem defines the file systems to be created. +type Filesystem struct { + // Device specifies the device name + Device string `json:"device"` + // Filesystem specifies the file system type. + Filesystem string `json:"filesystem"` + // Label specifies the file system label to be used. If set to None, no label is used. + Label string `json:"label"` + // Partition specifies the partition to use. The valid options are: "auto|any", "auto", "any", "none", and , where NUM is the actual partition number. + // +optional + Partition *string `json:"partition,omitempty"` + // Overwrite defines whether or not to overwrite any existing filesystem. + // If true, any pre-existing file system will be destroyed. Use with Caution. + // +optional + Overwrite *bool `json:"overwrite,omitempty"` + // ExtraOpts defined extra options to add to the command for creating the file system. + // +optional + ExtraOpts []string `json:"extraOpts,omitempty"` +} + +// MountPoints defines input for generated mounts in cloud-init. +type MountPoints []string + // +kubebuilder:object:root=true // +kubebuilder:resource:path=eksconfigs,scope=Namespaced,categories=cluster-api,shortName=eksc // +kubebuilder:storageversion diff --git a/bootstrap/eks/api/v1beta2/zz_generated.deepcopy.go b/bootstrap/eks/api/v1beta2/zz_generated.deepcopy.go index 841c9f33a1..891b81f500 100644 --- a/bootstrap/eks/api/v1beta2/zz_generated.deepcopy.go +++ b/bootstrap/eks/api/v1beta2/zz_generated.deepcopy.go @@ -26,6 +26,35 @@ import ( "sigs.k8s.io/cluster-api/api/v1beta1" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DiskSetup) DeepCopyInto(out *DiskSetup) { + *out = *in + if in.Partitions != nil { + in, out := &in.Partitions, &out.Partitions + *out = make([]Partition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Filesystems != nil { + in, out := &in.Filesystems, &out.Filesystems + *out = make([]Filesystem, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DiskSetup. +func (in *DiskSetup) DeepCopy() *DiskSetup { + if in == nil { + return nil + } + out := new(DiskSetup) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EKSConfig) DeepCopyInto(out *EKSConfig) { *out = *in @@ -130,6 +159,56 @@ func (in *EKSConfigSpec) DeepCopyInto(out *EKSConfigSpec) { *out = new(string) **out = **in } + if in.PreBootstrapCommands != nil { + in, out := &in.PreBootstrapCommands, &out.PreBootstrapCommands + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.PostBootstrapCommands != nil { + in, out := &in.PostBootstrapCommands, &out.PostBootstrapCommands + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.BootstrapCommandOverride != nil { + in, out := &in.BootstrapCommandOverride, &out.BootstrapCommandOverride + *out = new(string) + **out = **in + } + if in.Files != nil { + in, out := &in.Files, &out.Files + *out = make([]File, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.DiskSetup != nil { + in, out := &in.DiskSetup, &out.DiskSetup + *out = new(DiskSetup) + (*in).DeepCopyInto(*out) + } + if in.Mounts != nil { + in, out := &in.Mounts, &out.Mounts + *out = make([]MountPoints, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = make(MountPoints, len(*in)) + copy(*out, *in) + } + } + } + if in.Users != nil { + in, out := &in.Users, &out.Users + *out = make([]User, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.NTP != nil { + in, out := &in.NTP, &out.NTP + *out = new(NTP) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EKSConfigSpec. @@ -259,6 +338,157 @@ func (in *EKSConfigTemplateSpec) DeepCopy() *EKSConfigTemplateSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *File) DeepCopyInto(out *File) { + *out = *in + if in.ContentFrom != nil { + in, out := &in.ContentFrom, &out.ContentFrom + *out = new(FileSource) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new File. +func (in *File) DeepCopy() *File { + if in == nil { + return nil + } + out := new(File) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FileSource) DeepCopyInto(out *FileSource) { + *out = *in + out.Secret = in.Secret +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FileSource. +func (in *FileSource) DeepCopy() *FileSource { + if in == nil { + return nil + } + out := new(FileSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Filesystem) DeepCopyInto(out *Filesystem) { + *out = *in + if in.Partition != nil { + in, out := &in.Partition, &out.Partition + *out = new(string) + **out = **in + } + if in.Overwrite != nil { + in, out := &in.Overwrite, &out.Overwrite + *out = new(bool) + **out = **in + } + if in.ExtraOpts != nil { + in, out := &in.ExtraOpts, &out.ExtraOpts + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Filesystem. +func (in *Filesystem) DeepCopy() *Filesystem { + if in == nil { + return nil + } + out := new(Filesystem) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in MountPoints) DeepCopyInto(out *MountPoints) { + { + in := &in + *out = make(MountPoints, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MountPoints. +func (in MountPoints) DeepCopy() MountPoints { + if in == nil { + return nil + } + out := new(MountPoints) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NTP) DeepCopyInto(out *NTP) { + *out = *in + if in.Servers != nil { + in, out := &in.Servers, &out.Servers + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Enabled != nil { + in, out := &in.Enabled, &out.Enabled + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NTP. +func (in *NTP) DeepCopy() *NTP { + if in == nil { + return nil + } + out := new(NTP) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Partition) DeepCopyInto(out *Partition) { + *out = *in + if in.Overwrite != nil { + in, out := &in.Overwrite, &out.Overwrite + *out = new(bool) + **out = **in + } + if in.TableType != nil { + in, out := &in.TableType, &out.TableType + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Partition. +func (in *Partition) DeepCopy() *Partition { + if in == nil { + return nil + } + out := new(Partition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PasswdSource) DeepCopyInto(out *PasswdSource) { + *out = *in + out.Secret = in.Secret +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PasswdSource. +func (in *PasswdSource) DeepCopy() *PasswdSource { + if in == nil { + return nil + } + out := new(PasswdSource) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PauseContainer) DeepCopyInto(out *PauseContainer) { *out = *in @@ -273,3 +503,103 @@ func (in *PauseContainer) DeepCopy() *PauseContainer { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretFileSource) DeepCopyInto(out *SecretFileSource) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretFileSource. +func (in *SecretFileSource) DeepCopy() *SecretFileSource { + if in == nil { + return nil + } + out := new(SecretFileSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretPasswdSource) DeepCopyInto(out *SecretPasswdSource) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretPasswdSource. +func (in *SecretPasswdSource) DeepCopy() *SecretPasswdSource { + if in == nil { + return nil + } + out := new(SecretPasswdSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *User) DeepCopyInto(out *User) { + *out = *in + if in.Gecos != nil { + in, out := &in.Gecos, &out.Gecos + *out = new(string) + **out = **in + } + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = new(string) + **out = **in + } + if in.HomeDir != nil { + in, out := &in.HomeDir, &out.HomeDir + *out = new(string) + **out = **in + } + if in.Inactive != nil { + in, out := &in.Inactive, &out.Inactive + *out = new(bool) + **out = **in + } + if in.Shell != nil { + in, out := &in.Shell, &out.Shell + *out = new(string) + **out = **in + } + if in.Passwd != nil { + in, out := &in.Passwd, &out.Passwd + *out = new(string) + **out = **in + } + if in.PasswdFrom != nil { + in, out := &in.PasswdFrom, &out.PasswdFrom + *out = new(PasswdSource) + **out = **in + } + if in.PrimaryGroup != nil { + in, out := &in.PrimaryGroup, &out.PrimaryGroup + *out = new(string) + **out = **in + } + if in.LockPassword != nil { + in, out := &in.LockPassword, &out.LockPassword + *out = new(bool) + **out = **in + } + if in.Sudo != nil { + in, out := &in.Sudo, &out.Sudo + *out = new(string) + **out = **in + } + if in.SSHAuthorizedKeys != nil { + in, out := &in.SSHAuthorizedKeys, &out.SSHAuthorizedKeys + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new User. +func (in *User) DeepCopy() *User { + if in == nil { + return nil + } + out := new(User) + in.DeepCopyInto(out) + return out +} diff --git a/bootstrap/eks/controllers/eksconfig_controller.go b/bootstrap/eks/controllers/eksconfig_controller.go index 6f6faa5b39..ced21fc98f 100644 --- a/bootstrap/eks/controllers/eksconfig_controller.go +++ b/bootstrap/eks/controllers/eksconfig_controller.go @@ -199,6 +199,11 @@ func (r *EKSConfigReconciler) joinWorker(ctx context.Context, cluster *clusterv1 PreBootstrapCommands: config.Spec.PreBootstrapCommands, PostBootstrapCommands: config.Spec.PostBootstrapCommands, BootstrapCommandOverride: config.Spec.BootstrapCommandOverride, + NTP: config.Spec.NTP, + Users: config.Spec.Users, + DiskSetup: config.Spec.DiskSetup, + Mounts: config.Spec.Mounts, + Files: config.Spec.Files, } if config.Spec.PauseContainer != nil { nodeInput.PauseContainerAccount = &config.Spec.PauseContainer.AccountNumber diff --git a/bootstrap/eks/internal/userdata/commands.go b/bootstrap/eks/internal/userdata/commands.go new file mode 100644 index 0000000000..af7551d8b6 --- /dev/null +++ b/bootstrap/eks/internal/userdata/commands.go @@ -0,0 +1,26 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package userdata + +const ( + commandsTemplate = `{{- define "commands" -}} +{{ range . }} + - {{printf "%q" .}} +{{- end -}} +{{- end -}} +` +) diff --git a/bootstrap/eks/internal/userdata/disk_setup.go b/bootstrap/eks/internal/userdata/disk_setup.go new file mode 100644 index 0000000000..3344427e67 --- /dev/null +++ b/bootstrap/eks/internal/userdata/disk_setup.go @@ -0,0 +1,35 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package userdata + +const ( + diskSetupTemplate = `{{ define "disk_setup" -}} +{{- if . }} +disk_setup:{{ range .Partitions }} + {{ .Device }}: + {{- if .TableType }} + table_type: {{ .TableType }} + {{- end }} + layout: {{ .Layout }} + {{- if .Overwrite }} + overwrite: {{ .Overwrite }} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} +` +) diff --git a/bootstrap/eks/internal/userdata/files.go b/bootstrap/eks/internal/userdata/files.go new file mode 100644 index 0000000000..bcfbf8b665 --- /dev/null +++ b/bootstrap/eks/internal/userdata/files.go @@ -0,0 +1,40 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package userdata + +const ( + filesTemplate = `{{ define "files" -}} +write_files:{{ range . }} + - path: {{.Path}} + {{ if ne .Encoding "" -}} + encoding: "{{.Encoding}}" + {{ end -}} + {{ if ne .Owner "" -}} + owner: {{.Owner}} + {{ end -}} + {{ if ne .Permissions "" -}} + permissions: '{{.Permissions}}' + {{ end -}} + {{ if .Append -}} + append: true + {{ end -}} + content: | +{{.Content | Indent 6}} +{{- end -}} +{{- end -}} +` +) diff --git a/bootstrap/eks/internal/userdata/fs_setup.go b/bootstrap/eks/internal/userdata/fs_setup.go new file mode 100644 index 0000000000..0958b33885 --- /dev/null +++ b/bootstrap/eks/internal/userdata/fs_setup.go @@ -0,0 +1,41 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package userdata + +const ( + fsSetupTemplate = `{{ define "fs_setup" -}} +{{- if . }} +fs_setup:{{ range .Filesystems }} + - label: {{ .Label }} + filesystem: {{ .Filesystem }} + device: {{ .Device }} + {{- if .Partition }} + partition: {{ .Partition }} + {{- end }} + {{- if .Overwrite }} + overwrite: {{ .Overwrite }} + {{- end }} + {{- if .ExtraOpts }} + extra_opts: {{- range .ExtraOpts }} + - {{ . }} + {{- end -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} +` +) diff --git a/bootstrap/eks/internal/userdata/kubelet_args.go b/bootstrap/eks/internal/userdata/kubelet_args.go index 4122701cf9..3fe5a3a9f0 100644 --- a/bootstrap/eks/internal/userdata/kubelet_args.go +++ b/bootstrap/eks/internal/userdata/kubelet_args.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The Kubernetes Authors. +Copyright 2022 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/bootstrap/eks/internal/userdata/mounts.go b/bootstrap/eks/internal/userdata/mounts.go new file mode 100644 index 0000000000..222bac3408 --- /dev/null +++ b/bootstrap/eks/internal/userdata/mounts.go @@ -0,0 +1,29 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package userdata + +const ( + mountsTemplate = `{{ define "mounts" -}} +{{- if . }} +mounts:{{ range . }} + - {{- range . }} + - {{ . }} + {{- end }} +{{- end -}} +{{- end -}} +{{- end -}}` +) diff --git a/bootstrap/eks/internal/userdata/node.go b/bootstrap/eks/internal/userdata/node.go index 8339d01edd..7be304cdb7 100644 --- a/bootstrap/eks/internal/userdata/node.go +++ b/bootstrap/eks/internal/userdata/node.go @@ -22,22 +22,24 @@ import ( "text/template" "github.com/alessio/shellescape" + + eksbootstrapv1 "sigs.k8s.io/cluster-api-provider-aws/v2/bootstrap/eks/api/v1beta2" ) const ( defaultBootstrapCommand = "/etc/eks/bootstrap.sh" - nodeUserData = `#!/bin/bash -set -o errexit; set -o pipefail; set -o nounset; + nodeUserData = `#cloud-config +{{template "files" .Files}} +runcmd: {{- template "commands" .PreBootstrapCommands }} -{{ .BootstrapCommand }} {{.ClusterName}} {{- template "args" . }} + - {{ .BootstrapCommand }} {{.ClusterName}} {{- template "args" . }} {{- template "commands" .PostBootstrapCommands }} -` - commandsTemplate = `{{- define "commands" -}} -{{ range . }} -{{.}} -{{- end -}} -{{- end -}} +{{- template "ntp" .NTP }} +{{- template "users" .Users }} +{{- template "disk_setup" .DiskSetup}} +{{- template "fs_setup" .DiskSetup}} +{{- template "mounts" .Mounts}} ` ) @@ -59,6 +61,11 @@ type NodeInput struct { PreBootstrapCommands []string PostBootstrapCommands []string BootstrapCommandOverride *string + Files []eksbootstrapv1.File + DiskSetup *eksbootstrapv1.DiskSetup + Mounts []eksbootstrapv1.MountPoints + Users []eksbootstrapv1.User + NTP *eksbootstrapv1.NTP } func (ni *NodeInput) DockerConfigJSONEscaped() string { @@ -79,7 +86,11 @@ func (ni *NodeInput) BootstrapCommand() string { // NewNode returns the user data string to be used on a node instance. func NewNode(input *NodeInput) ([]byte, error) { - tm := template.New("Node") + tm := template.New("Node").Funcs(defaultTemplateFuncMap) + + if _, err := tm.Parse(filesTemplate); err != nil { + return nil, fmt.Errorf("failed to parse args template: %w", err) + } if _, err := tm.Parse(argsTemplate); err != nil { return nil, fmt.Errorf("failed to parse args template: %w", err) @@ -93,6 +104,26 @@ func NewNode(input *NodeInput) ([]byte, error) { return nil, fmt.Errorf("failed to parse commandsTemplate template: %w", err) } + if _, err := tm.Parse(ntpTemplate); err != nil { + return nil, fmt.Errorf("failed to parse ntp template: %w", err) + } + + if _, err := tm.Parse(usersTemplate); err != nil { + return nil, fmt.Errorf("failed to parse users template: %w", err) + } + + if _, err := tm.Parse(diskSetupTemplate); err != nil { + return nil, fmt.Errorf("failed to parse disk setup template: %w", err) + } + + if _, err := tm.Parse(fsSetupTemplate); err != nil { + return nil, fmt.Errorf("failed to parse fs setup template: %w", err) + } + + if _, err := tm.Parse(mountsTemplate); err != nil { + return nil, fmt.Errorf("failed to parse mounts template: %w", err) + } + t, err := tm.Parse(nodeUserData) if err != nil { return nil, fmt.Errorf("failed to parse Node template: %w", err) diff --git a/bootstrap/eks/internal/userdata/node_test.go b/bootstrap/eks/internal/userdata/node_test.go index ebad985e57..3f44a68583 100644 --- a/bootstrap/eks/internal/userdata/node_test.go +++ b/bootstrap/eks/internal/userdata/node_test.go @@ -19,9 +19,12 @@ package userdata import ( "testing" + "github.com/aws/aws-sdk-go/aws" . "github.com/onsi/gomega" "github.com/onsi/gomega/format" "k8s.io/utils/pointer" + + eksbootstrapv1 "sigs.k8s.io/cluster-api-provider-aws/v2/bootstrap/eks/api/v1beta2" ) func TestNewNode(t *testing.T) { @@ -45,9 +48,10 @@ func TestNewNode(t *testing.T) { ClusterName: "test-cluster", }, }, - expectedBytes: []byte(`#!/bin/bash -set -o errexit; set -o pipefail; set -o nounset; -/etc/eks/bootstrap.sh test-cluster + expectedBytes: []byte(`#cloud-config +write_files: +runcmd: + - /etc/eks/bootstrap.sh test-cluster `), expectErr: false, }, @@ -62,9 +66,10 @@ set -o errexit; set -o pipefail; set -o nounset; }, }, }, - expectedBytes: []byte(`#!/bin/bash -set -o errexit; set -o pipefail; set -o nounset; -/etc/eks/bootstrap.sh test-cluster --kubelet-extra-args '--node-labels=node-role.undistro.io/infra=true --register-with-taints=dedicated=infra:NoSchedule' + expectedBytes: []byte(`#cloud-config +write_files: +runcmd: + - /etc/eks/bootstrap.sh test-cluster --kubelet-extra-args '--node-labels=node-role.undistro.io/infra=true --register-with-taints=dedicated=infra:NoSchedule' `), }, { @@ -75,9 +80,10 @@ set -o errexit; set -o pipefail; set -o nounset; ContainerRuntime: pointer.String("containerd"), }, }, - expectedBytes: []byte(`#!/bin/bash -set -o errexit; set -o pipefail; set -o nounset; -/etc/eks/bootstrap.sh test-cluster --container-runtime containerd + expectedBytes: []byte(`#cloud-config +write_files: +runcmd: + - /etc/eks/bootstrap.sh test-cluster --container-runtime containerd `), }, { @@ -92,9 +98,10 @@ set -o errexit; set -o pipefail; set -o nounset; ContainerRuntime: pointer.String("containerd"), }, }, - expectedBytes: []byte(`#!/bin/bash -set -o errexit; set -o pipefail; set -o nounset; -/etc/eks/bootstrap.sh test-cluster --kubelet-extra-args '--node-labels=node-role.undistro.io/infra=true --register-with-taints=dedicated=infra:NoSchedule' --container-runtime containerd + expectedBytes: []byte(`#cloud-config +write_files: +runcmd: + - /etc/eks/bootstrap.sh test-cluster --kubelet-extra-args '--node-labels=node-role.undistro.io/infra=true --register-with-taints=dedicated=infra:NoSchedule' --container-runtime containerd `), }, { @@ -106,9 +113,10 @@ set -o errexit; set -o pipefail; set -o nounset; IPFamily: pointer.String("ipv6"), }, }, - expectedBytes: []byte(`#!/bin/bash -set -o errexit; set -o pipefail; set -o nounset; -/etc/eks/bootstrap.sh test-cluster --ip-family ipv6 --service-ipv6-cidr fe80:0000:0000:0000:0204:61ff:fe9d:f156/24 + expectedBytes: []byte(`#cloud-config +write_files: +runcmd: + - /etc/eks/bootstrap.sh test-cluster --ip-family ipv6 --service-ipv6-cidr fe80:0000:0000:0000:0204:61ff:fe9d:f156/24 `), }, { @@ -119,9 +127,10 @@ set -o errexit; set -o pipefail; set -o nounset; UseMaxPods: pointer.Bool(false), }, }, - expectedBytes: []byte(`#!/bin/bash -set -o errexit; set -o pipefail; set -o nounset; -/etc/eks/bootstrap.sh test-cluster --use-max-pods false + expectedBytes: []byte(`#cloud-config +write_files: +runcmd: + - /etc/eks/bootstrap.sh test-cluster --use-max-pods false `), }, { @@ -132,9 +141,10 @@ set -o errexit; set -o pipefail; set -o nounset; APIRetryAttempts: pointer.Int(5), }, }, - expectedBytes: []byte(`#!/bin/bash -set -o errexit; set -o pipefail; set -o nounset; -/etc/eks/bootstrap.sh test-cluster --aws-api-retry-attempts 5 + expectedBytes: []byte(`#cloud-config +write_files: +runcmd: + - /etc/eks/bootstrap.sh test-cluster --aws-api-retry-attempts 5 `), }, { @@ -146,9 +156,10 @@ set -o errexit; set -o pipefail; set -o nounset; PauseContainerVersion: pointer.String("v1"), }, }, - expectedBytes: []byte(`#!/bin/bash -set -o errexit; set -o pipefail; set -o nounset; -/etc/eks/bootstrap.sh test-cluster --pause-container-account 12345678 --pause-container-version v1 + expectedBytes: []byte(`#cloud-config +write_files: +runcmd: + - /etc/eks/bootstrap.sh test-cluster --pause-container-account 12345678 --pause-container-version v1 `), }, { @@ -159,9 +170,10 @@ set -o errexit; set -o pipefail; set -o nounset; DNSClusterIP: pointer.String("192.168.0.1"), }, }, - expectedBytes: []byte(`#!/bin/bash -set -o errexit; set -o pipefail; set -o nounset; -/etc/eks/bootstrap.sh test-cluster --dns-cluster-ip 192.168.0.1 + expectedBytes: []byte(`#cloud-config +write_files: +runcmd: + - /etc/eks/bootstrap.sh test-cluster --dns-cluster-ip 192.168.0.1 `), }, { @@ -172,9 +184,10 @@ set -o errexit; set -o pipefail; set -o nounset; DockerConfigJSON: pointer.String("{\"debug\":true}"), }, }, - expectedBytes: []byte(`#!/bin/bash -set -o errexit; set -o pipefail; set -o nounset; -/etc/eks/bootstrap.sh test-cluster --docker-config-json '{"debug":true}' + expectedBytes: []byte(`#cloud-config +write_files: +runcmd: + - /etc/eks/bootstrap.sh test-cluster --docker-config-json '{"debug":true}' `), }, { @@ -185,11 +198,12 @@ set -o errexit; set -o pipefail; set -o nounset; PreBootstrapCommands: []string{"date", "echo \"testing\""}, }, }, - expectedBytes: []byte(`#!/bin/bash -set -o errexit; set -o pipefail; set -o nounset; -date -echo "testing" -/etc/eks/bootstrap.sh test-cluster + expectedBytes: []byte(`#cloud-config +write_files: +runcmd: + - "date" + - "echo \"testing\"" + - /etc/eks/bootstrap.sh test-cluster `), }, { @@ -200,11 +214,12 @@ echo "testing" PostBootstrapCommands: []string{"date", "echo \"testing\""}, }, }, - expectedBytes: []byte(`#!/bin/bash -set -o errexit; set -o pipefail; set -o nounset; -/etc/eks/bootstrap.sh test-cluster -date -echo "testing" + expectedBytes: []byte(`#cloud-config +write_files: +runcmd: + - /etc/eks/bootstrap.sh test-cluster + - "date" + - "echo \"testing\"" `), }, { @@ -216,11 +231,12 @@ echo "testing" PostBootstrapCommands: []string{"echo \"testing post\""}, }, }, - expectedBytes: []byte(`#!/bin/bash -set -o errexit; set -o pipefail; set -o nounset; -echo "testing pre" -/etc/eks/bootstrap.sh test-cluster -echo "testing post" + expectedBytes: []byte(`#cloud-config +write_files: +runcmd: + - "echo \"testing pre\"" + - /etc/eks/bootstrap.sh test-cluster + - "echo \"testing post\"" `), }, { @@ -231,9 +247,128 @@ echo "testing post" BootstrapCommandOverride: pointer.String("/custom/mybootstrap.sh"), }, }, - expectedBytes: []byte(`#!/bin/bash -set -o errexit; set -o pipefail; set -o nounset; -/custom/mybootstrap.sh test-cluster + expectedBytes: []byte(`#cloud-config +write_files: +runcmd: + - /custom/mybootstrap.sh test-cluster +`), + }, + { + name: "with disk setup and mount points", + args: args{ + input: &NodeInput{ + ClusterName: "test-cluster", + DiskSetup: &eksbootstrapv1.DiskSetup{ + Filesystems: []eksbootstrapv1.Filesystem{ + { + Device: "/dev/sdb", + Filesystem: "ext4", + Label: "vol2", + }, + }, + Partitions: []eksbootstrapv1.Partition{ + { + Device: "/dev/sdb", + Layout: true, + }, + }, + }, + Mounts: []eksbootstrapv1.MountPoints{ + []string{"LABEL=vol2", "/mnt/vol2", "ext4", "defaults"}, + []string{"LABEL=vol2", "/opt/data", "ext4", "defaults"}, + }, + }, + }, + expectedBytes: []byte(`#cloud-config +write_files: +runcmd: + - /etc/eks/bootstrap.sh test-cluster +disk_setup: + /dev/sdb: + layout: true +fs_setup: + - label: vol2 + filesystem: ext4 + device: /dev/sdb +mounts: + - + - LABEL=vol2 + - /mnt/vol2 + - ext4 + - defaults + - + - LABEL=vol2 + - /opt/data + - ext4 + - defaults +`), + }, + { + name: "with files", + args: args{ + input: &NodeInput{ + ClusterName: "test-cluster", + Files: []eksbootstrapv1.File{ + { + Path: "/etc/sysctl.d/91-fs-inotify.conf", + Content: "fs.inotify.max_user_instances=256", + }, + }, + }, + }, + expectedBytes: []byte(`#cloud-config +write_files: + - path: /etc/sysctl.d/91-fs-inotify.conf + content: | + fs.inotify.max_user_instances=256 +runcmd: + - /etc/eks/bootstrap.sh test-cluster +`), + }, + { + name: "with ntp", + args: args{ + input: &NodeInput{ + ClusterName: "test-cluster", + NTP: &eksbootstrapv1.NTP{ + Enabled: aws.Bool(true), + Servers: []string{"time1.google.com", "time2.google.com", "time3.google.com", "time4.google.com"}, + }, + }, + }, + expectedBytes: []byte(`#cloud-config +write_files: +runcmd: + - /etc/eks/bootstrap.sh test-cluster +ntp: + enabled: true + servers: + - time1.google.com + - time2.google.com + - time3.google.com + - time4.google.com +`), + }, + { + name: "with users", + args: args{ + input: &NodeInput{ + ClusterName: "test-cluster", + Users: []eksbootstrapv1.User{ + { + Name: "testuser", + Shell: aws.String("/bin/bash"), + }, + }, + }, + }, + expectedBytes: []byte(`#cloud-config +write_files: +runcmd: + - /etc/eks/bootstrap.sh test-cluster +users: + - name: testuser + shell: /bin/bash `), }, } diff --git a/bootstrap/eks/internal/userdata/ntp.go b/bootstrap/eks/internal/userdata/ntp.go new file mode 100644 index 0000000000..2587bc1371 --- /dev/null +++ b/bootstrap/eks/internal/userdata/ntp.go @@ -0,0 +1,32 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package userdata + +const ( + ntpTemplate = `{{ define "ntp" -}} +{{- if . }} +ntp: + {{ if .Enabled -}} + enabled: true + {{ end -}} + servers:{{ range .Servers }} + - {{ . }} + {{- end -}} +{{- end -}} +{{- end -}} +` +) diff --git a/bootstrap/eks/internal/userdata/users.go b/bootstrap/eks/internal/userdata/users.go new file mode 100644 index 0000000000..afe6887062 --- /dev/null +++ b/bootstrap/eks/internal/userdata/users.go @@ -0,0 +1,60 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package userdata + +const ( + usersTemplate = `{{ define "users" -}} +{{- if . }} +users:{{ range . }} + - name: {{ .Name }} + {{- if .Passwd }} + passwd: {{ .Passwd }} + {{- end -}} + {{- if .Gecos }} + gecos: {{ .Gecos }} + {{- end -}} + {{- if .Groups }} + groups: {{ .Groups }} + {{- end -}} + {{- if .HomeDir }} + homedir: {{ .HomeDir }} + {{- end -}} + {{- if .Inactive }} + inactive: true + {{- end -}} + {{- if .LockPassword }} + lock_passwd: {{ .LockPassword }} + {{- end -}} + {{- if .Shell }} + shell: {{ .Shell }} + {{- end -}} + {{- if .PrimaryGroup }} + primary_group: {{ .PrimaryGroup }} + {{- end -}} + {{- if .Sudo }} + sudo: {{ .Sudo }} + {{- end -}} + {{- if .SSHAuthorizedKeys }} + ssh_authorized_keys:{{ range .SSHAuthorizedKeys }} + - {{ . }} + {{- end -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} +` +) diff --git a/bootstrap/eks/internal/userdata/utils.go b/bootstrap/eks/internal/userdata/utils.go new file mode 100644 index 0000000000..aee2a82125 --- /dev/null +++ b/bootstrap/eks/internal/userdata/utils.go @@ -0,0 +1,34 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package userdata + +import ( + "strings" + "text/template" +) + +var ( + defaultTemplateFuncMap = template.FuncMap{ + "Indent": templateYAMLIndent, + } +) + +func templateYAMLIndent(i int, input string) string { + split := strings.Split(input, "\n") + ident := "\n" + strings.Repeat(" ", i) + return strings.Repeat(" ", i) + strings.Join(split, ident) +} diff --git a/config/crd/bases/bootstrap.cluster.x-k8s.io_eksconfigs.yaml b/config/crd/bases/bootstrap.cluster.x-k8s.io_eksconfigs.yaml index 56e4dfe08d..62444bbcd5 100644 --- a/config/crd/bases/bootstrap.cluster.x-k8s.io_eksconfigs.yaml +++ b/config/crd/bases/bootstrap.cluster.x-k8s.io_eksconfigs.yaml @@ -213,6 +213,80 @@ spec: description: ContainerRuntime specify the container runtime to use when bootstrapping EKS. type: string + diskSetup: + description: DiskSetup specifies options for the creation of partition + tables and file systems on devices. + properties: + filesystems: + description: Filesystems specifies the list of file systems to + setup. + items: + description: Filesystem defines the file systems to be created. + properties: + device: + description: Device specifies the device name + type: string + extraOpts: + description: ExtraOpts defined extra options to add to the + command for creating the file system. + items: + type: string + type: array + filesystem: + description: Filesystem specifies the file system type. + type: string + label: + description: Label specifies the file system label to be + used. If set to None, no label is used. + type: string + overwrite: + description: Overwrite defines whether or not to overwrite + any existing filesystem. If true, any pre-existing file + system will be destroyed. Use with Caution. + type: boolean + partition: + description: 'Partition specifies the partition to use. + The valid options are: "auto|any", "auto", "any", "none", + and , where NUM is the actual partition number.' + type: string + required: + - device + - filesystem + - label + type: object + type: array + partitions: + description: Partitions specifies the list of the partitions to + setup. + items: + description: Partition defines how to create and layout a partition. + properties: + device: + description: Device is the name of the device. + type: string + layout: + description: Layout specifies the device layout. If it is + true, a single partition will be created for the entire + device. When layout is false, it means don't partition + or ignore existing partitioning. + type: boolean + overwrite: + description: Overwrite describes whether to skip checks + and create the partition if a partition or filesystem + is found on the device. Use with caution. Default is 'false'. + type: boolean + tableType: + description: 'TableType specifies the tupe of partition + table. The following are supported: ''mbr'': default and + setups a MS-DOS partition table ''gpt'': setups a GPT + partition table' + type: string + required: + - device + - layout + type: object + type: array + type: object dnsClusterIP: description: DNSClusterIP overrides the IP address to use for DNS queries within the cluster. @@ -222,12 +296,92 @@ spec: file. Useful if you want a custom config differing from the default one in the AMI. This is expected to be a json string. type: string + files: + description: Files specifies extra files to be passed to user_data + upon creation. + items: + description: File defines the input for generating write_files in + cloud-init. + properties: + append: + description: Append specifies whether to append Content to existing + file if Path exists. + type: boolean + content: + description: Content is the actual content of the file. + type: string + contentFrom: + description: ContentFrom is a referenced source of content to + populate the file. + properties: + secret: + description: Secret represents a secret that should populate + this file. + properties: + key: + description: Key is the key in the secret's data map + for this value. + type: string + name: + description: Name of the secret in the KubeadmBootstrapConfig's + namespace to use. + type: string + required: + - key + - name + type: object + required: + - secret + type: object + encoding: + description: Encoding specifies the encoding of the file contents. + enum: + - base64 + - gzip + - gzip+base64 + type: string + owner: + description: Owner specifies the ownership of the file, e.g. + "root:root". + type: string + path: + description: Path specifies the full path on disk where to store + the file. + type: string + permissions: + description: Permissions specifies the permissions to assign + to the file, e.g. "0640". + type: string + required: + - path + type: object + type: array kubeletExtraArgs: additionalProperties: type: string description: KubeletExtraArgs passes the specified kubelet args into the Amazon EKS machine bootstrap script type: object + mounts: + description: Mounts specifies a list of mount points to be setup. + items: + description: MountPoints defines input for generated mounts in cloud-init. + items: + type: string + type: array + type: array + ntp: + description: NTP specifies NTP configuration + properties: + enabled: + description: Enabled specifies whether NTP should be enabled + type: boolean + servers: + description: Servers specifies which NTP servers to use + items: + type: string + type: array + type: object pauseContainer: description: PauseContainer allows customization of the pause container to use. @@ -243,10 +397,6 @@ spec: - accountNumber - version type: object - serviceIPV6Cidr: - description: ServiceIPV6Cidr is the ipv6 cidr range of the cluster. - If this is specified then the ip family will be set to ipv6. - type: string postBootstrapCommands: description: PostBootstrapCommands specifies extra commands to run after bootstrapping nodes to the cluster @@ -259,9 +409,86 @@ spec: items: type: string type: array + serviceIPV6Cidr: + description: ServiceIPV6Cidr is the ipv6 cidr range of the cluster. + If this is specified then the ip family will be set to ipv6. + type: string useMaxPods: description: UseMaxPods sets --max-pods for the kubelet when true. type: boolean + users: + description: Users specifies extra users to add + items: + description: User defines the input for a generated user in cloud-init. + properties: + gecos: + description: Gecos specifies the gecos to use for the user + type: string + groups: + description: Groups specifies the additional groups for the + user + type: string + homeDir: + description: HomeDir specifies the home directory to use for + the user + type: string + inactive: + description: Inactive specifies whether to mark the user as + inactive + type: boolean + lockPassword: + description: LockPassword specifies if password login should + be disabled + type: boolean + name: + description: Name specifies the username + type: string + passwd: + description: Passwd specifies a hashed password for the user + type: string + passwdFrom: + description: PasswdFrom is a referenced source of passwd to + populate the passwd. + properties: + secret: + description: Secret represents a secret that should populate + this password. + properties: + key: + description: Key is the key in the secret's data map + for this value. + type: string + name: + description: Name of the secret in the KubeadmBootstrapConfig's + namespace to use. + type: string + required: + - key + - name + type: object + required: + - secret + type: object + primaryGroup: + description: PrimaryGroup specifies the primary group for the + user + type: string + shell: + description: Shell specifies the user's shell + type: string + sshAuthorizedKeys: + description: SSHAuthorizedKeys specifies a list of ssh authorized + keys for the user + items: + type: string + type: array + sudo: + description: Sudo specifies a sudo role for the user + type: string + required: + - name + type: object + type: array type: object status: description: EKSConfigStatus defines the observed state of the Amazon diff --git a/config/crd/bases/bootstrap.cluster.x-k8s.io_eksconfigtemplates.yaml b/config/crd/bases/bootstrap.cluster.x-k8s.io_eksconfigtemplates.yaml index 3bc7a1be9a..8d9e0996fe 100644 --- a/config/crd/bases/bootstrap.cluster.x-k8s.io_eksconfigtemplates.yaml +++ b/config/crd/bases/bootstrap.cluster.x-k8s.io_eksconfigtemplates.yaml @@ -146,6 +146,86 @@ spec: description: ContainerRuntime specify the container runtime to use when bootstrapping EKS. type: string + diskSetup: + description: DiskSetup specifies options for the creation + of partition tables and file systems on devices. + properties: + filesystems: + description: Filesystems specifies the list of file systems + to setup. + items: + description: Filesystem defines the file systems to + be created. + properties: + device: + description: Device specifies the device name + type: string + extraOpts: + description: ExtraOpts defined extra options to + add to the command for creating the file system. + items: + type: string + type: array + filesystem: + description: Filesystem specifies the file system + type. + type: string + label: + description: Label specifies the file system label + to be used. If set to None, no label is used. + type: string + overwrite: + description: Overwrite defines whether or not to + overwrite any existing filesystem. If true, any + pre-existing file system will be destroyed. Use + with Caution. + type: boolean + partition: + description: 'Partition specifies the partition + to use. The valid options are: "auto|any", "auto", + "any", "none", and , where NUM is the actual + partition number.' + type: string + required: + - device + - filesystem + - label + type: object + type: array + partitions: + description: Partitions specifies the list of the partitions + to setup. + items: + description: Partition defines how to create and layout + a partition. + properties: + device: + description: Device is the name of the device. + type: string + layout: + description: Layout specifies the device layout. + If it is true, a single partition will be created + for the entire device. When layout is false, it + means don't partition or ignore existing partitioning. + type: boolean + overwrite: + description: Overwrite describes whether to skip + checks and create the partition if a partition + or filesystem is found on the device. Use with + caution. Default is 'false'. + type: boolean + tableType: + description: 'TableType specifies the tupe of partition + table. The following are supported: ''mbr'': default + and setups a MS-DOS partition table ''gpt'': setups + a GPT partition table' + type: string + required: + - device + - layout + type: object + type: array + type: object dnsClusterIP: description: DNSClusterIP overrides the IP address to use for DNS queries within the cluster. @@ -156,12 +236,95 @@ spec: config differing from the default one in the AMI. This is expected to be a json string. type: string + files: + description: Files specifies extra files to be passed to user_data + upon creation. + items: + description: File defines the input for generating write_files + in cloud-init. + properties: + append: + description: Append specifies whether to append Content + to existing file if Path exists. + type: boolean + content: + description: Content is the actual content of the file. + type: string + contentFrom: + description: ContentFrom is a referenced source of content + to populate the file. + properties: + secret: + description: Secret represents a secret that should + populate this file. + properties: + key: + description: Key is the key in the secret's + data map for this value. + type: string + name: + description: Name of the secret in the KubeadmBootstrapConfig's + namespace to use. + type: string + required: + - key + - name + type: object + required: + - secret + type: object + encoding: + description: Encoding specifies the encoding of the + file contents. + enum: + - base64 + - gzip + - gzip+base64 + type: string + owner: + description: Owner specifies the ownership of the file, + e.g. "root:root". + type: string + path: + description: Path specifies the full path on disk where + to store the file. + type: string + permissions: + description: Permissions specifies the permissions to + assign to the file, e.g. "0640". + type: string + required: + - path + type: object + type: array kubeletExtraArgs: additionalProperties: type: string description: KubeletExtraArgs passes the specified kubelet args into the Amazon EKS machine bootstrap script type: object + mounts: + description: Mounts specifies a list of mount points to be + setup. + items: + description: MountPoints defines input for generated mounts + in cloud-init. + items: + type: string + type: array + type: array + ntp: + description: NTP specifies NTP configuration + properties: + enabled: + description: Enabled specifies whether NTP should be enabled + type: boolean + servers: + description: Servers specifies which NTP servers to use + items: + type: string + type: array + type: object pauseContainer: description: PauseContainer allows customization of the pause container to use. @@ -178,11 +341,6 @@ spec: - accountNumber - version type: object - serviceIPV6Cidr: - description: ServiceIPV6Cidr is the ipv6 cidr range of the - cluster. If this is specified then the ip family will be - set to ipv6. - type: string postBootstrapCommands: description: PostBootstrapCommands specifies extra commands to run after bootstrapping nodes to the cluster @@ -195,10 +353,91 @@ spec: items: type: string type: array + serviceIPV6Cidr: + description: ServiceIPV6Cidr is the ipv6 cidr range of the + cluster. If this is specified then the ip family will be + set to ipv6. + type: string useMaxPods: description: UseMaxPods sets --max-pods for the kubelet when true. type: boolean + users: + description: Users specifies extra users to add + items: + description: User defines the input for a generated user + in cloud-init. + properties: + gecos: + description: Gecos specifies the gecos to use for the + user + type: string + groups: + description: Groups specifies the additional groups + for the user + type: string + homeDir: + description: HomeDir specifies the home directory to + use for the user + type: string + inactive: + description: Inactive specifies whether to mark the + user as inactive + type: boolean + lockPassword: + description: LockPassword specifies if password login + should be disabled + type: boolean + name: + description: Name specifies the username + type: string + passwd: + description: Passwd specifies a hashed password for + the user + type: string + passwdFrom: + description: PasswdFrom is a referenced source of passwd + to populate the passwd. + properties: + secret: + description: Secret represents a secret that should + populate this password. + properties: + key: + description: Key is the key in the secret's + data map for this value. + type: string + name: + description: Name of the secret in the KubeadmBootstrapConfig's + namespace to use. + type: string + required: + - key + - name + type: object + required: + - secret + type: object + primaryGroup: + description: PrimaryGroup specifies the primary group + for the user + type: string + shell: + description: Shell specifies the user's shell + type: string + sshAuthorizedKeys: + description: SSHAuthorizedKeys specifies a list of ssh + authorized keys for the user + items: + type: string + type: array + sudo: + description: Sudo specifies a sudo role for the user + type: string + required: + - name + type: object + type: array type: object type: object required: