diff --git a/bootstrap/api/v1beta2/ck8sconfig_types.go b/bootstrap/api/v1beta2/ck8sconfig_types.go index 3d936b7b..31acb1f8 100644 --- a/bootstrap/api/v1beta2/ck8sconfig_types.go +++ b/bootstrap/api/v1beta2/ck8sconfig_types.go @@ -69,6 +69,18 @@ type CK8sConfigSpec struct { // +optional SnapstoreProxyID string `json:"snapstoreProxyId,omitempty"` + // HTTPSProxy is optional https proxy configuration + // +optional + HTTPSProxy string `json:"httpsProxy,omitempty"` + + // HTTPProxy is optional http proxy configuration + // +optional + HTTPProxy string `json:"httpProxy,omitempty"` + + // NoProxy is optional no proxy configuration + // +optional + NoProxy string `json:"noProxy,omitempty"` + // Channel is the channel to use for the snap install. // +optional Channel string `json:"channel,omitempty"` diff --git a/bootstrap/config/crd/bases/bootstrap.cluster.x-k8s.io_ck8sconfigs.yaml b/bootstrap/config/crd/bases/bootstrap.cluster.x-k8s.io_ck8sconfigs.yaml index b237b1aa..d0b175e2 100644 --- a/bootstrap/config/crd/bases/bootstrap.cluster.x-k8s.io_ck8sconfigs.yaml +++ b/bootstrap/config/crd/bases/bootstrap.cluster.x-k8s.io_ck8sconfigs.yaml @@ -51,9 +51,6 @@ spec: items: type: string type: array - channel: - description: Channel is the channel to use for the snap install. - type: string bootstrapConfig: description: BootstrapConfig is the data to be passed to the bootstrap script. @@ -87,6 +84,9 @@ spec: - secret type: object type: object + channel: + description: Channel is the channel to use for the snap install. + type: string controlPlane: description: CK8sControlPlaneConfig is configuration for the control plane node. @@ -201,6 +201,12 @@ spec: - path type: object type: array + httpProxy: + description: HTTPProxy is optional http proxy configuration + type: string + httpsProxy: + description: HTTPSProxy is optional https proxy configuration + type: string initConfig: description: CK8sInitConfig is configuration for the initializing the cluster features. @@ -233,6 +239,9 @@ spec: LocalPath is the path of a local snap file in the workload cluster to use for the snap install. If Channel or Revision are set, this will be ignored. type: string + noProxy: + description: NoProxy is optional no proxy configuration + type: string nodeName: description: |- NodeName is the name to use for the kubelet of this node. It is needed for clouds diff --git a/bootstrap/config/crd/bases/bootstrap.cluster.x-k8s.io_ck8sconfigtemplates.yaml b/bootstrap/config/crd/bases/bootstrap.cluster.x-k8s.io_ck8sconfigtemplates.yaml index c868cfd5..a9fd896b 100644 --- a/bootstrap/config/crd/bases/bootstrap.cluster.x-k8s.io_ck8sconfigtemplates.yaml +++ b/bootstrap/config/crd/bases/bootstrap.cluster.x-k8s.io_ck8sconfigtemplates.yaml @@ -58,9 +58,6 @@ spec: items: type: string type: array - channel: - description: Channel is the channel to use for the snap install. - type: string bootstrapConfig: description: BootstrapConfig is the data to be passed to the bootstrap script. @@ -94,6 +91,9 @@ spec: - secret type: object type: object + channel: + description: Channel is the channel to use for the snap install. + type: string controlPlane: description: CK8sControlPlaneConfig is configuration for the control plane node. @@ -210,6 +210,12 @@ spec: - path type: object type: array + httpProxy: + description: HTTPProxy is optional http proxy configuration + type: string + httpsProxy: + description: HTTPSProxy is optional https proxy configuration + type: string initConfig: description: CK8sInitConfig is configuration for the initializing the cluster features. @@ -242,6 +248,9 @@ spec: LocalPath is the path of a local snap file in the workload cluster to use for the snap install. If Channel or Revision are set, this will be ignored. type: string + noProxy: + description: NoProxy is optional no proxy configuration + type: string nodeName: description: |- NodeName is the name to use for the kubelet of this node. It is needed for clouds diff --git a/bootstrap/controllers/ck8sconfig_controller.go b/bootstrap/controllers/ck8sconfig_controller.go index 82d8002e..60489ef8 100644 --- a/bootstrap/controllers/ck8sconfig_controller.go +++ b/bootstrap/controllers/ck8sconfig_controller.go @@ -378,6 +378,9 @@ func (r *CK8sConfigReconciler) joinWorker(ctx context.Context, scope *Scope) err ConfigFileContents: string(joinConfig), MicroclusterAddress: scope.Config.Spec.ControlPlaneConfig.MicroclusterAddress, MicroclusterPort: microclusterPort, + HTTPProxy: scope.Config.Spec.HTTPProxy, + HTTPSProxy: scope.Config.Spec.HTTPSProxy, + NoProxy: scope.Config.Spec.NoProxy, AirGapped: scope.Config.Spec.AirGapped, SnapstoreProxyScheme: scope.Config.Spec.SnapstoreProxyScheme, SnapstoreProxyDomain: scope.Config.Spec.SnapstoreProxyDomain, @@ -693,6 +696,9 @@ func (r *CK8sConfigReconciler) handleClusterNotInitialized(ctx context.Context, MicroclusterAddress: scope.Config.Spec.ControlPlaneConfig.MicroclusterAddress, MicroclusterPort: microclusterPort, NodeName: scope.Config.Spec.NodeName, + HTTPProxy: scope.Config.Spec.HTTPProxy, + HTTPSProxy: scope.Config.Spec.HTTPSProxy, + NoProxy: scope.Config.Spec.NoProxy, AirGapped: scope.Config.Spec.AirGapped, SnapstoreProxyScheme: scope.Config.Spec.SnapstoreProxyScheme, SnapstoreProxyDomain: scope.Config.Spec.SnapstoreProxyDomain, diff --git a/controlplane/config/crd/bases/controlplane.cluster.x-k8s.io_ck8scontrolplanes.yaml b/controlplane/config/crd/bases/controlplane.cluster.x-k8s.io_ck8scontrolplanes.yaml index 7dc56812..bc1ad0bb 100644 --- a/controlplane/config/crd/bases/controlplane.cluster.x-k8s.io_ck8scontrolplanes.yaml +++ b/controlplane/config/crd/bases/controlplane.cluster.x-k8s.io_ck8scontrolplanes.yaml @@ -246,9 +246,6 @@ spec: items: type: string type: array - channel: - description: Channel is the channel to use for the snap install. - type: string bootstrapConfig: description: BootstrapConfig is the data to be passed to the bootstrap script. @@ -282,6 +279,9 @@ spec: - secret type: object type: object + channel: + description: Channel is the channel to use for the snap install. + type: string controlPlane: description: CK8sControlPlaneConfig is configuration for the control plane node. @@ -398,6 +398,12 @@ spec: - path type: object type: array + httpProxy: + description: HTTPProxy is optional http proxy configuration + type: string + httpsProxy: + description: HTTPSProxy is optional https proxy configuration + type: string initConfig: description: CK8sInitConfig is configuration for the initializing the cluster features. @@ -430,6 +436,9 @@ spec: LocalPath is the path of a local snap file in the workload cluster to use for the snap install. If Channel or Revision are set, this will be ignored. type: string + noProxy: + description: NoProxy is optional no proxy configuration + type: string nodeName: description: |- NodeName is the name to use for the kubelet of this node. It is needed for clouds diff --git a/controlplane/config/crd/bases/controlplane.cluster.x-k8s.io_ck8scontrolplanetemplates.yaml b/controlplane/config/crd/bases/controlplane.cluster.x-k8s.io_ck8scontrolplanetemplates.yaml index ff6e0fcd..715a65e3 100644 --- a/controlplane/config/crd/bases/controlplane.cluster.x-k8s.io_ck8scontrolplanetemplates.yaml +++ b/controlplane/config/crd/bases/controlplane.cluster.x-k8s.io_ck8scontrolplanetemplates.yaml @@ -221,10 +221,6 @@ spec: items: type: string type: array - channel: - description: Channel is the channel to use for the snap - install. - type: string bootstrapConfig: description: BootstrapConfig is the data to be passed to the bootstrap script. @@ -258,6 +254,10 @@ spec: - secret type: object type: object + channel: + description: Channel is the channel to use for the snap + install. + type: string controlPlane: description: CK8sControlPlaneConfig is configuration for the control plane node. @@ -376,6 +376,12 @@ spec: - path type: object type: array + httpProxy: + description: HTTPProxy is optional http proxy configuration + type: string + httpsProxy: + description: HTTPSProxy is optional https proxy configuration + type: string initConfig: description: CK8sInitConfig is configuration for the initializing the cluster features. @@ -408,6 +414,9 @@ spec: LocalPath is the path of a local snap file in the workload cluster to use for the snap install. If Channel or Revision are set, this will be ignored. type: string + noProxy: + description: NoProxy is optional no proxy configuration + type: string nodeName: description: |- NodeName is the name to use for the kubelet of this node. It is needed for clouds diff --git a/pkg/cloudinit/common.go b/pkg/cloudinit/common.go index 7f820350..bd3320b8 100644 --- a/pkg/cloudinit/common.go +++ b/pkg/cloudinit/common.go @@ -49,6 +49,12 @@ type BaseUserData struct { SnapstoreProxyDomain string // The snap store proxy ID SnapstoreProxyID string + // HTTPProxy is http_proxy configuration. + HTTPProxy string + // HTTPSProxy is https_proxy configuration. + HTTPSProxy string + // NoProxy is no_proxy configuration. + NoProxy string // MicroclusterAddress is the address to use for microcluster. MicroclusterAddress string // MicroclusterPort is the port to use for microcluster. @@ -95,6 +101,12 @@ func NewBaseCloudConfig(data BaseUserData) (CloudConfig, error) { config.RunCommands = append(config.RunCommands, "/capi/scripts/configure-snapstore-proxy.sh") } + // proxy configuration + if proxyConfigFiles := getProxyConfigFiles(data); proxyConfigFiles != nil { + config.WriteFiles = append(config.WriteFiles, proxyConfigFiles...) + config.RunCommands = append(config.RunCommands, "/capi/scripts/configure-proxy.sh") + } + var configFileContents string if data.BootstrapConfig != "" { configFileContents = data.BootstrapConfig @@ -139,7 +151,6 @@ func NewBaseCloudConfig(data BaseUserData) (CloudConfig, error) { }, )..., ) - // boot commands config.BootCommands = data.BootCommands @@ -190,3 +201,36 @@ func getSnapstoreProxyConfigFiles(data BaseUserData) []File { return []File{schemeFile, domainFile, storeIDFile} } + +// getProxyConfigFiles returns the proxy config files. +// Returns slice of files for each proxy parameters are present in data structure with corresponding value +// Nil indicates that no files are returned. +func getProxyConfigFiles(data BaseUserData) []File { + var files []File + if data.HTTPProxy != "" { + files = append(files, File{ + Path: "/capi/etc/http-proxy", + Content: data.HTTPProxy, + Permissions: "0400", + Owner: "root:root", + }) + } + if data.HTTPSProxy != "" { + files = append(files, File{ + Path: "/capi/etc/https-proxy", + Content: data.HTTPSProxy, + Permissions: "0400", + Owner: "root:root", + }) + } + if data.NoProxy != "" { + files = append(files, File{ + Path: "/capi/etc/no-proxy", + Content: data.NoProxy, + Permissions: "0400", + Owner: "root:root", + }) + } + + return files +} diff --git a/pkg/cloudinit/controlplane_init_test.go b/pkg/cloudinit/controlplane_init_test.go index d113658c..676fe9ae 100644 --- a/pkg/cloudinit/controlplane_init_test.go +++ b/pkg/cloudinit/controlplane_init_test.go @@ -23,6 +23,7 @@ import ( . "github.com/onsi/gomega" format "github.com/onsi/gomega/format" "github.com/onsi/gomega/gstruct" + "github.com/onsi/gomega/types" "github.com/canonical/cluster-api-k8s/pkg/cloudinit" ) @@ -88,6 +89,7 @@ func TestNewInitControlPlane(t *testing.T) { HaveField("Path", "/capi/scripts/wait-apiserver-ready.sh"), HaveField("Path", "/capi/scripts/deploy-manifests.sh"), HaveField("Path", "/capi/scripts/configure-auth-token.sh"), + HaveField("Path", "/capi/scripts/configure-proxy.sh"), HaveField("Path", "/capi/scripts/configure-node-token.sh"), HaveField("Path", "/capi/scripts/create-sentinel-bootstrap.sh"), HaveField("Path", "/capi/scripts/configure-snapstore-proxy.sh"), @@ -105,6 +107,73 @@ func TestNewInitControlPlane(t *testing.T) { ), "Some /capi/scripts files are missing") } +func TestNewInitControlPlaneWithOptionalProxySettings(t *testing.T) { + g := NewWithT(t) + for _, tc := range []struct { + name string + baseUserData cloudinit.BaseUserData + expectRunCommand bool + expectWriteFiles []types.GomegaMatcher + }{ + { + name: "AllProxyFieldsSet", + baseUserData: cloudinit.BaseUserData{ + KubernetesVersion: "v1.30.0", + HTTPProxy: "http://proxy.internal", + HTTPSProxy: "https://proxy.internal", + NoProxy: "10.0.0.0/8,10.152.183.1,192.168.0.0/16", + MicroclusterAddress: "10.0.0.0/8", + }, + expectRunCommand: true, + expectWriteFiles: []types.GomegaMatcher{ + HaveField("Path", "/capi/scripts/configure-proxy.sh"), + HaveField("Path", "/capi/etc/http-proxy"), + HaveField("Path", "/capi/etc/https-proxy"), + HaveField("Path", "/capi/etc/no-proxy"), + }, + }, + { + name: "HTTPSProxyOnly", + baseUserData: cloudinit.BaseUserData{ + KubernetesVersion: "v1.30.0", + HTTPSProxy: "https://proxy.internal", + MicroclusterAddress: "10.0.0.0/8", + }, + expectRunCommand: true, + expectWriteFiles: []types.GomegaMatcher{ + HaveField("Path", "/capi/scripts/configure-proxy.sh"), + HaveField("Path", "/capi/etc/https-proxy"), + }, + }, + { + name: "NoProxyFields", + baseUserData: cloudinit.BaseUserData{ + KubernetesVersion: "v1.30.0", + MicroclusterAddress: "10.0.0.0/8", + }, + expectRunCommand: false, + expectWriteFiles: []types.GomegaMatcher{ + HaveField("Path", "/capi/scripts/configure-proxy.sh"), + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + config, err := cloudinit.NewInitControlPlane(cloudinit.InitControlPlaneInput{BaseUserData: tc.baseUserData}) + + g.Expect(err).ToNot(HaveOccurred()) + // Verify proxy run command. + if tc.expectRunCommand { + g.Expect(config.RunCommands).To(ContainElement("/capi/scripts/configure-proxy.sh")) + } else { + g.Expect(config.RunCommands).NotTo(ContainElement("/capi/scripts/configure-proxy.sh")) + } + // Verify proxy files present. + g.Expect(config.WriteFiles).To(ContainElements(tc.expectWriteFiles), + "Required files in /capi directory are missing") + }) + } +} + func TestUserSuppliedBootstrapConfig(t *testing.T) { g := NewWithT(t) diff --git a/pkg/cloudinit/controlplane_join_test.go b/pkg/cloudinit/controlplane_join_test.go index a9a009ae..57eca5c5 100644 --- a/pkg/cloudinit/controlplane_join_test.go +++ b/pkg/cloudinit/controlplane_join_test.go @@ -6,6 +6,7 @@ import ( . "github.com/onsi/gomega" "github.com/onsi/gomega/gstruct" + "github.com/onsi/gomega/types" "github.com/canonical/cluster-api-k8s/pkg/cloudinit" ) @@ -66,6 +67,7 @@ func TestNewJoinControlPlane(t *testing.T) { HaveField("Path", "/capi/scripts/wait-apiserver-ready.sh"), HaveField("Path", "/capi/scripts/deploy-manifests.sh"), HaveField("Path", "/capi/scripts/configure-auth-token.sh"), + HaveField("Path", "/capi/scripts/configure-proxy.sh"), HaveField("Path", "/capi/scripts/configure-node-token.sh"), HaveField("Path", "/capi/scripts/create-sentinel-bootstrap.sh"), HaveField("Path", "/capi/scripts/configure-snapstore-proxy.sh"), @@ -82,6 +84,73 @@ func TestNewJoinControlPlane(t *testing.T) { ), "Some /capi/scripts files are missing") } +func TestNewJoinControlPlaneOptionalProxySettings(t *testing.T) { + g := NewWithT(t) + for _, tc := range []struct { + name string + baseUserData cloudinit.BaseUserData + expectRunCommand bool + expectWriteFiles []types.GomegaMatcher + }{ + { + name: "AllProxyFieldsSet", + baseUserData: cloudinit.BaseUserData{ + KubernetesVersion: "v1.30.0", + HTTPProxy: "http://proxy.internal", + HTTPSProxy: "https://proxy.internal", + NoProxy: "10.0.0.0/8,10.152.183.1,192.168.0.0/16", + MicroclusterAddress: "10.0.0.0/8", + }, + expectRunCommand: true, + expectWriteFiles: []types.GomegaMatcher{ + HaveField("Path", "/capi/scripts/configure-proxy.sh"), + HaveField("Path", "/capi/etc/http-proxy"), + HaveField("Path", "/capi/etc/https-proxy"), + HaveField("Path", "/capi/etc/no-proxy"), + }, + }, + { + name: "HTTPSProxyOnly", + baseUserData: cloudinit.BaseUserData{ + KubernetesVersion: "v1.30.0", + HTTPSProxy: "https://proxy.internal", + MicroclusterAddress: "10.0.0.0/8", + }, + expectRunCommand: true, + expectWriteFiles: []types.GomegaMatcher{ + HaveField("Path", "/capi/scripts/configure-proxy.sh"), + HaveField("Path", "/capi/etc/https-proxy"), + }, + }, + { + name: "NoProxyFields", + baseUserData: cloudinit.BaseUserData{ + KubernetesVersion: "v1.30.0", + MicroclusterAddress: "10.0.0.0/8", + }, + expectRunCommand: false, + expectWriteFiles: []types.GomegaMatcher{ + HaveField("Path", "/capi/scripts/configure-proxy.sh"), + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + config, err := cloudinit.NewJoinControlPlane(cloudinit.JoinControlPlaneInput{BaseUserData: tc.baseUserData}) + + g.Expect(err).ToNot(HaveOccurred()) + // Verify proxy run command. + if tc.expectRunCommand { + g.Expect(config.RunCommands).To(ContainElement("/capi/scripts/configure-proxy.sh")) + } else { + g.Expect(config.RunCommands).NotTo(ContainElement("/capi/scripts/configure-proxy.sh")) + } + // Verify proxy files present. + g.Expect(config.WriteFiles).To(ContainElements(tc.expectWriteFiles), + "Required files in /capi directory are missing") + }) + } +} + func TestNewJoinControlPlaneInvalidVersionError(t *testing.T) { g := NewWithT(t) diff --git a/pkg/cloudinit/embed.go b/pkg/cloudinit/embed.go index 1d00a962..dafaa308 100644 --- a/pkg/cloudinit/embed.go +++ b/pkg/cloudinit/embed.go @@ -20,6 +20,7 @@ var ( scriptBootstrap script = "bootstrap.sh" scriptLoadImages script = "load-images.sh" scriptConfigureAuthToken script = "configure-auth-token.sh" // #nosec G101 + scriptConfigureProxy script = "configure-proxy.sh" scriptConfigureNodeToken script = "configure-node-token.sh" // #nosec G101 scriptJoinCluster script = "join-cluster.sh" scriptWaitAPIServerReady script = "wait-apiserver-ready.sh" @@ -44,6 +45,7 @@ var ( scriptBootstrap: mustEmbed(scriptBootstrap), scriptLoadImages: mustEmbed(scriptLoadImages), scriptConfigureAuthToken: mustEmbed(scriptConfigureAuthToken), + scriptConfigureProxy: mustEmbed(scriptConfigureProxy), scriptConfigureNodeToken: mustEmbed(scriptConfigureNodeToken), scriptJoinCluster: mustEmbed(scriptJoinCluster), scriptWaitAPIServerReady: mustEmbed(scriptWaitAPIServerReady), diff --git a/pkg/cloudinit/scripts/configure-proxy.sh b/pkg/cloudinit/scripts/configure-proxy.sh new file mode 100644 index 00000000..f473e2a8 --- /dev/null +++ b/pkg/cloudinit/scripts/configure-proxy.sh @@ -0,0 +1,27 @@ +#!/bin/bash -e + +# Assumptions: +# - runs before install k8s + +HTTP_PROXY_FILE="/tmp/capi/etc/http-proxy" +HTTPS_PROXY_FILE="/tmp/capi/etc/https-proxy" +NO_PROXY_FILE="/tmp/capi/etc/no-proxy" +ENVIRONMENT_FILE="/tmp/etc/environment" + +if [ -f ${HTTP_PROXY_FILE} ]; then + HTTP_PROXY=$(cat ${HTTP_PROXY_FILE}) + echo "http_proxy=${HTTP_PROXY}" >> "${ENVIRONMENT_FILE}" + echo "HTTP_PROXY=${HTTP_PROXY}" >> "${ENVIRONMENT_FILE}" +fi + +if [ -f ${HTTPS_PROXY_FILE} ]; then + HTTPS_PROXY=$(cat ${HTTPS_PROXY_FILE}) + echo "https_proxy=${HTTPS_PROXY}" >> "${ENVIRONMENT_FILE}" + echo "HTTPS_PROXY=${HTTPS_PROXY}" >> "${ENVIRONMENT_FILE}" +fi + +if [ -f ${NO_PROXY_FILE} ]; then + NO_PROXY=$(cat ${NO_PROXY_FILE}) + echo "no_proxy=${NO_PROXY}" >> "${ENVIRONMENT_FILE}" + echo "NO_PROXY=${NO_PROXY}" >> "${ENVIRONMENT_FILE}" +fi diff --git a/pkg/cloudinit/worker_join_test.go b/pkg/cloudinit/worker_join_test.go index addadc38..3fbd06f6 100644 --- a/pkg/cloudinit/worker_join_test.go +++ b/pkg/cloudinit/worker_join_test.go @@ -6,6 +6,7 @@ import ( . "github.com/onsi/gomega" "github.com/onsi/gomega/gstruct" + "github.com/onsi/gomega/types" "github.com/canonical/cluster-api-k8s/pkg/cloudinit" ) @@ -66,6 +67,7 @@ func TestNewJoinWorker(t *testing.T) { HaveField("Path", "/capi/scripts/wait-apiserver-ready.sh"), HaveField("Path", "/capi/scripts/deploy-manifests.sh"), HaveField("Path", "/capi/scripts/configure-auth-token.sh"), + HaveField("Path", "/capi/scripts/configure-proxy.sh"), HaveField("Path", "/capi/scripts/configure-node-token.sh"), HaveField("Path", "/capi/scripts/create-sentinel-bootstrap.sh"), HaveField("Path", "/capi/scripts/configure-snapstore-proxy.sh"), @@ -82,6 +84,74 @@ func TestNewJoinWorker(t *testing.T) { ), "Some /capi/scripts files are missing") } +func TestNewJoinWorkerWithProxySettings(t *testing.T) { + g := NewWithT(t) + for _, tc := range []struct { + name string + baseUserData cloudinit.BaseUserData + expectRunCommand bool + expectWriteFiles []types.GomegaMatcher + }{ + { + name: "AllProxyFieldsSet", + baseUserData: cloudinit.BaseUserData{ + KubernetesVersion: "v1.30.0", + HTTPProxy: "http://proxy.internal", + HTTPSProxy: "https://proxy.internal", + NoProxy: "10.0.0.0/8,10.152.183.1,192.168.0.0/16", + MicroclusterAddress: "10.0.0.0/8", + }, + expectRunCommand: true, + expectWriteFiles: []types.GomegaMatcher{ + HaveField("Path", "/capi/scripts/configure-proxy.sh"), + HaveField("Path", "/capi/etc/http-proxy"), + HaveField("Path", "/capi/etc/https-proxy"), + HaveField("Path", "/capi/etc/no-proxy"), + }, + }, + { + name: "HTTPSProxyOnly", + baseUserData: cloudinit.BaseUserData{ + KubernetesVersion: "v1.30.0", + HTTPSProxy: "https://proxy.internal", + NoProxy: "10.0.0.0/8,10.152.183.1,192.168.0.0/16", + MicroclusterAddress: "10.0.0.0/8", + }, + expectRunCommand: true, + expectWriteFiles: []types.GomegaMatcher{ + HaveField("Path", "/capi/scripts/configure-proxy.sh"), + HaveField("Path", "/capi/etc/https-proxy"), + }, + }, + { + name: "NoProxyFields", + baseUserData: cloudinit.BaseUserData{ + KubernetesVersion: "v1.30.0", + MicroclusterAddress: "10.0.0.0/8", + }, + expectRunCommand: false, + expectWriteFiles: []types.GomegaMatcher{ + HaveField("Path", "/capi/scripts/configure-proxy.sh"), + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + config, err := cloudinit.NewJoinWorker(cloudinit.JoinWorkerInput{BaseUserData: tc.baseUserData}) + + g.Expect(err).ToNot(HaveOccurred()) + // Verify proxy run command. + if tc.expectRunCommand { + g.Expect(config.RunCommands).To(ContainElement("/capi/scripts/configure-proxy.sh")) + } else { + g.Expect(config.RunCommands).NotTo(ContainElement("/capi/scripts/configure-proxy.sh")) + } + // Verify proxy files present. + g.Expect(config.WriteFiles).To(ContainElements(tc.expectWriteFiles), + "Required files in /capi directory are missing") + }) + } +} + func TestNewJoinWorkerInvalidVersionError(t *testing.T) { g := NewWithT(t)