Skip to content

Instantly share code, notes, and snippets.

@davidejones
Last active January 25, 2025 19:37
Show Gist options
  • Save davidejones/f9de97141657b8f616c099227e6fe48b to your computer and use it in GitHub Desktop.
Save davidejones/f9de97141657b8f616c099227e6fe48b to your computer and use it in GitHub Desktop.
Hugo deploy config to aws cli commands

hugodeploy2cli

Uses the hugo deploy config files to generate equivalent commands for other deployers

Usage

To generate the aws cli equivalent commands use the --aws flag and provide a hugo config that contains the deployment block

hugodeploy2cli.py ./config/preview/config.yaml --aws

e.g given something like this

deployment:
    targets:
      - name: "live"
        URL: "s3://my-cool-bucket?region=us-east-1"
    matchers:
      - pattern: "^.+\\.(js|css|svg)$"
        cacheControl: "max-age=31536000, no-transform, public"
      - pattern: "^.+\\.html$"
        contentType: "text/html; charset=utf-8"
        cacheControl: "public, must-revalidate, proxy-revalidate, max-age=0"

should give you

aws s3 cp \
    --recursive \
    --cache-control "max-age=31536000, no-transform, public" \
    --exclude "*" \
    --include "*.js" \
    --include "*.css" \
    --include "*.svg" \
    "C:\path\to\dist" "s3://my-cool-bucket?region=us-east-1" 

aws s3 cp \
    --recursive \
    --cache-control "public, must-revalidate, proxy-revalidate, max-age=0" \
    --exclude "*" \
    --include "*.html" \
    "C:\path\to\dist" "s3://my-cool-bucket?region=us-east-1" 
import argparse
import json
import sys
import tomllib
from pathlib import Path
import yaml
def parse_config(config_path):
config_dict = {}
if config_path.suffix == '.yaml' or config_path.suffix == '.yml':
with open(config_path, 'r') as f:
config_dict = yaml.safe_load(f.read())
elif config_path.suffix == '.json':
with open(config_path, 'r') as f:
config_dict = json.load(f)
elif config_path.suffix == '.toml':
with open("pyproject.toml", "rb") as f:
config_dict = tomllib.load(f)
else:
sys.exit('Invalid file extension')
return config_dict
def parseGlob(s):
delim = ','
if '|' in s:
delim = '|'
items = (s
.replace('^','')
.replace('$','')
.replace('.+','')
.replace('.*','')
.replace('.(','')
.replace('\\.','')
.replace('+', '')
.replace('*', '')
.replace('{','')
.replace('}','')
.replace('(','')
.replace(')','')
.replace('\\', '')
.split(delim)
)
for i, item in enumerate(items):
if len(item) <= 5:
items[i] = f'*.{item}'
return items
def process(options):
config_path = Path(options.hugo_config)
config_dict = parse_config(config_path)
deployment = config_dict.get('deployment', {})
if not deployment:
sys.exit('No deployment specified')
# no matchers we just want our plain targets with include/excludes
for target in deployment.get('targets', []):
matchers = deployment.get('matchers', [])
if matchers:
# matchers! we need to output per matcher...
for match in matchers:
s = 'aws s3 cp \\\n'
s += ' --recursive \\\n'
cache_control = match.get('cacheControl', '')
if cache_control:
s += f' --cache-control "{cache_control}" \\\n'
s += f' --exclude "*" \\\n'
patterns = parseGlob(match.get('pattern', ''))
for pat in patterns:
s += f' --include "{pat}" \\\n'
s += f' "{options.dist}" "{target.get("URL")}" \n'
print(s)
else:
s = 'aws s3 cp \\\n'
s += ' --recursive \\\n'
exclude = target.get('exclude', '')
if exclude:
excludes = parseGlob(exclude)
for enc in excludes:
s += f' --exclude "*.{enc}" \\\n'
else:
s += f' --exclude "*" \\\n'
include = target.get('include', '')
if include:
includes = parseGlob(include)
print(target.get('include', ''), includes)
for inc in includes:
s += f' --include "*.{inc}" \\\n'
else:
s += f' --include "*" \\\n'
s += f' "{options.dist}" "{target.get("URL")}" \n'
print(s)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Hugo deploy config converter')
parser.add_argument('-v', '--verbose', action='store_true', default=False, help='Verbose output')
parser.add_argument('hugo_config', help='path to the hugo config file')
parser.add_argument('dist', nargs='?', default=Path(Path.cwd() / "dist"), help='path to the built hugo site e.g ./dist or ./public')
group = parser.add_mutually_exclusive_group()
group.add_argument('-a', '--aws', action='store_true')
# add other output targets here at some point...
options = parser.parse_args()
process(options)
import unittest
from hugodeploy2cli import process, parse_config
class ParseConfigTests(unittest.TestCase):
def test_unsupported_file_extension(self):
self.assertEqual(parse_config("./foo.yaml"), 'ff')
class ProcessTests(unittest.TestCase):
def test_deployment_entry_not_found(self):
self.assertEqual(process("fdsa"), [])
if __name__ == '__main__':
unittest.main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment