From 41f442556670abade23ee6d5e09f2fd8384f9b77 Mon Sep 17 00:00:00 2001
From: Adar Nimrod <nimrod@shore.co.il>
Date: Mon, 6 Dec 2021 00:26:01 +0200
Subject: [PATCH] Packer hooks.

New hooks for packer fmt, fix and validate. Includes test files.
---
 .gitlab-ci.yml              |  2 +-
 .pre-commit-hooks.yaml      | 21 +++++++++++++++++++
 README.md                   | 15 +++++++++++++
 hooks/packer_fix.py         | 38 +++++++++++++++++++++++++++++++++
 hooks/packer_fmt.py         | 22 +++++++++++++++++++
 hooks/packer_validate.py    | 22 +++++++++++++++++++
 setup.py                    |  3 +++
 test_files/packer.json      | 16 ++++++++++++++
 test_files/template.pkr.hcl | 42 +++++++++++++++++++++++++++++++++++++
 9 files changed, 180 insertions(+), 1 deletion(-)
 create mode 100644 hooks/packer_fix.py
 create mode 100644 hooks/packer_fmt.py
 create mode 100644 hooks/packer_validate.py
 create mode 100644 test_files/packer.json
 create mode 100644 test_files/template.pkr.hcl

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 9061a98..bc2bc32 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -21,7 +21,7 @@ pre-commit-try-repo:
       /etc/apt/sources.list.d/hashicorp.list
     # yamllint enable rule:line-length
     - apt-get update
-    - apt-get install -y terraform
+    - apt-get install -y terraform packer
   script:
     - pre-commit try-repo --all-files ./
   cache:
diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml
index 8deb7d4..6b2ad4e 100644
--- a/.pre-commit-hooks.yaml
+++ b/.pre-commit-hooks.yaml
@@ -37,6 +37,27 @@
   types: [terraform]
   entry: terraform-validate
 
+- id: packer-fix
+  name: Fix Packer templates
+  description: Fix known backwards incompatibilities in Packer templates
+  language: python
+  entry: packer-fix
+  types: [json, hcl]
+
+- id: packer-fmt
+  name: Format Packer templates
+  description: Rewrites all Packer configuration files to a canonical format
+  language: python
+  entry: packer-fmt
+  types: [hcl]
+
+- id: packer-validate
+  name: Validate Packer templates
+  description: Checks that the Packer template is valid
+  language: python
+  entry: packer-validate
+  types: [json, hcl]
+
 - id: poetry-check
   name: poetry check
   description: Validate pyproject.toml files using Poetry
diff --git a/README.md b/README.md
index 0cf0a2e..143df4f 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,9 @@ A collection of [pre-commit](https://pre-commit.com/) hooks.
     - id: docker-compose
     - id: terraform-fmt
     - id: terraform-validate
+    - id: packer-fmt
+    - id: packer-fix
+    - id: packer-validate
     - id: poetry-check
     - id: branch-merge-conflict
 ```
@@ -50,6 +53,18 @@ Requires an installed `terraform`.
 Validate Terraform modules using `terraform validate`.
 Requires an installed `terraform`.
 
+### `packer-fix`
+
+Fix known backwards incompatibilities in Packer templates.
+
+### `packer-fmt`
+
+Rewrites all Packer configuration files to a canonical format (just HCL files).
+
+### `packer-validate`
+
+Checks that the Packer template is valid.
+
 ### `poetry-check`
 
 Validate `pyproject.toml` files using Poetry.
diff --git a/hooks/packer_fix.py b/hooks/packer_fix.py
new file mode 100644
index 0000000..c8d8aee
--- /dev/null
+++ b/hooks/packer_fix.py
@@ -0,0 +1,38 @@
+"""Fix known backwards incompatibilities in Packer templates."""
+
+import argparse
+import locale
+import pathlib
+import sys
+import hooks.utils
+
+
+def packer_fix(file):
+    """Runs packer fix.
+
+    If the invocation succeeds, overwrite the file with the fixed output from
+    packer. If it fails, fail.
+    """
+    proc = hooks.utils.run(["packer", "fix", file])
+    if proc.returncode > 0:
+        return 1
+    # pylint: disable=invalid-name
+    with open(file, "w", encoding=locale.getpreferredencoding()) as fh:
+        fh.write(proc.stdout)
+    return 0
+
+
+def main():
+    """Main entrypoint."""
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument("file", nargs="+", type=pathlib.Path)
+    args = parser.parse_args()
+    hooks.utils.check_executable("packer")
+    return hooks.utils.bulk_check(
+        packer_fix,
+        args.file,
+    )
+
+
+if __name__ == "__main__":
+    sys.exit(main())
diff --git a/hooks/packer_fmt.py b/hooks/packer_fmt.py
new file mode 100644
index 0000000..a9e2a21
--- /dev/null
+++ b/hooks/packer_fmt.py
@@ -0,0 +1,22 @@
+"""Rewrites all Packer configuration files to a canonical format."""
+
+import argparse
+import pathlib
+import sys
+import hooks.utils
+
+
+def main():
+    """Main entrypoint."""
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument("file", nargs="+", type=pathlib.Path)
+    args = parser.parse_args()
+    hooks.utils.check_executable("packer")
+    return hooks.utils.bulk_check(
+        lambda x: hooks.utils.check_file(["packer", "fmt", "-diff", x]),
+        args.file,
+    )
+
+
+if __name__ == "__main__":
+    sys.exit(main())
diff --git a/hooks/packer_validate.py b/hooks/packer_validate.py
new file mode 100644
index 0000000..7df19eb
--- /dev/null
+++ b/hooks/packer_validate.py
@@ -0,0 +1,22 @@
+"""Checks that the Packer template is valid."""
+
+import argparse
+import pathlib
+import sys
+import hooks.utils
+
+
+def main():
+    """Main entrypoint."""
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument("file", nargs="+", type=pathlib.Path)
+    args = parser.parse_args()
+    hooks.utils.check_executable("packer")
+    return hooks.utils.bulk_check(
+        lambda x: hooks.utils.check_file(["packer", "validate", x]),
+        args.file,
+    )
+
+
+if __name__ == "__main__":
+    sys.exit(main())
diff --git a/setup.py b/setup.py
index 1b48a0a..de3c388 100644
--- a/setup.py
+++ b/setup.py
@@ -19,6 +19,9 @@ setup(
             "docker-compose-validate=hooks.docker_compose_validate:main",
             "terraform-validate=hooks.terraform_validate:main",
             "terraform-fmt=hooks.terraform_fmt:main",
+            "packer-fix=hooks.packer_fix:main",
+            "packer-fmt=hooks.packer_fmt:main",
+            "packer-validate=hooks.packer_validate:main",
             "poetry-check=hooks.poetry_check:main",
         ]
     },
diff --git a/test_files/packer.json b/test_files/packer.json
new file mode 100644
index 0000000..e26c6e1
--- /dev/null
+++ b/test_files/packer.json
@@ -0,0 +1,16 @@
+{
+  "variables": {
+    "access_key": "{{env `AWS_ACCESS_KEY_ID`}}",
+    "secret_key": "{{env `AWS_SECRET_ACCESS_KEY`}}"
+  },
+  "builders": [{
+    "type": "amazon-ebs",
+    "access_key": "{{user `access_key`}}",
+    "secret_key": "{{user `access_key`}}",
+    "region": "us-east-1",
+    "source_ami": "ami-de0d9eb7",
+    "instance_type": "t1.micro",
+    "ssh_username": "ubuntu",
+    "ami_name": "packer-example {{timestamp}}"
+  }]
+}
diff --git a/test_files/template.pkr.hcl b/test_files/template.pkr.hcl
new file mode 100644
index 0000000..06f4a86
--- /dev/null
+++ b/test_files/template.pkr.hcl
@@ -0,0 +1,42 @@
+
+build {
+  name = "alpine"
+  description = <<EOF
+This build creates alpine images for versions :
+* v3.12
+For the following builders :
+* virtualbox-iso
+EOF
+
+  // the common fields of the source blocks are defined in the
+  // source.builder-type.pkr.hcl files, here we only set the fields specific to
+  // the different versions of ubuntu.
+  source "source.virtualbox-iso.base-alpine-amd64" {
+    name                    = "3.12"
+    iso_url                 = local.iso_url_alpine_312
+    iso_checksum            = "file:${local.iso_checksum_url_alpine_312}"
+    output_directory        = "virtualbox_iso_alpine_312_amd64"
+    boot_command            = local.alpine_312_floppy_boot_command
+    boot_wait               = "10s"
+  }
+
+  source "source.vsphere-iso.base-alpine-amd64" {
+    name                    = "3.12"
+    vm_name                 = "alpine-3.12"
+    iso_url                 = local.iso_url_alpine_312
+    iso_checksum            = "file:${local.iso_checksum_url_alpine_312}"
+    boot_command            = local.alpine_312_floppy_boot_command_vsphere
+    boot_wait               = "10s"
+  }
+
+  source "source.vmware-iso.esxi-base-alpine-amd64" {
+    name                    = "3.12-from-esxi"
+    iso_url                 = local.iso_url_alpine_312
+    iso_checksum            = "file:${local.iso_checksum_url_alpine_312}"
+    boot_command            = local.alpine_312_floppy_boot_command_vsphere
+  }
+
+  provisioner "shell" {
+    inline = ["echo hi"]
+  }
+}
-- 
GitLab