HashiQube - DevOps Lab
Youtube Channel Medium Posts Riaan Nolan Linkedin Riaan Nolan Hashicorp Ambassador

.

Packer

Packer Logo

In this HashiQube DevOps lab you will get hands on experience with HashiCorp Packer.

Packer is an open source tool for creating identical machine images for multiple platforms from a single source configuration. Packer is lightweight, runs on every major operating system, and is highly performant, creating machine images for multiple platforms in parallel.

Packer will build a Docker container, use the Shell and Ansible provisioners, Ansible will also connect to Vault to retrieve secrets using a Token.

Keeping track of base images can be challenging. In this whiteboard video, HashiCorp Co-Founder and CTO Armon Dadgar explains how HCP Packer forms the core of a multi-cloud golden image pipeline.

HashiCorp Packer allows you to codify and automate build pipelines for machine images in multiple formats. But how do you make these images discoverable and ensure only the correct versions are deployed to production?

HCP Packer, part of the HashiCorp Cloud Platform, provides a registry that tracks your image metadata and presents it to downstream processes through an API. Together with the Packer data source in the HCP provider for Terraform, this forms the foundation of a multi-cloud golden image pipeline to automate the lifecycle of images from build through deployment.

Introduction to HashiCorp Packer

https://learn.hashicorp.com/vault/getting-started/secrets-engines https://docs.ansible.com/ansible/latest/plugins/lookup/hashi_vault.html

Packer Templates can be found in packer/packer/linux and packer/packer/windows

You can build local Windows and Ubuntu boxes with packer using these commands

You must be in the directory packer

Now you can run ./run.sh

Packer Templates

Packer uses the HashiCorp Configuration Language - HCL - designed to allow concise descriptions of the required steps to get to a build file.

Ubuntu 22.04 Packer Template

packer/linux/ubuntu/ubuntu-2204.pkr.hcl

# Hashicorp Packer
#
# https://www.packer.io/
#

# source blocks are generated from your builders; a source can be referenced in
# build blocks. A build block runs provisioner and post-processors on a
# source. Read the documentation for source blocks here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/source
source "azure-arm" "ubuntu-2204" {
  client_id     = "${var.azure_client_id}"
  client_secret = "${var.azure_client_secret}"
  #tenant_id                        = "${var.azure_tenant_id}"
  subscription_id                   = "${var.azure_subscription_id}"
  image_offer                       = "0001-com-ubuntu-server-jammy"
  image_publisher                   = "Canonical"
  image_sku                         = "22_04-lts-gen2"
  image_version                     = "latest"
  managed_image_name                = "ubuntu-2204"
  location                          = "${var.azure_region}"
  managed_image_resource_group_name = "resourcegroup"
  os_type                           = "linux"
  vm_size                           = "Standard_DS2_v2"
  shared_image_gallery_destination {
    gallery_name        = "SharedImageGallery"
    image_name          = "ubuntu-2204"
    image_version       = "${local.azure_version_number}"
    replication_regions = ["${var.azure_region}"]
    resource_group      = "resourcegroup"
  }
  azure_tags = {
    vm_name = "ubuntu-2204"
  }
}

source "amazon-ebs" "ubuntu-2204" {
  source_ami_filter {
    filters = {
      name         = "*ubuntu-jammy-22.04-amd64-server*"
      architecture = "x86_64"
    }
    owners = ["099720109477"]
    most_recent = true
  }
  access_key    = "${var.aws_access_key}"
  secret_key    = "${var.aws_secret_key}"
  region        = "${var.aws_region}"
  instance_type = "${var.aws_instance_type}"
  ssh_username  = "ubuntu"
  ami_name      = "ubuntu-2204-${local.version_number}"
  tags = {
    vm_name = "ubuntu-2204"
  }
}

source "googlecompute" "ubuntu-2204" {
  project_id          = "${var.gcp_project_id}"
  account_file        = "${var.gcp_account_file}"
  disk_size           = "${var.disk_size}"
  image_name          = "ubuntu-2204-${local.version_number}"
  source_image_family = "ubuntu-2204-lts"
  ssh_username        = "packer"
  zone                = "${var.gcp_zone}"
  image_labels = {
    vm_name = "ubuntu-2204"
  }
  image_family = "soe-ubuntu-2204-lts"
}

source "vagrant" "ubuntu-2204" {
  source_path     = "ubuntu/jammy64"
  template        = "linux/ubuntu/templates/ubuntu/2204/Vagrantfile.tpl"
  provider        = "virtualbox"
  teardown_method = "suspend"
  skip_package    = true
  communicator    = "ssh"
  box_name        = "ubuntu-2204"
  output_dir      = "${var.build_directory}/ubuntu-2204/vagrant"
}

source "docker" "ubuntu-2204" {
  image   = "ubuntu:22.04"
  commit  = false
  discard = true
}

# a build block invokes sources and runs provisioning steps on them. The
# documentation for build blocks can be found here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/build
build {
  sources = ["source.docker.ubuntu-2204", "source.vagrant.ubuntu-2204", "source.azure-arm.ubuntu-2204", "source.amazon-ebs.ubuntu-2204", "source.googlecompute.ubuntu-2204"]

  provisioner "shell" {
    inline = ["cat /etc/os-release"]
  }

  provisioner "ansible" {
    command = "./scripts/ansible.sh"
    extra_arguments = [
      "-vvv",
      "--tags", "always,day0",
      "--extra-vars", "ansible_become=true version_number=${local.version_number}"
    ]
    ansible_ssh_extra_args = [
      "-o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa"
    ]
    host_alias    = "none"
    playbook_file = "../../ansible/roles/ansible-role-example-role/site.yml"
    only          = ["vagrant.ubuntu-2204", "azure-arm.ubuntu-2204", "googlecompute.ubuntu-2204"]
  }

  provisioner "ansible" {
    command = "./scripts/ansible.sh"
    user    = "${build.User}"
    extra_arguments = [
      #"-v",
      "--extra-vars", "foo=bar"
    ]
    ansible_ssh_extra_args = [
      "-o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa"
    ]
    host_alias    = "none"
    playbook_file = "../../ansible/galaxy/roles/UBUNTU22-CIS/site.yml"
    only          = ["vagrant.ubuntu-2204", "azure-arm.ubuntu-2204", "googlecompute.ubuntu-2204"]
  }

  provisioner "ansible" {
    command = "./scripts/ansible.sh"
    user    = "${build.User}"
    extra_arguments = [
      #"-v",
      "--extra-vars", "foo=bar"
    ]
    ansible_ssh_extra_args = [
      "-o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa"
    ]
    host_alias    = "none"
    playbook_file = "../../ansible/galaxy/roles/UBUNTU22-CIS/site.yml"
    only          = ["amazon-ebs.ubuntu-2204"]
  }

  provisioner "shell-local" {
    inline = ["curl -s https://api.ipify.org/?format=none"]
  }

  provisioner "shell" {
    execute_command = "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'"
    inline          = ["/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync"]
    inline_shebang  = "/bin/sh -x"
    only            = ["azure-arm.ubuntu-2204"]
  }
}

Windows 2019 Packer Template

packer/windows/windowsserver/windows-2019.pkr.hcl

# Hashicorp Packer
#
# https://www.packer.io/
#

# source blocks are generated from your builders; a source can be referenced in
# build blocks. A build block runs provisioner and post-processors on a
# source. Read the documentation for source blocks here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/source
source "azure-arm" "windows-2019" {
  client_id     = "${var.azure_client_id}"
  client_secret = "${var.azure_client_secret}"
  #tenant_id                         = "${var.azure_tenant_id}"
  subscription_id                   = "${var.azure_subscription_id}"
  image_offer                       = "WindowsServer"
  image_publisher                   = "MicrosoftWindowsServer"
  image_sku                         = "2019-Datacenter"
  image_version                     = "latest"
  managed_image_name                = "windows-2019"
  location                          = "${var.azure_region}"
  managed_image_resource_group_name = "resourcegroup"
  os_type                           = "windows"
  vm_size                           = "Standard_DS2_v2"
  communicator                      = "winrm"
  winrm_username                    = "packer_user"
  winrm_insecure                    = true
  winrm_use_ssl                     = true
  shared_image_gallery_destination {
    gallery_name        = "SharedImageGallery"
    image_name          = "windows-2019"
    image_version       = "${local.azure_version_number}"
    replication_regions = ["${var.azure_region}"]
    resource_group      = "resourcegroup"
  }
  azure_tags = {
    vm_name       = "windows-2019"
    image_version = "${local.version_number}"
  }
}

source "amazon-ebs" "windows-2019" {
  force_deregister = true
  access_key       = "${var.aws_access_key}"
  secret_key       = "${var.aws_secret_key}"
  region           = "${var.aws_region}"
  ami_name         = "windows-2019-${local.version_number}"
  instance_type    = "${var.aws_instance_type}"
  user_data_file   = "./windows/windowsserver/scripts/bootstrap.txt"
  communicator     = "winrm"
  winrm_username   = "Administrator"
  winrm_insecure   = true
  winrm_use_ssl    = true
  source_ami_filter {
    filters = {
      name                = "Windows_Server-2019-English-Full-Base*"
      root-device-type    = "ebs"
      virtualization-type = "hvm"
    }
    most_recent = true
    owners      = ["801119661308"]
  }
}

source "googlecompute" "windows-2019" {
  project_id          = "${var.gcp_project_id}"
  account_file        = "${var.gcp_account_file}"
  disk_size           = "${var.disk_size}"
  image_name          = "windows-2019-${local.version_number}"
  source_image_family = "windows-2019"
  communicator        = "winrm"
  winrm_username      = "packer_user"
  winrm_insecure      = true
  winrm_use_ssl       = true
  zone                = "${var.gcp_zone}"
  metadata = {
    windows-startup-script-cmd = "winrm quickconfig -quiet & net user /add packer_user & net localgroup administrators packer_user /add & winrm set winrm/config/service/auth @{Basic=\"true\"}"
  }
  image_labels = {
    vm_name = "windows-2019"
  }
  image_family = "soe-windows-2019"
}

source "vagrant" "windows-2019" {
  source_path = "jborean93/WindowsServer2019"
  provider    = "virtualbox"
  # the Vagrant builder currently only supports the ssh communicator
  communicator    = "ssh"
  ssh_username    = "vagrant"
  ssh_password    = "vagrant"
  teardown_method = "suspend"
  skip_package    = true
  box_name        = "windows-2019"
  output_dir      = "${var.build_directory}/windows-2019/vagrant"
}

# a build block invokes sources and runs provisioning steps on them. The
# documentation for build blocks can be found here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/build
build {
  sources = ["source.azure-arm.windows-2019", "source.amazon-ebs.windows-2019", "source.googlecompute.windows-2019", "source.vagrant.windows-2019"]

  provisioner "powershell" {
    script = "./windows/windowsserver/scripts/ConfigureRemotingForAnsible.ps1"
    only   = ["azure-arm.windows-2019"]
  }

  provisioner "ansible" {
    command   = "./scripts/ansible.sh"
    user      = "${build.User}"
    use_proxy = false
    ansible_env_vars = [
      "ANSIBLE_HOST_KEY_CHECKING=False",
      "ANSIBLE_SSH_ARGS='-o ForwardAgent=yes -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'",
      "ANSIBLE_NOCOLOR=True"
    ]
    extra_arguments = [
      #"-v",
      "--extra-vars",
      "ansible_ssh_pass=${build.User} version_number=${local.version_number} ansible_shell_type=cmd ansible_shell_executable=None"
    ]
    host_alias    = "none"
    playbook_file = "../../ansible/roles/ansible-role-example-role/site.yml"
    only          = ["vagrant.windows-2019"]
  }

  provisioner "ansible" {
    command   = "./scripts/ansible.sh"
    user      = "vagrant"
    use_proxy = false
    ansible_env_vars = [
      "ANSIBLE_HOST_KEY_CHECKING=False",
      "ANSIBLE_SSH_ARGS='-o ForwardAgent=yes -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'",
      "ANSIBLE_NOCOLOR=True"
    ]
    extra_arguments = [
      #"-v",
      "--extra-vars",
      "ansible_ssh_pass=vagrant version_number=${local.version_number} ansible_shell_type=cmd ansible_shell_executable=None rule_2_3_1_5=false win_skip_for_test=true rule_2_3_1_1=false"
    ]
    host_alias    = "none"
    playbook_file = "../../ansible/galaxy/roles/Windows-2019-CIS/site.yml"
    only          = ["vagrant.windows-2019"]
  }

  provisioner "ansible" {
    command   = "./scripts/ansible.sh"
    user      = "vagrant"
    use_proxy = false
    ansible_env_vars = [
      "ANSIBLE_HOST_KEY_CHECKING=False",
      "ANSIBLE_SSH_ARGS='-o ForwardAgent=yes -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'",
      "ANSIBLE_NOCOLOR=True"
    ]
    extra_arguments = [
      # "-vvv",
      "--extra-vars",
      "ansible_ssh_pass=vagrant version_number=${local.version_number} ansible_shell_type=cmd ansible_shell_executable=None ansbile_become=yes ansible_become_method=runas"
    ]
    host_alias    = "none"
    playbook_file = "../../ansible/roles/ansible-role-vm-config/site.yml"
    only          = ["vagrant.windows-2019"]
  }

  provisioner "ansible" {
    command   = "./scripts/ansible.sh"
    user      = "${build.User}"
    use_proxy = false
    extra_arguments = [
      #"-v",
      "--extra-vars",
      "ansible_winrm_server_cert_validation=ignore version_number=${local.version_number}"
    ]
    host_alias    = "none"
    playbook_file = "../../ansible/roles/ansible-role-example-role/site.yml"
    only          = ["amazon-ebs.windows-2019", "googlecompute.windows-2019", "azure-arm.windows-2019"]
  }

  /*
  provisioner "ansible" {
    command   = "./scripts/ansible.sh"
    user      = "${build.User}"
    use_proxy = false
    extra_arguments = [
      #"-v",
      "--extra-vars",
      "ansible_winrm_server_cert_validation=ignore version_number=${local.version_number}"
    ]
    host_alias    = "none"
    playbook_file = "../../ansible/galaxy/roles/ansible-role-win_openssh/site.yml"
    only          = ["amazon-ebs.windows-2019", "googlecompute.windows-2019", "azure-arm.windows-2019"]
  }
  */
  
  provisioner "ansible" {
    command   = "./scripts/ansible.sh"
    user      = "${build.User}"
    use_proxy = false
    extra_arguments = [
      #"-v",
      "--extra-vars",
      "ansible_winrm_server_cert_validation=ignore version_number=${local.version_number}"
    ]
    host_alias    = "none"
    playbook_file = "../../ansible/roles/ansible-role-example-role/site.yml"
    only          = ["amazon-ebs.windows-2019", "googlecompute.windows-2019", "azure-arm.windows-2019"]
  }

  provisioner "ansible" {
    command   = "./scripts/ansible.sh"
    user      = "${build.User}"
    use_proxy = false
    extra_arguments = [
      #"-v",
      "--extra-vars",
      "ansible_winrm_server_cert_validation=ignore section02_patch=false rule_2_3_1_5=false rule_2_3_1_1=false win_skip_for_test=true rule_2_3_1_5=false rule_2_3_1_6=false"
    ]
    host_alias    = "none"
    playbook_file = "../../ansible/galaxy/roles/Windows-2019-CIS/site.yml"
    only          = ["amazon-ebs.windows-2019", "googlecompute.windows-2019", "azure-arm.windows-2019"]
  }

  provisioner "shell-local" {
    inline = ["curl -s https://api.ipify.org/?format=none"]
  }

  provisioner "powershell" {
    inline = [
      "Get-ItemProperty HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\State",
      "C:\\windows\\system32/sysprep\\sysprep.exe /oobe /generalize /quiet /quit /mode:vm",
      "while($true) { $imageState = Get-ItemProperty HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\State | Select ImageState; if($imageState.ImageState -ne 'IMAGE_STATE_GENERALIZE_RESEAL_TO_OOBE') { Write-Output $imageState.ImageState; Start-Sleep -s 10  } else { break } }"
    ]
    only = ["azure-arm.windows-2019"]
  }

  provisioner "powershell" {
    inline = [
      "Get-ItemProperty HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\State",
      "GCESysprep -no_shutdown"
    ]
    only = ["googlecompute.windows-2019"]
  }
}

Packer Vagrant Provisioner

packer.sh

#!/bin/bash

function packer-install() {

  arch=$(lscpu | grep "Architecture" | awk '{print $NF}')
  if [[ $arch == x86_64* ]]; then
    ARCH="amd64"
  elif  [[ $arch == aarch64 ]]; then
    ARCH="arm64"
  fi
  echo -e '\e[38;5;198m'"CPU is $ARCH"

  if pgrep -x "vault" >/dev/null
  then
    echo -e '\e[38;5;198m'"++++ "
    echo -e '\e[38;5;198m'"++++ Vault is running.."
    echo -e '\e[38;5;198m'"++++ "
  else
    echo -e '\e[38;5;198m'"++++ "
    echo -e '\e[38;5;198m'"++++ Ensure Vault is running.."
    echo -e '\e[38;5;198m'"++++ "
    sudo bash /vagrant/vault/vault.sh
  fi

  grep -q "PACKER_LOG=1" /etc/environment
  if [ $? -eq 1 ]; then
    echo "PACKER_LOG=1" >> /etc/environment
  else
    sudo sed 's/PACKER_LOG=.*/PACKER_LOG=1/g' /etc/environment
  fi
  grep -q "PACKER_LOG_PATH=/var/log/packer.log" /etc/environment
  if [ $? -eq 1 ]; then
    echo "PACKER_LOG_PATH=/var/log/packer.log" >> /etc/environment
  else
    sudo sed 's/PACKER_LOG_PATH=.*/PACKER_LOG_PATH=\/var\/log\/packer.log/g' /etc/environment
  fi
  sudo touch /var/log/packer.log
  sudo chmod 777 /var/log/packer.log
  sudo DEBIAN_FRONTEND=noninteractive apt-get --assume-yes install -qq curl unzip jq python3-hvac < /dev/null > /dev/null
  if [ -f /usr/local/bin/packer ]; then
    echo -e '\e[38;5;198m'"++++ "
    echo -e '\e[38;5;198m'"++++ `/usr/local/bin/packer version` already installed at /usr/local/bin/packer"
    echo -e '\e[38;5;198m'"++++ "
  else
    LATEST_URL=$(curl --silent https://releases.hashicorp.com/index.json | jq '{packer}' | egrep "linux.*$ARCH" | sort -rh | head -1 | awk -F[\"] '{print $4}')
    wget -q $LATEST_URL -O /tmp/packer.zip
    sudo mkdir -p /usr/local/bin
    (cd /usr/local/bin && unzip /tmp/packer.zip)
    echo -e '\e[38;5;198m'"++++ "
    echo -e '\e[38;5;198m'"++++ Installed: `/usr/local/bin/packer version`"
    echo -e '\e[38;5;198m'"++++ "
  fi

  echo -e '\e[38;5;198m'"++++ "
  echo -e '\e[38;5;198m'"++++ Ensure Environment Variables from /etc/environment"
  echo -e '\e[38;5;198m'"++++ "
  set -a; source /etc/environment; set +a;

  # Packer will build a Docker container, use the Shell and Ansible provisioners, Ansible will also connect to Vault to retrieve secrets using a Token.
  # https://learn.hashicorp.com/vault/getting-started/secrets-engines
  # https://docs.ansible.com/ansible/latest/plugins/lookup/hashi_vault.html
  # https://learn.hashicorp.com/vault/identity-access-management/iam-authentication
  echo -e '\e[38;5;198m'"++++ "
  echo -e '\e[38;5;198m'"++++ https://www.vaultproject.io/docs/auth/approle/"
  echo -e '\e[38;5;198m'"++++ Using the root Vault token, enable the AppRole auth method"
  echo -e '\e[38;5;198m'"++++ vault auth enable approle"
  echo -e '\e[38;5;198m'"++++ "
  vault auth enable approle
  echo -e '\e[38;5;198m'"++++ "
  echo -e '\e[38;5;198m'"++++ Using the root Vault token, Create an Ansible role"
  echo -e '\e[38;5;198m'"++++ Create an policy named ansible allowing Ansible to read secrets"
  echo -e '\e[38;5;198m'"++++ "
  tee ansible-vault-policy.hcl <<"EOF"
  # Read-only permission on 'kv/ansible*' path
  path "kv/ansible*" {
    capabilities = [ "read" ]
  }
EOF
  vault policy write ansible ansible-vault-policy.hcl
  echo -e '\e[38;5;198m'"++++ "
  echo -e '\e[38;5;198m'"++++ vault write auth/approle/role/ansible \
    secret_id_ttl=10h \\n
    token_policies=ansible \\n
    token_num_uses=100 \\n
    token_ttl=10h \\n
    token_max_ttl=10h \\n
    secret_id_num_uses=100"
  vault write auth/approle/role/ansible \
    secret_id_ttl=10h \
    token_policies=ansible \
    token_num_uses=100 \
    token_ttl=10h \
    token_max_ttl=10h \
    secret_id_num_uses=100
  echo -e '\e[38;5;198m'"++++ "
  echo -e '\e[38;5;198m'"++++ Fetch the RoleID of the Ansible's Role"
  echo -e '\e[38;5;198m'"++++ vault read auth/approle/role/ansible/role-id"
  echo -e '\e[38;5;198m'"++++ "
  vault read auth/approle/role/ansible/role-id
  echo -e '\e[38;5;198m'"++++ "
  echo -e '\e[38;5;198m'"++++ Using the root Vault token,Get a SecretID issued against the AppRole"
  echo -e '\e[38;5;198m'"++++ vault write -f auth/approle/role/ansible/secret-id"
  echo -e '\e[38;5;198m'"++++ "
  vault write -f auth/approle/role/ansible/secret-id
  echo -e '\e[38;5;198m'"++++ "
  echo -e '\e[38;5;198m'"++++ Fetch the Token that Ansible will use to lookup secrets"
  echo -e '\e[38;5;198m'"++++ "
  ANSIBLE_ROLE_ID=$(vault read auth/approle/role/ansible/role-id | grep role_id | tr -s ' ' | cut -d ' ' -f2)
  echo -e '\e[38;5;198m'"++++ "
  echo -e '\e[38;5;198m'"++++ ANSIBLE_ROLE_ID: ${ANSIBLE_ROLE_ID}"
  echo -e '\e[38;5;198m'"++++ "
  ANSIBLE_ROLE_SECRET_ID=$(vault write -f auth/approle/role/ansible/secret-id | grep secret_id | head -n 1 | tr -s ' ' | cut -d ' ' -f2)
  echo -e '\e[38;5;198m'"++++ "
  echo -e '\e[38;5;198m'"++++ ANSIBLE_ROLE_SECRET_ID: ${ANSIBLE_ROLE_SECRET_ID}"
  echo -e '\e[38;5;198m'"++++ vault write auth/approle/login role_id=\"${ANSIBLE_ROLE_ID}\" secret_id=\"${ANSIBLE_ROLE_ID}\""
  echo -e '\e[38;5;198m'"++++ "
  vault write auth/approle/login role_id="${ANSIBLE_ROLE_ID}" secret_id="${ANSIBLE_ROLE_SECRET_ID}"
  echo -e '\e[38;5;198m'"++++ "
  echo -e '\e[38;5;198m'"++++ Using the root Vault token, add a Secret in Vault which Ansible will retrieve"
  echo -e '\e[38;5;198m'"++++ vault secrets enable -path=kv kv"
  echo -e '\e[38;5;198m'"++++ "
  vault secrets enable -path=kv kv
  echo -e '\e[38;5;198m'"++++ "
  echo -e '\e[38;5;198m'"++++ Create a Secret that Ansible will have access too"
  echo -e '\e[38;5;198m'"++++ vault kv put kv/ansible devops=\"all the things\""
  echo -e '\e[38;5;198m'"++++ "
  vault kv put kv/ansible devops="all the things"
  ANSIBLE_TOKEN=$(vault write auth/approle/login role_id="${ANSIBLE_ROLE_ID}" secret_id="${ANSIBLE_ROLE_SECRET_ID}" | grep token | head -n1 | tr -s ' ' | cut -d ' ' -f2)
  echo -e '\e[38;5;198m'"++++ "
  echo -e '\e[38;5;198m'"++++ ANSIBLE_TOKEN: ${ANSIBLE_TOKEN}"
  # sed -i "s:token=[^ ]*:token=${ANSIBLE_TOKEN}:" /vagrant/packer/packer/linux/ubuntu/playbook.yml
  echo -e '\e[38;5;198m'"++++ Install Ansible to configure Containers/VMs/AMIs/Whatever"
  echo -e '\e[38;5;198m'"++++ "
  sudo pip3 install ansible --break-system-packages --quiet
  if [ -f /usr/local/bin/ansible ]; then
    echo -e '\e[38;5;198m'"++++ `/usr/local/bin/ansible --version | head -n 1` already installed at /usr/local/bin/ansible"
  else
    sudo pip3 install ansible --break-system-packages --quiet
  fi
  echo -e '\e[38;5;198m'"++++ "
  echo -e '\e[38;5;198m'"++++ Install Docker so we can build Docker Images"
  # https://docs.docker.com/install/linux/docker-ce/ubuntu/
  if [ -f /usr/bin/docker ]; then
    echo -e '\e[38;5;198m'"++++ `/usr/bin/docker -v` already installed at /usr/bin/docker"
  else
    sudo bash /vagrant/docker/docker.sh
  fi
  echo -e '\e[38;5;198m'"++++ Packer build Linux Docker container configured with Ansible"
  echo -e '\e[38;5;198m'"++++ "
  # packer build /vagrant/packer/packer/linux/ubuntu/ubuntu-2204.hcl
  cd /vagrant/packer/packer/
  ./run.sh
}

packer-install