diff --git a/images/capi/.gitignore b/images/capi/.gitignore index d3d946cd88..b744523822 100644 --- a/images/capi/.gitignore +++ b/images/capi/.gitignore @@ -7,6 +7,7 @@ /.local/bin/ manifest.json **.DS_Store +/packer/ssh-password.auto.pkrvars.json # Goss test droppings debug-goss-spec.yaml diff --git a/images/capi/Makefile b/images/capi/Makefile index 553103e4f8..d7b53e40f6 100644 --- a/images/capi/Makefile +++ b/images/capi/Makefile @@ -676,13 +676,17 @@ $(NUTANIX_BUILD_TARGETS): deps-nutanix set-ssh-password $(if $(findstring windows,$@),,$(eval NUTANIX_VAR_FILE:=$(abspath packer/nutanix/$(subst build-nutanix-,,$@).json))) $(if $(findstring windows,$@),,jq '.user_data = "$(NUTANIX_USERDATA)"' $(NUTANIX_VAR_FILE) > $(NUTANIX_VAR_FILE).templated && mv $(NUTANIX_VAR_FILE).templated $(NUTANIX_VAR_FILE)) # This uses a packer file builder to input unattend variables into a JSON file to be consumed by the python script before running the nutanix provisioner - $(if $(findstring windows,$@),$(PACKER) build $(PACKER_WINDOWS_NODE_FLAGS) -var-file="packer/nutanix/nutanix.json" -var-file="$(abspath packer/nutanix/$(subst build-nutanix-,,$@).json)" -only=file $(ABSOLUTE_PACKER_VAR_FILES) packer/nutanix/packer$(if $(findstring windows,$@),-windows,).json,) + $(if $(findstring windows,$@),$(PACKER) build $(PACKER_WINDOWS_NODE_FLAGS) -var-file="packer/nutanix/nutanix.json" -var-file="$(abspath packer/nutanix/$(subst build-nutanix-,,$@).json)" -only=file $(ABSOLUTE_PACKER_VAR_FILES) packer/nutanix/packer-windows.json,) $(if $(findstring windows,$@),hack/windows-unattend.py --unattend-file='./packer/nutanix/windows/$(subst build-nutanix-,,$@)/autounattend.xml',) - $(PACKER) build $(if $(findstring windows,$@),$(PACKER_WINDOWS_NODE_FLAGS),$(PACKER_NODE_FLAGS)) -var-file="packer/nutanix/nutanix.json" -var-file="$(abspath packer/nutanix/$(subst build-nutanix-,,$@).json)" $(ABSOLUTE_PACKER_VAR_FILES) packer/nutanix/packer$(if $(findstring windows,$@),-windows,).json + $(if $(findstring windows,$@),\ + $(PACKER) build $(PACKER_WINDOWS_NODE_FLAGS) -var-file="packer/nutanix/nutanix.json" -var-file="$(abspath packer/nutanix/$(subst build-nutanix-,,$@).json)" $(ABSOLUTE_PACKER_VAR_FILES) packer/nutanix/packer-windows.json,\ + $(PACKER) build $(PACKER_NODE_FLAGS) -var-file="packer/nutanix/nutanix.json" -var-file="$(abspath packer/nutanix/$(subst build-nutanix-,,$@).json)" -var-file="$(abspath packer/ssh-password.auto.pkrvars.json)" $(ABSOLUTE_PACKER_VAR_FILES) packer/nutanix) .PHONY: $(NUTANIX_VALIDATE_TARGETS) $(NUTANIX_VALIDATE_TARGETS): deps-nutanix set-ssh-password - $(PACKER) validate $(if $(findstring windows,$@),$(PACKER_WINDOWS_NODE_FLAGS),$(PACKER_NODE_FLAGS)) -var-file="packer/nutanix/nutanix.json" -var-file="$(abspath packer/nutanix/$(subst validate-nutanix-,,$@).json)" $(ABSOLUTE_PACKER_VAR_FILES) packer/nutanix/packer$(if $(findstring windows,$@),-windows,).json + $(if $(findstring windows,$@),\ + $(PACKER) validate $(PACKER_WINDOWS_NODE_FLAGS) -var-file="packer/nutanix/nutanix.json" -var-file="$(abspath packer/nutanix/$(subst validate-nutanix-,,$@).json)" $(ABSOLUTE_PACKER_VAR_FILES) packer/nutanix/packer-windows.json,\ + $(PACKER) validate $(PACKER_NODE_FLAGS) -var-file="packer/nutanix/nutanix.json" -var-file="$(abspath packer/nutanix/$(subst validate-nutanix-,,$@).json)" -var-file="$(abspath packer/ssh-password.auto.pkrvars.json)" $(ABSOLUTE_PACKER_VAR_FILES) packer/nutanix) .PHONY: $(HCLOUD_BUILD_TARGETS) $(HCLOUD_BUILD_TARGETS): deps-hcloud diff --git a/images/capi/hack/set-ssh-password.sh b/images/capi/hack/set-ssh-password.sh index 1005289824..f329934c33 100755 --- a/images/capi/hack/set-ssh-password.sh +++ b/images/capi/hack/set-ssh-password.sh @@ -55,3 +55,13 @@ for file in $(find $PACKER_DIR -type f -name "*.tmpl"); do fi sed -e "s|\$SSH_PASSWORD|$SSH_PASSWORD|g" -e "s|\$ENCRYPTED_SSH_PASSWORD|$ENCRYPTED_SSH_PASSWORD|g" $file | tee ${file%.*} done + +# HCL2 templates can't pick up $SSH_PASSWORD/$ENCRYPTED_SSH_PASSWORD via the +# .tmpl sed substitution above (that's plain text replacement, not something +# Packer's HCL2 engine does), and the env vars exported above don't survive +# into the separate shell that runs the actual `packer build`/`validate` +# recipe line. Write them to a var-file instead, which does survive (Make +# prerequisites and recipes only share the filesystem, not environment). +jq -n --arg ssh_password "$SSH_PASSWORD" --arg encrypted_ssh_password "$ENCRYPTED_SSH_PASSWORD" \ + '{ssh_password: $ssh_password, encrypted_ssh_password: $encrypted_ssh_password}' \ + > "$PACKER_DIR/ssh-password.auto.pkrvars.json" diff --git a/images/capi/packer/nutanix/build.pkr.hcl b/images/capi/packer/nutanix/build.pkr.hcl new file mode 100644 index 0000000000..984799c938 --- /dev/null +++ b/images/capi/packer/nutanix/build.pkr.hcl @@ -0,0 +1,68 @@ +build { + sources = ["source.nutanix.node"] + + post-processor "shell-local" { + environment_vars = [ + "CUSTOM_POST_PROCESSOR=${var.custom_post_processor}" + ] + inline = [ + "if [ \"$CUSTOM_POST_PROCESSOR\" != \"true\" ]; then exit 0; fi", + var.custom_post_processor_command, + ] + } + + provisioner "shell" { + environment_vars = [ + "BUILD_NAME=${var.build_name}", + "PYPY_HTTP_SOURCE=${var.pypy_http_source}", + ] + execute_command = "BUILD_NAME=${var.build_name}; if [[ \"$${BUILD_NAME}\" == *\"flatcar\"* ]]; then sudo {{.Vars}} -S -E bash '{{.Path}}'; fi" + script = "./packer/files/flatcar/scripts/bootstrap-flatcar.sh" + } + + provisioner "ansible" { + ansible_env_vars = [ + "ANSIBLE_SSH_ARGS='${var.existing_ansible_ssh_args} -o IdentitiesOnly=yes'" + ] + extra_arguments = [ + "--extra-vars", local.ansible_common_vars, + "--extra-vars", var.ansible_extra_vars, + "--extra-vars", var.ansible_user_vars, + "--scp-extra-args=${local.ansible_scp_extra_args}", + ] + playbook_file = "./ansible/node.yml" + user = "builder" + } + + provisioner "goss" { + arch = var.goss_arch + format = var.goss_format + format_options = var.goss_format_options + goss_file = var.goss_entry_file + inspect = var.goss_inspect_mode + tests = [var.goss_tests_dir] + url = var.goss_url + use_sudo = true + vars_file = var.goss_vars_file + vars_inline = { + ARCH = "amd64" + OS = lower(var.distro_name) + OS_VERSION = lower(var.distribution_version) + PROVIDER = "nutanix" + containerd_enable_limit_no_file = var.containerd_enable_limit_no_file + containerd_gvisor_runtime = var.containerd_gvisor_runtime + containerd_gvisor_version = var.containerd_gvisor_version + containerd_image_pull_progress_timeout = (var.containerd_image_pull_progress_timeout != null ? var.containerd_image_pull_progress_timeout : "") + containerd_version = var.containerd_version + kubernetes_cni_deb_version = (var.kubernetes_cni_deb_version != null ? var.kubernetes_cni_deb_version : "") + kubernetes_cni_rpm_version = split("-", var.kubernetes_cni_rpm_version != null ? var.kubernetes_cni_rpm_version : "")[0] + kubernetes_cni_source_type = var.kubernetes_cni_source_type + kubernetes_cni_version = replace(var.kubernetes_cni_semver, "v", "") + kubernetes_deb_version = var.kubernetes_deb_version + kubernetes_rpm_version = split("-", var.kubernetes_rpm_version)[0] + kubernetes_source_type = var.kubernetes_source_type + kubernetes_version = replace(var.kubernetes_semver, "v", "") + } + version = var.goss_version + } +} diff --git a/images/capi/packer/nutanix/flatcar.json b/images/capi/packer/nutanix/flatcar.json index 2a6075a0c7..a44a144190 100644 --- a/images/capi/packer/nutanix/flatcar.json +++ b/images/capi/packer/nutanix/flatcar.json @@ -1,10 +1,8 @@ { "ansible_extra_vars": "ansible_python_interpreter=/opt/bin/python3", - "build_name": "flatcar-{{env `FLATCAR_CHANNEL`}}-{{env `FLATCAR_VERSION`}}", - "channel_name": "{{env `FLATCAR_CHANNEL`}}", + "build_name": "flatcar", "distribution": "flatcar", "distribution_release": "Core", - "distribution_version": "{{env `FLATCAR_CHANNEL`}}", "distro_name": "flatcar", "guest_os_type": "Linux", "image_url": "flatcar_production_openstack_image.img", diff --git a/images/capi/packer/nutanix/locals.pkr.hcl b/images/capi/packer/nutanix/locals.pkr.hcl new file mode 100644 index 0000000000..1f7b04d4ee --- /dev/null +++ b/images/capi/packer/nutanix/locals.pkr.hcl @@ -0,0 +1,149 @@ +// Values that were derived from other user variables via nested `{{user ...}}` +// interpolation inside packer/config/*.json var-files (or in this template's +// own JSON "variables" block). HCL2 variable defaults must be constant +// expressions, so these become locals computed from the variables declared +// in variables.pkr.hcl instead. + +locals { + # packer/config/kubernetes.json + kubernetes_deb_gpg_key = "https://pkgs.k8s.io/core:/stable:/${var.kubernetes_series}/deb/Release.key" + kubernetes_deb_repo = "https://pkgs.k8s.io/core:/stable:/${var.kubernetes_series}/deb/" + kubernetes_rpm_gpg_key = "https://pkgs.k8s.io/core:/stable:/${var.kubernetes_series}/rpm/repodata/repomd.xml.key" + kubernetes_rpm_repo = "https://pkgs.k8s.io/core:/stable:/${var.kubernetes_series}/rpm/" + + # packer/config/wasm-shims.json + containerd_wasm_shims_url = "https://github.com/deislabs/containerd-wasm-shims/releases/download/${var.containerd_wasm_shims_version}/containerd-wasm-shims---linux-x86_64.tar.gz" + + # JSON-encoded blobs, kept as readable HCL maps here rather than escaped + # string literals (see the override note on the two variables in + # variables.pkr.hcl). + containerd_wasm_shims_runtime_versions = var.containerd_wasm_shims_runtime_versions != "" ? var.containerd_wasm_shims_runtime_versions : jsonencode({ + lunatic = "v1" + slight = "v1" + spin = "v2" + wws = "v1" + }) + containerd_wasm_shims_sha256 = var.containerd_wasm_shims_sha256 != "" ? var.containerd_wasm_shims_sha256 : jsonencode({ + lunatic = "7054bc882db755ce5f3ded46d114bfd4e0a318e437fa18a2601295d20b616b32" + slight = "a6ea87d965037933a7d9edb5e20cfc175265c8e1ca92a16535f1f3c3f376f5b0" + spin = "dcffedb8e4d2f585a851b3de489fa1e8a0054ec0ad72cf111c623623919245d0" + wws = "e917f90692d798d80873aa0f37990c7d652f2846129d64fecbfd41ffa77799b8" + }) + + # containerd.service is now rendered locally rather than fetched at build + # time, matching every other cloud's packer.json (#2102). + containerd_service_url = "" + + # packer/config/ansible-args.json: legacy value is the literal string + # "{{env `ANSIBLE_SCP_EXTRA_ARGS`}}". Declaring this as a `variable` of the + # same name would still get clobbered by that raw, never-interpolated string + # via -var-file=ansible-args.json, so it's a local backed by the + # differently-named ansible_scp_extra_args_env variable instead. + ansible_scp_extra_args = var.ansible_scp_extra_args_env + + # image_name: kept overridable under its ORIGINAL name (see variables.pkr.hcl) + # so any existing downstream var-file setting it keeps working, falling back + # to the same "-kube-" default as before. + image_name = var.image_name != "" ? var.image_name : "${var.build_name}-kube-${var.kubernetes_semver}" + + # nutanix_port/nutanix_insecure are native number/bool variables (see + # variables.pkr.hcl), so a JSON var-file/-var flag setting e.g. + # "9440"/"true" auto-converts fine. `env()` can't produce a non-empty + # number/bool default directly though, so fall back to the legacy + # NUTANIX_PORT/NUTANIX_INSECURE env vars (via the *_env string variables) + # whenever the typed variable itself is left unset. + nutanix_port = var.nutanix_port != null ? var.nutanix_port : (var.nutanix_port_env != "" ? var.nutanix_port_env : null) + nutanix_insecure = var.nutanix_insecure != null ? var.nutanix_insecure : (var.nutanix_insecure_env != "" ? var.nutanix_insecure_env : null) + + # packer/config/ansible-args.json: single space-separated string of + # extra-vars passed to the ansible provisioner, reproduced verbatim from the + # legacy var-file. A few of these (containerd_additional_settings, + # containerd_image_pull_progress_timeout, kubernetes_cni_deb_version, + # kubernetes_cni_rpm_version) are still shipped as JSON `null` in + # packer/config/cni.json / containerd.json, and that null reaches this local + # via -var-file, overriding the ""-default declared in variables.pkr.hcl at + # RUNTIME (not just at declaration time). HCL2 can't interpolate a null into + # a string template, so those four get an explicit `!= null ? x : ""` guard. + ansible_common_vars = join(" ", [ + "containerd_gvisor_runtime=${var.containerd_gvisor_runtime}", + "containerd_gvisor_version=${var.containerd_gvisor_version}", + "containerd_sha256=${var.containerd_sha256}", + "pause_image=${var.pause_image}", + "containerd_additional_settings=${(var.containerd_additional_settings != null ? var.containerd_additional_settings : "")}", + "containerd_cri_socket=${var.containerd_cri_socket}", + "containerd_version=${var.containerd_version}", + "containerd_image_pull_progress_timeout=${(var.containerd_image_pull_progress_timeout != null ? var.containerd_image_pull_progress_timeout : "")}", + "containerd_enable_limit_no_file=${var.containerd_enable_limit_no_file}", + "containerd_wasm_shims_url=${local.containerd_wasm_shims_url}", + "containerd_wasm_shims_version=${var.containerd_wasm_shims_version}", + "containerd_wasm_shims_sha256=${local.containerd_wasm_shims_sha256}", + "containerd_wasm_shims_runtimes=\"${var.containerd_wasm_shims_runtimes}\"", + "containerd_wasm_shims_runtime_versions=\"${local.containerd_wasm_shims_runtime_versions}\"", + "crictl_version=${var.crictl_version}", + "custom_role_names=\"${var.custom_role_names}\"", + "firstboot_custom_roles_pre=\"${var.firstboot_custom_roles_pre}\"", + "firstboot_custom_roles_post=\"${var.firstboot_custom_roles_post}\"", + "node_custom_roles_pre=\"${var.node_custom_roles_pre}\"", + "node_custom_roles_post=\"${var.node_custom_roles_post}\"", + "node_custom_roles_post_sysprep=\"${var.node_custom_roles_post_sysprep}\"", + "node_ansible_tmpdir=\"${var.node_ansible_tmpdir}\"", + "disable_public_repos=${var.disable_public_repos}", + "extra_debs=\"${var.extra_debs}\"", + "extra_kernel_boot_params=\"${var.extra_kernel_boot_params}\"", + "extra_repos=\"${var.extra_repos}\"", + "extra_rpms=\"${var.extra_rpms}\"", + "http_proxy=${var.http_proxy}", + "https_proxy=${var.https_proxy}", + "kubeadm_template=${var.kubeadm_template}", + "kubernetes_apiserver_port=${var.kubernetes_apiserver_port}", + "kubernetes_cni_http_source=${var.kubernetes_cni_http_source}", + "kubernetes_http_source=${var.kubernetes_http_source}", + "kubernetes_container_registry=${var.kubernetes_container_registry}", + "kubernetes_rpm_repo=${local.kubernetes_rpm_repo}", + "kubernetes_rpm_gpg_key=${local.kubernetes_rpm_gpg_key}", + "kubernetes_rpm_gpg_check=${var.kubernetes_rpm_gpg_check}", + "kubernetes_deb_repo=${local.kubernetes_deb_repo}", + "kubernetes_deb_gpg_key=${local.kubernetes_deb_gpg_key}", + "kubernetes_cni_deb_version=${(var.kubernetes_cni_deb_version != null ? var.kubernetes_cni_deb_version : "")}", + "kubernetes_cni_rpm_version=${(var.kubernetes_cni_rpm_version != null ? var.kubernetes_cni_rpm_version : "")}", + "kubernetes_cni_semver=${var.kubernetes_cni_semver}", + "kubernetes_cni_source_type=${var.kubernetes_cni_source_type}", + "kubernetes_semver=${var.kubernetes_semver}", + "kubernetes_source_type=${var.kubernetes_source_type}", + "kubernetes_load_additional_imgs=${var.kubernetes_load_additional_imgs}", + "kubernetes_deb_version=${var.kubernetes_deb_version}", + "kubernetes_rpm_version=${var.kubernetes_rpm_version}", + "no_proxy=${var.no_proxy}", + "pip_conf_file=${var.pip_conf_file}", + "python_path=${var.python_path}", + "redhat_epel_rpm=${var.redhat_epel_rpm}", + "epel_rpm_gpg_key=${var.epel_rpm_gpg_key}", + "reenable_public_repos=${var.reenable_public_repos}", + "remove_extra_repos=${var.remove_extra_repos}", + "systemd_prefix=${var.systemd_prefix}", + "sysusr_prefix=${var.sysusr_prefix}", + "sysusrlocal_prefix=${var.sysusrlocal_prefix}", + "load_additional_components=${var.load_additional_components}", + "additional_registry_images=${var.additional_registry_images}", + "additional_registry_images_list=${var.additional_registry_images_list}", + "ecr_credential_provider=${var.ecr_credential_provider}", + "additional_url_images=${var.additional_url_images}", + "additional_url_images_list=${var.additional_url_images_list}", + "additional_executables=${var.additional_executables}", + "additional_executables_list=${var.additional_executables_list}", + "additional_executables_destination_path=${var.additional_executables_destination_path}", + "additional_s3=${var.additional_s3}", + "build_target=${var.build_target}", + "amazon_ssm_agent_rpm=${var.amazon_ssm_agent_rpm}", + "enable_containerd_audit=${var.enable_containerd_audit}", + "kubernetes_enable_automatic_resource_sizing=${var.kubernetes_enable_automatic_resource_sizing}", + "debug_tools=${var.debug_tools}", + "ubuntu_repo=${var.ubuntu_repo}", + "ubuntu_security_repo=${var.ubuntu_security_repo}", + "gpu_block_nouveau_loading=${var.block_nouveau_loading}", + "runc_version=${var.runc_version}", + "containerd_service_url=${local.containerd_service_url}", + "netplan_removal_excludes=\"${var.netplan_removal_excludes}\"", + "image_builder_version=${var.ib_version}", + ]) +} diff --git a/images/capi/packer/nutanix/packer.json.tmpl b/images/capi/packer/nutanix/packer.json.tmpl deleted file mode 100644 index 75d7d5ffe8..0000000000 --- a/images/capi/packer/nutanix/packer.json.tmpl +++ /dev/null @@ -1,170 +0,0 @@ -{ - "builders": [ - { - "boot_type": "{{user `boot_type`}}", - "cluster_name": "{{user `nutanix_cluster_name`}}", - "cpu": "{{user `cpus`}}", - "force_deregister": "{{user `force_deregister`}}", - "image_delete": "{{user `image_delete`}}", - "image_description": "kube image-builder packer", - "image_export": "{{user `image_export`}}", - "image_name": "{{user `image_name`}}", - "memory_mb": "{{user `memory`}}", - "nutanix_endpoint": "{{user `nutanix_endpoint`}}", - "nutanix_insecure": "{{user `nutanix_insecure`}}", - "nutanix_password": "{{user `nutanix_password`}}", - "nutanix_port": "{{user `nutanix_port`}}", - "nutanix_username": "{{user `nutanix_username`}}", - "os_type": "{{user `guest_os_type`}}", - "shutdown_command": "echo '{{user `ssh_password`}}' | sudo -S -E sh -c 'userdel -f -r {{user `ssh_username`}} && rm -f /etc/sudoers.d/{{user `ssh_username` }} && rm -f /etc/sudoers.d/90-cloud-init-users && {{user `shutdown_command`}}'", - "ssh_handshake_attempts": "100", - "ssh_password": "{{user `ssh_password`}}", - "ssh_timeout": "20m", - "ssh_username": "{{user `ssh_username`}}", - "type": "nutanix", - "user_data": "{{user `user_data`}}", - "vm_disks": { - "disk_size_gb": "{{user `disk_size_gb`}}", - "image_type": "DISK_IMAGE", - "source_image_delete": "{{user `source_image_delete`}}", - "source_image_force": "{{user `source_image_force`}}", - "source_image_uri": "{{user `image_url`}}" - }, - "vm_force_delete": "{{user `vm_force_delete`}}", - "vm_name": "{{user `build_name`}}-kube-{{user `kubernetes_semver`}}", - "vm_nics": { - "subnet_name": "{{user `nutanix_subnet_name`}}" - } - } - ], - "post-processors": [ - { - "environment_vars": [ - "CUSTOM_POST_PROCESSOR={{user `custom_post_processor`}}" - ], - "inline": [ - "if [ \"$CUSTOM_POST_PROCESSOR\" != \"true\" ]; then exit 0; fi", - "{{user `custom_post_processor_command`}}" - ], - "name": "custom-post-processor", - "type": "shell-local" - } - ], - "provisioners": [ - { - "environment_vars": [ - "BUILD_NAME={{user `build_name`}}", - "PYPY_HTTP_SOURCE={{user `pypy_http_source`}}" - ], - "execute_command": "BUILD_NAME={{user `build_name`}}; if [[ \"${BUILD_NAME}\" == *\"flatcar\"* ]]; then sudo {{.Vars}} -S -E bash '{{.Path}}'; fi", - "script": "./packer/files/flatcar/scripts/bootstrap-flatcar.sh", - "type": "shell" - }, - { - "ansible_env_vars": [ - "ANSIBLE_SSH_ARGS='{{user `existing_ansible_ssh_args`}} -o IdentitiesOnly=yes'" - ], - "extra_arguments": [ - "--extra-vars", - "{{user `ansible_common_vars`}}", - "--extra-vars", - "{{user `ansible_extra_vars`}}", - "--extra-vars", - "{{user `ansible_user_vars`}}", - "--scp-extra-args={{user `scp_extra_vars`}}" - ], - "playbook_file": "./ansible/node.yml", - "type": "ansible", - "user": "builder" - }, - { - "arch": "{{user `goss_arch`}}", - "format": "{{user `goss_format`}}", - "format_options": "{{user `goss_format_options`}}", - "goss_file": "{{user `goss_entry_file`}}", - "inspect": "{{user `goss_inspect_mode`}}", - "tests": [ - "{{user `goss_tests_dir`}}" - ], - "type": "goss", - "url": "{{user `goss_url`}}", - "use_sudo": true, - "vars_file": "{{user `goss_vars_file`}}", - "vars_inline": { - "ARCH": "amd64", - "OS": "{{user `distro_name` | lower}}", - "OS_VERSION": "{{user `distribution_version` | lower}}", - "PROVIDER": "nutanix", - "containerd_enable_limit_no_file": "{{user `containerd_enable_limit_no_file`}}", - "containerd_gvisor_runtime": "{{user `containerd_gvisor_runtime`}}", - "containerd_gvisor_version": "{{user `containerd_gvisor_version`}}", - "containerd_image_pull_progress_timeout": "{{user `containerd_image_pull_progress_timeout`}}", - "containerd_version": "{{user `containerd_version`}}", - "kubernetes_cni_deb_version": "{{ user `kubernetes_cni_deb_version` }}", - "kubernetes_cni_rpm_version": "{{ split (user `kubernetes_cni_rpm_version`) \"-\" 0 }}", - "kubernetes_cni_source_type": "{{user `kubernetes_cni_source_type`}}", - "kubernetes_cni_version": "{{user `kubernetes_cni_semver` | replace \"v\" \"\" 1}}", - "kubernetes_deb_version": "{{ user `kubernetes_deb_version` }}", - "kubernetes_rpm_version": "{{ split (user `kubernetes_rpm_version`) \"-\" 0 }}", - "kubernetes_source_type": "{{user `kubernetes_source_type`}}", - "kubernetes_version": "{{user `kubernetes_semver` | replace \"v\" \"\" 1}}" - }, - "version": "{{user `goss_version`}}" - } - ], - "variables": { - "ansible_common_vars": "", - "ansible_extra_vars": "", - "ansible_user_vars": "", - "build_timestamp": "{{timestamp}}", - "containerd_enable_limit_no_file": "false", - "containerd_gvisor_runtime": "false", - "containerd_gvisor_version": "latest", - "containerd_version": null, - "containerd_image_pull_progress_timeout": null, - "containerd_service_url": "", - "runc_version": null, - "cpus": "1", - "crictl_version": null, - "disk_size_gb": "10", - "existing_ansible_ssh_args": "{{env `ANSIBLE_SSH_ARGS`}}", - "ib_version": "{{env `IB_VERSION`}}", - "force_deregister": "true", - "image_delete": "false", - "image_export": "false", - "image_name": "{{user `build_name`}}-kube-{{user `kubernetes_semver`}}", - "kubernetes_cni_deb_version": null, - "kubernetes_cni_http_source": null, - "kubernetes_cni_semver": null, - "kubernetes_cni_source_type": null, - "kubernetes_container_registry": null, - "kubernetes_deb_gpg_key": null, - "kubernetes_deb_repo": null, - "kubernetes_deb_version": null, - "kubernetes_http_source": null, - "kubernetes_load_additional_imgs": null, - "kubernetes_rpm_gpg_check": null, - "kubernetes_rpm_gpg_key": null, - "kubernetes_rpm_repo": null, - "kubernetes_rpm_version": null, - "kubernetes_semver": null, - "kubernetes_series": null, - "kubernetes_source_type": null, - "machine_id_mode": "444", - "memory": "2048", - "nutanix_cluster_name": "{{env `NUTANIX_CLUSTER_NAME`}}", - "nutanix_endpoint": "{{env `NUTANIX_ENDPOINT`}}", - "nutanix_insecure": "{{env `NUTANIX_INSECURE`}}", - "nutanix_password": "{{env `NUTANIX_PASSWORD`}}", - "nutanix_port": "{{env `NUTANIX_PORT`}}", - "nutanix_subnet_name": "{{env `NUTANIX_SUBNET_NAME`}}", - "nutanix_username": "{{env `NUTANIX_USERNAME`}}", - "python_path": "", - "scp_extra_vars": "{{user `ansible_scp_extra_args`}}", - "source_image_delete": "false", - "source_image_force": "false", - "ssh_password": "$SSH_PASSWORD", - "ssh_username": "builder", - "vm_force_delete": "true" - } -} diff --git a/images/capi/packer/nutanix/sources.pkr.hcl b/images/capi/packer/nutanix/sources.pkr.hcl new file mode 100644 index 0000000000..cd7377e796 --- /dev/null +++ b/images/capi/packer/nutanix/sources.pkr.hcl @@ -0,0 +1,45 @@ +source "nutanix" "node" { + boot_type = var.boot_type + cluster_name = var.nutanix_cluster_name + cpu = var.cpus + force_deregister = var.force_deregister + image_delete = var.image_delete + image_description = "kube image-builder packer" + image_export = var.image_export + image_name = local.image_name + memory_mb = var.memory + nutanix_endpoint = var.nutanix_endpoint + nutanix_insecure = local.nutanix_insecure + nutanix_password = var.nutanix_password + nutanix_port = local.nutanix_port + nutanix_username = var.nutanix_username + os_type = var.guest_os_type + shutdown_command = "echo '${var.ssh_password}' | sudo -S -E sh -c 'userdel -f -r ${var.ssh_username} && rm -f /etc/sudoers.d/${var.ssh_username} && rm -f /etc/sudoers.d/90-cloud-init-users && ${var.shutdown_command}'" + ssh_handshake_attempts = 100 + ssh_password = var.ssh_password + ssh_timeout = "20m" + ssh_username = var.ssh_username + user_data = var.user_data + + vm_disks { + disk_size_gb = var.disk_size_gb + image_type = "DISK_IMAGE" + source_image_delete = var.source_image_delete + source_image_force = var.source_image_force + source_image_uri = var.image_url + } + + vm_force_delete = var.vm_force_delete + # NOTE: intentional behavior change from the legacy template, where + # vm_name and image_name were two independently-computed fields that just + # happened to share the same default formula -- overriding image_name (e.g. + # for a custom naming convention) left vm_name unaffected. Here they're + # coupled, so an image_name override also renames the build VM -- almost + # certainly what you want in practice (a mismatched VM/image name is + # confusing), but flagging it since it's not byte-for-byte identical. + vm_name = local.image_name + + vm_nics { + subnet_name = var.nutanix_subnet_name + } +} diff --git a/images/capi/packer/nutanix/variables.pkr.hcl b/images/capi/packer/nutanix/variables.pkr.hcl new file mode 100644 index 0000000000..a2f8a503f9 --- /dev/null +++ b/images/capi/packer/nutanix/variables.pkr.hcl @@ -0,0 +1,632 @@ +// Variable declarations for the Nutanix Packer template. +// +// This template is still fed by the shared packer/config/*.json var-files via +// -var-file (see the Makefile), the same way every other, still-JSON, +// provider is. Those files are plain JSON key/value maps, which HCL2 accepts +// fine as a var-file -- so most of the declarations below just mirror the +// keys those shared files set. Values derived from OTHER variables (e.g. a +// Kubernetes repo URL built from `kubernetes_series`) can't be expressed as a +// variable default in HCL2 (only constant expressions/`env()` are allowed), +// so those live in locals.pkr.hcl instead. +// +// Variables referenced by a downstream var-file/CI pipeline keep their +// original legacy-JSON names on purpose (renaming them would silently break +// any existing var-file that overrides them -- this bit a downstream +// consumer during development). nutanix_port/nutanix_insecure are declared +// with their real number/bool types rather than the legacy string -- a JSON +// var-file or -var flag setting e.g. "9440"/"true" auto-converts fine, so +// there's no behavior loss for that path. `env()` can't produce a non-empty +// number/bool default directly, so the legacy NUTANIX_PORT/NUTANIX_INSECURE +// env-var fallback is wired up via the *_env string variables below and +// local.nutanix_port/local.nutanix_insecure in locals.pkr.hcl (used by +// sources.pkr.hcl instead of the raw variables). + +########################## +# Nutanix connection/API # +########################## + +variable "nutanix_endpoint" { + type = string + description = "Nutanix Prism Central endpoint (IP or hostname)." + default = env("NUTANIX_ENDPOINT") +} +variable "nutanix_username" { + type = string + description = "Nutanix Prism Central username." + default = env("NUTANIX_USERNAME") +} +variable "nutanix_password" { + type = string + description = <<-EOT + Nutanix Prism Central password. + + NOTE: if you override this via -var-file, the value must be a real, + already-resolved string -- HCL2 var-files are static data, not templates, + so a legacy-JSON-style "{{env `PASSWORD`}}" placeholder will be taken + literally (and will break authentication) instead of being resolved. + Pass the real secret directly, or use `-var nutanix_password=...`. + EOT + default = env("NUTANIX_PASSWORD") + sensitive = true +} +variable "nutanix_port" { + type = number + description = "Nutanix Prism Central API port. Leave unset (null) to fall back to the NUTANIX_PORT env var, then to the plugin's own default." + default = null +} +variable "nutanix_port_env" { + type = string + description = "Fallback for nutanix_port, sourced from the legacy NUTANIX_PORT env var; see local.nutanix_port in locals.pkr.hcl." + default = env("NUTANIX_PORT") +} +variable "nutanix_insecure" { + type = bool + description = "Whether to skip TLS certificate verification against Prism Central. Leave unset (null) to fall back to the NUTANIX_INSECURE env var, then to the plugin's own default." + default = null +} +variable "nutanix_insecure_env" { + type = string + description = "Fallback for nutanix_insecure, sourced from the legacy NUTANIX_INSECURE env var; see local.nutanix_insecure in locals.pkr.hcl." + default = env("NUTANIX_INSECURE") +} +variable "nutanix_cluster_name" { + type = string + description = "Name of the Nutanix cluster to build on." + default = env("NUTANIX_CLUSTER_NAME") +} +variable "nutanix_subnet_name" { + type = string + description = "Name of the Nutanix subnet to attach the build VM's NIC to." + default = env("NUTANIX_SUBNET_NAME") +} + +##################### +# VM sizing/lifecycle # +##################### + +variable "cpus" { + type = number + description = "Number of vCPUs for the build VM." + default = 1 +} +variable "memory" { + type = number + description = "Memory (MB) for the build VM." + default = 2048 +} +variable "disk_size_gb" { + type = number + description = "Boot disk size (GB) for the build VM." + default = 10 +} +variable "force_deregister" { + type = bool + description = "Deregister (delete) any pre-existing image with the same name before building." + default = true +} +variable "image_delete" { + type = bool + description = "Delete the resulting Nutanix image if the build fails." + default = false +} +variable "image_export" { + type = bool + description = "Export the resulting image after the build." + default = false +} +variable "source_image_delete" { + type = bool + description = "Delete the downloaded source disk image after the build VM is created." + default = false +} +variable "source_image_force" { + type = bool + description = "Force re-download of the source disk image even if a matching one already exists." + default = false +} +variable "vm_force_delete" { + type = bool + description = "Force-delete the build VM (skip graceful shutdown) during cleanup." + default = true +} +variable "ssh_username" { + type = string + description = "SSH user Packer connects as during provisioning." + default = "builder" +} + +# Generated fresh by hack/set-ssh-password.sh (via the `set-ssh-password` Make +# prerequisite) into packer/ssh-password.auto.pkrvars.json, since the env var +# exported there doesn't survive into the separate shell that runs the +# actual packer build/validate recipe line -- only the filesystem does. +variable "ssh_password" { + type = string + description = "SSH password for the build user, freshly generated per-build by hack/set-ssh-password.sh." + default = "" + sensitive = true +} + +# The resulting image/VM name. Derived from build_name + kubernetes_semver by +# default (see locals.pkr.hcl) but stays overridable via -var/-var-file for +# callers (e.g. a downstream CI pipeline) that want their own naming +# convention -- pass a real, already-resolved string, not a template. +variable "image_name" { + type = string + description = "Full name for the resulting Nutanix image/VM. Leave empty to use the default `-kube-` pattern." + default = "" +} + +############################################# +# Never set anywhere in this template today # +############################################# + +variable "boot_type" { + type = string + description = "VM boot type (e.g. `legacy`/`uefi`). Unset by any current var-file; harmless no-op if left blank." + default = "" +} +variable "custom_post_processor" { + type = string + description = "Set to `true` to run custom_post_processor_command after the build." + default = "" +} +variable "custom_post_processor_command" { + type = string + description = "Shell command to run when custom_post_processor is `true`." + default = "" +} +variable "pypy_http_source" { + type = string + description = "PyPy download source, passed through to the flatcar bootstrap script's environment. Unset by any current var-file; harmless no-op if left blank." + default = "" +} + +variable "ansible_extra_vars" { + type = string + description = "Extra --extra-vars payload appended to the ansible provisioner, on top of the common one built in locals.pkr.hcl." + default = "" +} + +########################################################################## +# Populated by the Makefile's jq step (base64-encoded per-OS cloud-init # +# user-data) via -var-file before every build/validate. # +########################################################################## + +variable "user_data" { + type = string + description = "Base64-encoded cloud-init/ignition user-data for the VM's initial boot, injected by the Makefile." + default = "" +} + +########################################################## +# Supplied via the per-OS var-file (e.g. ubuntu-2204.json) # +########################################################## + +variable "build_name" { + type = string + description = "Short name identifying the OS/build variant (e.g. `ubuntu-2204`)." +} +variable "distro_name" { + type = string + description = "Distro identifier used for goss's OS test variable and the ansible_common_vars payload (e.g. `ubuntu`, `rhel`)." +} +variable "distribution_version" { + type = string + description = "Distro version used for goss's OS_VERSION test variable (e.g. `22.04`). Not set by the ubuntu var-files (blank is fine there)." + default = "" +} +variable "guest_os_type" { + type = string + description = "Guest OS type as understood by the Nutanix builder (`Linux`)." +} +variable "image_url" { + type = string + description = "URL (or path) of the source disk image to boot from." +} +variable "shutdown_command" { + type = string + description = "Command used to shut the VM down cleanly at the end of the build." +} + +############################################################################### +# Everything below mirrors the shared packer/config/*.json var-files that get # +# stacked onto every provider's build via -var-file in the Makefile # +# ($(PACKER_NODE_FLAGS)). They're declared here (rather than shared/symlinked # +# across providers) since this migration is scoped to nutanix only for now. # +# Nothing here has a provider-specific meaning; most of it just feeds the # +# composed ansible_common_vars string built in locals.pkr.hcl. # +############################################################################### + +# --- packer/config/kubernetes.json --- +variable "crictl_version" { + type = string + default = "1.36.0" +} +variable "kubeadm_template" { + type = string + default = "etc/kubeadm.yml" +} +variable "kubernetes_apiserver_port" { + type = string + default = "6443" +} +variable "kubernetes_container_registry" { + type = string + default = "registry.k8s.io" +} +variable "kubernetes_deb_version" { + type = string + default = "1.36.1-1.1" +} +variable "kubernetes_http_source" { + type = string + default = "https://dl.k8s.io/release" +} +variable "kubernetes_load_additional_imgs" { + type = string + default = "false" +} +variable "kubernetes_rpm_gpg_check" { + type = string + default = "True" +} +variable "kubernetes_rpm_version" { + type = string + default = "1.36.1" +} +variable "kubernetes_semver" { + type = string + description = "Full Kubernetes version to install (e.g. `v1.36.1`)." + default = "v1.36.1" +} +variable "kubernetes_series" { + type = string + description = "Kubernetes minor-version series used to build the pkgs.k8s.io repo URLs (e.g. `v1.36`)." + default = "v1.36" +} +variable "kubernetes_source_type" { + type = string + default = "pkg" +} +variable "systemd_prefix" { + type = string + default = "/usr/lib/systemd" +} +variable "sysusr_prefix" { + type = string + default = "/usr" +} +variable "sysusrlocal_prefix" { + type = string + default = "/usr/local" +} + +# --- packer/config/cni.json --- +variable "kubernetes_cni_deb_version" { + type = string + default = "" +} +variable "kubernetes_cni_http_source" { + type = string + default = "https://github.com/containernetworking/plugins/releases/download" +} +variable "kubernetes_cni_rpm_version" { + type = string + default = "" +} +variable "kubernetes_cni_semver" { + type = string + default = "v1.2.0" +} +variable "kubernetes_cni_source_type" { + type = string + default = "pkg" +} + +# --- packer/config/containerd.json --- +variable "containerd_additional_settings" { + type = string + default = "" +} +variable "containerd_cri_socket" { + type = string + default = "/var/run/containerd/containerd.sock" +} +variable "containerd_enable_limit_no_file" { + type = string + default = "false" +} +variable "containerd_gvisor_runtime" { + type = string + default = "false" +} +variable "containerd_gvisor_version" { + type = string + default = "latest" +} +variable "containerd_image_pull_progress_timeout" { + type = string + default = "" +} +variable "containerd_version" { + type = string + description = "containerd version to install." + default = "2.3.2" +} +variable "runc_version" { + type = string + default = "1.4.3" +} +# Real default lives in packer/config/ppc64le/containerd.json; empty on other archs. +variable "containerd_sha256" { + type = string + default = "" +} + +# --- packer/config/wasm-shims.json --- +# The two vars below hold literal JSON blobs (that's what the ansible role on +# the other end expects). A `variable` default can only be a literal +# expression -- jsonencode() isn't allowed there -- so the readable map lives +# in locals.pkr.hcl instead, with these left as an empty-string override point +# (same pattern as image_name above) for anyone who wants to supply their own +# encoded blob via -var/-var-file. +variable "containerd_wasm_shims_runtime_versions" { + type = string + description = "JSON-encoded map of wasm shim runtime versions. Leave empty to use the default set in locals.pkr.hcl." + default = "" +} +variable "containerd_wasm_shims_runtimes" { + type = string + default = "" +} +variable "containerd_wasm_shims_sha256" { + type = string + description = "JSON-encoded map of wasm shim sha256 checksums. Leave empty to use the default set in locals.pkr.hcl." + default = "" +} +variable "containerd_wasm_shims_version" { + type = string + default = "v0.11.1" +} + +# --- packer/config/common.json --- +variable "build_target" { + type = string + default = "virt" +} +variable "debug_tools" { + type = string + default = "false" +} +variable "disable_public_repos" { + type = string + default = "false" +} +variable "extra_debs" { + type = string + default = "" +} +variable "extra_kernel_boot_params" { + type = string + default = "" +} +variable "extra_repos" { + type = string + default = "" +} +variable "extra_rpms" { + type = string + default = "" +} +variable "firstboot_custom_roles_post" { + type = string + default = "" +} +variable "firstboot_custom_roles_pre" { + type = string + default = "" +} +variable "http_proxy" { + type = string + default = "" +} +variable "https_proxy" { + type = string + default = "" +} +variable "netplan_removal_excludes" { + type = string + default = "" +} +variable "no_proxy" { + type = string + default = "" +} +variable "node_ansible_tmpdir" { + type = string + default = "" +} +variable "node_custom_roles_post" { + type = string + description = "Comma-separated list of custom ansible roles to run after the standard node role. Set by downstream var-files (e.g. a CI pipeline's own `base` role) to extend provisioning." + default = "" +} +variable "node_custom_roles_post_sysprep" { + type = string + default = "" +} +variable "node_custom_roles_pre" { + type = string + default = "" +} +variable "pause_image" { + type = string + default = "registry.k8s.io/pause:3.10.2" +} +variable "pip_conf_file" { + type = string + default = "" +} +variable "python_path" { + type = string + default = "" +} +variable "redhat_epel_rpm" { + type = string + default = "https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm" +} +variable "epel_rpm_gpg_key" { + type = string + default = "" +} +variable "reenable_public_repos" { + type = string + default = "true" +} +variable "remove_extra_repos" { + type = string + default = "false" +} +variable "ubuntu_repo" { + type = string + default = "http://us.archive.ubuntu.com/ubuntu" +} +variable "ubuntu_security_repo" { + type = string + default = "http://security.ubuntu.com/ubuntu" +} +variable "custom_role_names" { + type = string + default = "" +} +variable "block_nouveau_loading" { + type = string + default = "" +} +variable "amazon_ssm_agent_rpm" { + type = string + default = "" +} +variable "enable_containerd_audit" { + type = string + default = "" +} +variable "kubernetes_enable_automatic_resource_sizing" { + type = string + default = "" +} + +# --- packer/config/additional_components.json --- +variable "additional_executables" { + type = string + default = "false" +} +variable "additional_executables_destination_path" { + type = string + default = "" +} +variable "additional_executables_list" { + type = string + default = "" +} +variable "additional_registry_images" { + type = string + default = "false" +} +variable "additional_registry_images_list" { + type = string + default = "" +} +variable "additional_s3" { + type = string + default = "false" +} +variable "additional_url_images" { + type = string + default = "false" +} +variable "additional_url_images_list" { + type = string + default = "" +} +variable "load_additional_components" { + type = string + default = "false" +} + +# --- packer/config/ecr_credential_provider.json --- +variable "ecr_credential_provider" { + type = string + default = "false" +} + +# --- packer/config/goss-args.json --- +variable "goss_arch" { + type = string + default = "amd64" +} +variable "goss_entry_file" { + type = string + default = "goss/goss.yaml" +} +variable "goss_format" { + type = string + default = "json" +} +variable "goss_format_options" { + type = string + default = "pretty" +} +variable "goss_inspect_mode" { + type = string + default = "false" +} +variable "goss_tests_dir" { + type = string + default = "packer/goss" +} +variable "goss_url" { + type = string + default = "" +} +variable "goss_vars_file" { + type = string + default = "packer/goss/goss-vars.yaml" +} +variable "goss_version" { + type = string + default = "0.3.23" +} + +# --- packer/config/ansible-args.json (literal value only; the composed +# ansible_common_vars string and ansible_scp_extra_args live in +# locals.pkr.hcl -- see the note there for why) --- +variable "ansible_common_ssh_args" { + type = string + default = "-o IdentitiesOnly=yes" +} + +# Named differently from the legacy "ansible_scp_extra_args" var-file key so +# it can't be clobbered by that key's raw, never-interpolated-by-HCL2 value +# (packer/config/ansible-args.json sets it to the literal string +# "{{env `ANSIBLE_SCP_EXTRA_ARGS`}}"). `env()` is only valid in a variable +# default, not in a locals block, hence this indirection. +variable "ansible_scp_extra_args_env" { + type = string + default = env("ANSIBLE_SCP_EXTRA_ARGS") +} + +# --- boilerplate duplicated verbatim in every legacy template's own +# "variables" block (env-sourced, identical everywhere) --- +variable "existing_ansible_ssh_args" { + type = string + default = env("ANSIBLE_SSH_ARGS") +} +variable "ib_version" { + type = string + default = env("IB_VERSION") +} +variable "ansible_user_vars" { + type = string + default = "" +}