Skip to content

Commit

Permalink
feat(pre-commit-hook): Use filenames from hook, update api, and add m…
Browse files Browse the repository at this point in the history
…ore tests #22
  • Loading branch information
dannyvassallo committed Jul 9, 2024
1 parent 4b60889 commit 5d23c99
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 43 deletions.
68 changes: 65 additions & 3 deletions .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ on:
paths:
- 'pre_commit_hooks/**'
- 'pre_commit_hooks_tests/**'
- '.github/workflows/pre-commit.yml'
pull_request:
paths:
- 'pre_commit_hooks/**'
- 'pre_commit_hooks_tests/**'
- '.github/workflows/pre-commit.yml'
jobs:
run-unit-tests-and-lint:
runs-on: ubuntu-latest
Expand All @@ -23,12 +25,14 @@ jobs:
- run: ruff check ./pre_commit_hooks_tests/ ./pre_commit_hooks
- run: mypy ./pre_commit_hooks/ ./pre_commit_hooks_tests/
- run: black --check .
integ-test-linux:
validate-integ-test-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: sudo apt-get install pre-commit
- run: pre-commit install
- run: echo >> ./guard/resources/validate/data-dir/advanced_regex_negative_lookbehind_non_compliant.yaml
- run: git add .
- name: Run pre-commit hook
run: |
output=$(pre-commit run 2>&1 || true)
Expand All @@ -37,12 +41,30 @@ jobs:
echo "Expected output not found"
exit 1
fi
integ-test-macos:
test-integ-test-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: sudo apt-get install pre-commit
- run: pre-commit install
- run: echo >> ./pre_commit_hooks_tests/resources/s3_bucket_logging_enabled.guard
- run: git add .
- name: Run pre-commit hook
run: |
output=$(pre-commit run 2>&1 || true)
echo $output
if ! echo $output | grep -q "exit code: 7"; then
echo "Expected output not found"
exit 1
fi
validate-integ-test-macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- run: brew install pre-commit
- run: pre-commit install
- run: echo >> ./guard/resources/validate/data-dir/advanced_regex_negative_lookbehind_non_compliant.yaml
- run: git add .
- name: Run pre-commit hook
run: |
output=$(pre-commit run 2>&1 || true)
Expand All @@ -51,7 +73,23 @@ jobs:
echo "Expected output not found"
exit 1
fi
integ-test-windows:
test-integ-test-macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- run: brew install pre-commit
- run: pre-commit install
- run: echo >> ./pre_commit_hooks_tests/resources/s3_bucket_logging_enabled.guard
- run: git add .
- name: Run pre-commit hook
run: |
output=$(pre-commit run 2>&1 || true)
echo $output
if ! echo $output | grep -q "exit code: 7"; then
echo "Expected output not found"
exit 1
fi
validate-integ-test-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -60,6 +98,8 @@ jobs:
python-version: '3.10.0'
- run: pip install pre-commit
- run: pre-commit install
- run: '"`n" | Out-File -Append ./guard/resources/validate/data-dir/advanced_regex_negative_lookbehind_non_compliant.yaml'
- run: git add .
- name: Run pre-commit hook
shell: powershell
run: |
Expand All @@ -71,3 +111,25 @@ jobs:
Write-Host $output
exit 1
}
test-integ-test-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.10.0'
- run: pip install pre-commit
- run: pre-commit install
- run: '"`n" | Out-File -Append ./pre_commit_hooks_tests/resources/s3_bucket_logging_enabled.guard'
- run: git add .
- name: Run pre-commit hook
shell: powershell
run: |
$output = pre-commit run | Out-String
if ($output | Select-String -Pattern "exit code: 7") {
exit 0
} else {
Write-Host "Expected output not found"
Write-Host $output
exit 1
}
13 changes: 9 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
# TODO: Update this to use the release from CFN once merged
repos:
- repo: https://github.com/dannyvassallo/cloudformation-guard
rev: pre-commit-v0.0.62
rev: pre-commit-v0.0.96
hooks:
- id: cfn-guard
args:
- validate
- --rules=./guard/resources/validate/rules-dir/
- --data=./guard/resources/validate/data-dir/
- "--operation=validate"
- "--rules=guard/resources/validate/rules-dir/"
files: ^guard/resources/validate/data-dir/.*
- id: cfn-guard
args:
- "--operation=test"
- "--dir=pre_commit_hooks_tests/resources/"
files: ^pre_commit_hooks_tests/resources.*
2 changes: 1 addition & 1 deletion .pre-commit-hooks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
language: python
verbose: true
always_run: true
pass_filenames: false
pass_filenames: true
58 changes: 34 additions & 24 deletions pre_commit_hooks/cfn_guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,25 @@
This module contains the logic for the cfn-guard pre-commit hook
"""

import json
import os
import platform
import shutil
import subprocess
import sys
import tarfile
import tempfile
import argparse
from pathlib import Path
from typing import Sequence, Union
from urllib.request import Request, urlopen

# pylint: disable=C0301
LATEST_RELEASE_URL = (
"https://api.github.com/repos/aws-cloudformation/cloudformation-guard/releases/latest"
)
BIN_NAME = "cfn-guard"
UNSUPPORTED_OS_MSG = "Unsupported operating system. Could not install cfn-guard."
UNKNOWN_OPERATION_MSG = (
"Unknown operation. cfn-guard pre-commit-hook only supports validate and test commands."
)
# Hardcode this so the pre-commit-hook rev is tied to a specific version
GUARD_BINARY_VERSION = "3.1.1"

release_urls_dict = {
# pylint: disable=C0301
Expand Down Expand Up @@ -66,16 +67,6 @@ def request(url: str):
return Request(url, headers={"User-Agent": "Mozilla/5.0"})


def get_latest_tag() -> str:
"""Get the latest release tag from Github"""

req = request(LATEST_RELEASE_URL)

with urlopen(req) as response:
data = response.read().decode("utf-8")
return json.loads(data)["tag_name"]


def get_binary_name() -> str:
"""Get an OS specific binary name"""

Expand All @@ -84,15 +75,14 @@ def get_binary_name() -> str:

def install_cfn_guard():
"""
Install the latest cfn-guard to the install_dir to avoid
Install the cfn-guard to the install_dir to avoid
global version conflicts with existing installations, rust,
and cargo.
"""
latest_tag = get_latest_tag()
binary_name = get_binary_name()

if current_os in supported_oses:
url = release_urls_dict[current_os].replace("TAG", latest_tag)
url = release_urls_dict[current_os].replace("TAG", GUARD_BINARY_VERSION)
# Download tarball of release from Github
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
with urlopen(url) as response:
Expand Down Expand Up @@ -124,18 +114,18 @@ def install_cfn_guard():
raise CfnGuardPreCommitError(f"{UNSUPPORTED_OS_MSG}: {current_os}", code=1)


def run_cfn_guard(args: Sequence[str]) -> int:
def run_cfn_guard(args: str) -> int:
"""Pass arguments to and run cfn-guard"""

binary_name = get_binary_name()
binary_path: str = str(Path(os.path.join(install_dir, binary_name)))

if os.path.exists(binary_path):
project_root: str = os.getcwd()
cmd = [binary_path] + list(args)
cmd = f"{binary_path} {args}"

try:
result = subprocess.run(" ".join(cmd), cwd=project_root, shell=True, check=True)
result = subprocess.run(cmd, cwd=project_root, shell=True, check=True)
return result.returncode
except subprocess.CalledProcessError as e:
return e.returncode
Expand All @@ -147,12 +137,32 @@ def run_cfn_guard(args: Sequence[str]) -> int:

def main(argv: Union[Sequence[str], None] = None) -> int:
"""Entry point for the pre-commit hook"""

# This only serves to chop the first arg (the filename) when running the script directly
if argv is None:
argv = sys.argv[1:]

return run_cfn_guard(argv)
parser = argparse.ArgumentParser()
parser.add_argument("filenames", nargs="*", help="Files to validate")
parser.add_argument("--operation", action="append", help="cfn-guard operation", required=True)
parser.add_argument("--rules", action="append", help="Rules file/directory")
parser.add_argument("--dir", action="append", help="Test & rules directory")

args = parser.parse_args(argv)

exit_code = 0

for filename in args.filenames:
if args.operation[0] == "validate":
cmd = f"validate --rules={args.rules[0]} --data={filename}"
elif args.operation[0] == "test":
cmd = f"test --dir={args.dir[0]}"
else:
raise CfnGuardPreCommitError(UNKNOWN_OPERATION_MSG)

result = run_cfn_guard(cmd)
if result != 0:
exit_code = result

return exit_code


# Handle invocation from python directly
Expand Down
41 changes: 41 additions & 0 deletions pre_commit_hooks_tests/resources/s3_bucket_logging_enabled.guard
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#
#####################################
## Gherkin ##
#####################################
# Rule Identifier:
# S3_BUCKET_LOGGING_ENABLED
#
# Description:
# Checks whether logging is enabled for your S3 buckets.
#
# Reports on:
# AWS::S3::Bucket
#
# Evaluates:
# AWS CloudFormation
#
# Rule Parameters:
# NA
#
# Scenarios:
# a) SKIP: when there are no S3 resource present
# b) PASS: when all S3 resources Logging Configuration exists
# c) FAIL: when all S3 resources have Logging Configuration is not set
# d) SKIP: when metadata includes the suppression for rule S3_BUCKET_LOGGING_ENABLED

#
# Select all S3 resources from incoming template (payload)
#

let s3_buckets_bucket_logging_enabled = Resources.*[ Type == 'AWS::S3::Bucket'
Metadata.guard.SuppressedRules not exists or
Metadata.guard.SuppressedRules.* != "S3_BUCKET_LOGGING_ENABLED"
]

rule S3_BUCKET_LOGGING_ENABLED when %s3_buckets_bucket_logging_enabled !empty {
%s3_buckets_bucket_logging_enabled.Properties.LoggingConfiguration exists
<<
Violation: S3 Bucket Logging needs to be configured to enable logging.
Fix: Set the S3 Bucket property LoggingConfiguration to start logging into S3 bucket.
>>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
###
# S3_BUCKET_LOGGING_ENABLED failing tests
###
---
- name: S3 Bucket with Logging Configuration missing with suppression, SKIP
input:
Resources:
ExampleS3:
Type: AWS::S3::Bucket
Metadata:
guard:
SuppressedRules:
- S3_BUCKET_LOGGING_ENABLED
Properties:
BucketName: my-bucket
expectations:
rules:
S3_BUCKET_LOGGING_ENABLED: FAIL
Loading

0 comments on commit 5d23c99

Please sign in to comment.