modules/packer/custom-image/image.pkr.hcl (194 lines of code) (raw):

# Copyright 2022 Google LLC # # 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. locals { # This label allows for billing report tracking based on module. labels = merge(var.labels, { ghpc_module = "custom-image", ghpc_role = "packer" }) # construct a unique image name from the image family image_family = var.image_family != null ? var.image_family : var.deployment_name image_name_default = "${local.image_family}-${formatdate("YYYYMMDD't'hhmmss'z'", timestamp())}" image_name = var.image_name != null ? var.image_name : local.image_name_default # construct vm image name for use when getting logs instance_name = "packer-${substr(uuidv4(), 0, 6)}" # default to explicit var.communicator, otherwise in-order: ssh/winrm/none shell_script_communicator = length(var.shell_scripts) > 0 ? "ssh" : "" ansible_playbook_communicator = length(var.ansible_playbooks) > 0 ? "ssh" : "" powershell_script_communicator = length(var.windows_startup_ps1) > 0 ? "winrm" : "" communicator = coalesce( var.communicator, local.shell_script_communicator, local.ansible_playbook_communicator, local.powershell_script_communicator, "none" ) # must not enable IAP when no communicator is in use use_iap = local.communicator == "none" ? false : var.use_iap # construct metadata from startup_script and metadata variables startup_script_metadata = var.startup_script == null ? {} : { startup-script = var.startup_script } linux_user_metadata = { block-project-ssh-keys = "TRUE" shutdown-script = <<-EOT #!/bin/bash userdel -r ${var.ssh_username} sed -i '/${var.ssh_username}/d' /var/lib/google/google_users EOT } windows_packer_user = "packer_user" windows_user_metadata = { sysprep-specialize-script-cmd = "winrm quickconfig -quiet & net user /add ${local.windows_packer_user} & net localgroup administrators ${local.windows_packer_user} /add & winrm set winrm/config/service/auth @{Basic=\\\"true\\\"}" windows-shutdown-script-cmd = <<-EOT net user /delete ${local.windows_packer_user} EOT } user_metadata = local.communicator == "winrm" ? local.windows_user_metadata : local.linux_user_metadata # merge metadata such that var.metadata always overrides user management # metadata but always allow var.startup_script to override var.metadata metadata = merge( local.user_metadata, var.metadata, local.startup_script_metadata, ) # determine best value for on_host_maintenance if not supplied by user machine_vals = split("-", var.machine_type) machine_family = local.machine_vals[0] gpu_attached = contains(["a2", "g2"], local.machine_family) || var.accelerator_type != null on_host_maintenance_default = local.gpu_attached ? "TERMINATE" : "MIGRATE" on_host_maintenance = ( var.on_host_maintenance != null ? var.on_host_maintenance : local.on_host_maintenance_default ) accelerator_type = var.accelerator_type == null ? null : "projects/${var.project_id}/zones/${var.zone}/acceleratorTypes/${var.accelerator_type}" winrm_username = local.communicator == "winrm" ? "packer_user" : null winrm_insecure = local.communicator == "winrm" ? true : null winrm_use_ssl = local.communicator == "winrm" ? true : null enable_integrity_monitoring = var.enable_shielded_vm && var.shielded_instance_config.enable_integrity_monitoring enable_secure_boot = var.enable_shielded_vm && var.shielded_instance_config.enable_secure_boot enable_vtpm = var.enable_shielded_vm && var.shielded_instance_config.enable_vtpm image_licenses = [ "projects/click-to-deploy-images/global/licenses/hpc-toolkit-vm-image" ] } source "googlecompute" "toolkit_image" { communicator = local.communicator project_id = var.project_id image_name = local.image_name image_family = local.image_family image_labels = local.labels instance_name = local.instance_name machine_type = var.machine_type accelerator_type = local.accelerator_type accelerator_count = var.accelerator_count on_host_maintenance = local.on_host_maintenance disk_size = var.disk_size disk_type = var.disk_type omit_external_ip = var.omit_external_ip use_internal_ip = var.omit_external_ip subnetwork = var.subnetwork_name network_project_id = var.network_project_id service_account_email = var.service_account_email scopes = var.service_account_scopes source_image = var.source_image source_image_family = var.source_image_family source_image_project_id = var.source_image_project_id ssh_username = var.ssh_username tags = var.tags use_iap = local.use_iap use_os_login = var.use_os_login winrm_username = local.winrm_username winrm_insecure = local.winrm_insecure winrm_use_ssl = local.winrm_use_ssl zone = var.zone labels = local.labels metadata = local.metadata startup_script_file = var.startup_script_file wrap_startup_script = var.wrap_startup_script state_timeout = var.state_timeout image_storage_locations = var.image_storage_locations enable_secure_boot = local.enable_secure_boot enable_vtpm = local.enable_vtpm enable_integrity_monitoring = local.enable_integrity_monitoring image_licenses = local.image_licenses } build { name = var.deployment_name sources = ["sources.googlecompute.toolkit_image"] # using dynamic blocks to create provisioners ensures that there are no # provisioner blocks when none are provided and we can use the none # communicator when using startup-script # provisioner "shell" blocks dynamic "provisioner" { labels = ["shell"] for_each = var.shell_scripts content { execute_command = "sudo -H sh -c '{{ .Vars }} {{ .Path }}'" script = provisioner.value } } # provisioner "powershell" blocks dynamic "provisioner" { labels = ["powershell"] for_each = var.windows_startup_ps1 content { inline = split("\n", provisioner.value) } } dynamic "provisioner" { labels = ["powershell"] for_each = length(var.windows_startup_ps1) > 0 ? [1] : [] content { inline = [ "GCESysprep -no_shutdown" ] } } # provisioner "ansible-local" blocks # this installs custom roles/collections from ansible-galaxy in /home/packer # which will be removed at the end; consider modifying /etc/ansible/ansible.cfg dynamic "provisioner" { labels = ["ansible-local"] for_each = var.ansible_playbooks content { playbook_file = provisioner.value.playbook_file galaxy_file = provisioner.value.galaxy_file extra_arguments = provisioner.value.extra_arguments } } post-processor "manifest" { output = var.manifest_file strip_path = true custom_data = { built-by = "cloud-hpc-toolkit" } } # If there is an error during image creation, print out command for getting packer VM logs error-cleanup-provisioner "shell-local" { environment_vars = [ "PRJ_ID=${var.project_id}", "INST_NAME=${local.instance_name}", "ZONE=${var.zone}", ] inline_shebang = "/bin/bash -e" inline = [ "type -P gcloud > /dev/null || exit 0", "INST_ID=$(gcloud compute instances describe $INST_NAME --project $PRJ_ID --format=\"value(id)\" --zone=$ZONE)", "echo 'Error building image try checking logs:'", join(" ", ["echo \"gcloud logging --project $PRJ_ID read", "'logName=(\\\"projects/$PRJ_ID/logs/GCEMetadataScripts\\\" OR \\\"projects/$PRJ_ID/logs/google_metadata_script_runner\\\") AND resource.labels.instance_id=$INST_ID'", "--format=\\\"table(timestamp, resource.labels.instance_id, jsonPayload.message)\\\"", "--order=asc\"" ] ) ] } }