diff --git a/Dockerfile b/Dockerfile index 96c77a6..17546c6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ -ARG RUNTIME_VERSION="3.8" -ARG DISTRO_VERSION="3.15" -ARG PULUMI_VERSION="3.35.0" +ARG RUNTIME_VERSION="3.9" +ARG DISTRO_VERSION="3.16" +ARG PULUMI_VERSION="3.35.1" FROM python:${RUNTIME_VERSION}-alpine${DISTRO_VERSION} AS builder ARG PULUMI_VERSION diff --git a/Makefile b/Makefile index b5ab0b9..1d9bf3f 100644 --- a/Makefile +++ b/Makefile @@ -14,10 +14,7 @@ endif .PHONY: pytest build test_upload upload all dev_setup pybuild -all: pybuild pytest - -dev_setup: - pip install -U -r dev-requirements.txt --user +all: pytest pybuild pytest: flake8 cloudbender tests @@ -27,7 +24,7 @@ clean: rm -rf .cache build .coverage .eggs cloudbender.egg-info .pytest_cache dist pybuild: - python setup.py bdist_wheel --universal + hatchling build test_upload: $(PACKAGE_FILE) twine upload --repository-url https://test.pypi.org/legacy/ dist/cloudbender-*.whl diff --git a/cloudbender/jinja.py b/cloudbender/jinja.py index 99ef87c..9035b9e 100644 --- a/cloudbender/jinja.py +++ b/cloudbender/jinja.py @@ -7,18 +7,15 @@ import yaml import copy import subprocess import sys +import zlib import jinja2 +import python_minifier import markupsafe + from jinja2.filters import make_attrgetter from jinja2.runtime import Undefined -import pyminifier.token_utils -import pyminifier.minification -import pyminifier.compression -import pyminifier.obfuscate -import types - import logging logger = logging.getLogger(__name__) @@ -129,40 +126,41 @@ def sub(value="", pattern="", replace="", ignorecase=False): return re.sub(pattern, replace, value, flags=flags) -def pyminify(source, obfuscate=False, minify=True): - # pyminifier options - options = types.SimpleNamespace( - tabs=False, - replacement_length=1, - use_nonlatin=0, - obfuscate=0, - obf_variables=1, - obf_classes=0, - obf_functions=0, - obf_import_methods=0, - obf_builtins=0, - ) +def pyminify(source): + minified = python_minifier.awslambda(source, filename=None, entrypoint=None) + gz_source = gz_pack(minified) - tokens = pyminifier.token_utils.listified_tokenizer(source) - - if minify: - source = pyminifier.minification.minify(tokens, options) - tokens = pyminifier.token_utils.listified_tokenizer(source) - - if obfuscate: - name_generator = pyminifier.obfuscate.obfuscation_machine(use_unicode=False) - pyminifier.obfuscate.obfuscate( - "__main__", tokens, options, name_generator=name_generator - ) - # source = pyminifier.obfuscate.apply_obfuscation(source) - - source = pyminifier.token_utils.untokenize(tokens) - # logger.info(source) - minified_source = pyminifier.compression.gz_pack(source) logger.info( - "Compressed python code from {} to {}".format(len(source), len(minified_source)) + "Compressed python code from {} to {}".format(len(source), len(gz_source)) ) - return minified_source + return gz_source + + +# From pyminifier +def gz_pack(source): + """ + Returns 'source' as a gzip-compressed, self-extracting python script. + + .. note:: + + This method uses up more space than the zip_pack method but it has the + advantage in that the resulting .py file can still be imported into a + python program. + """ + out = "" + # Preserve shebangs (don't care about encodings for this) + first_line = source.split("\n")[0] + if re.compile('^#!.*$').match(first_line): + if first_line.rstrip().endswith("python"): + first_line = first_line.rstrip() + first_line += "3" + out = first_line + "\n" + compressed_source = zlib.compress(source.encode("utf-8")) + out += "import zlib, base64\n" + out += "exec(zlib.decompress(base64.b64decode('" + out += base64.b64encode(compressed_source).decode("utf-8") + out += "')))\n" + return out def inline_yaml(block): diff --git a/cloudbender/stack.py b/cloudbender/stack.py index c9361ff..20bd8e3 100644 --- a/cloudbender/stack.py +++ b/cloudbender/stack.py @@ -7,7 +7,7 @@ import time import pathlib import pprint import pulumi -import pkg_resources +import importlib.resources as pkg_resources from datetime import datetime, timedelta from dateutil.tz import tzutc @@ -518,12 +518,8 @@ class Stack(object): if self.outputs: if self.store_outputs: - try: - filename = self.cfn_data["Metadata"]["CustomOutputs"]["Name"] - my_template = self.cfn_data["Metadata"]["CustomOutputs"]["Template"] - except (TypeError, KeyError): - filename = self.stackname + ".yaml" - my_template = pkg_resources.read_text(templates, "outputs.yaml") + filename = self.stackname + ".yaml" + my_template = pkg_resources.read_text(templates, "outputs.yaml") output_file = os.path.join( self.ctx["outputs_path"], self.rel_path, filename diff --git a/dev-requirements.txt b/dev-requirements.txt deleted file mode 100644 index a57959d..0000000 --- a/dev-requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -boto3 -Jinja2 -click -pyminifier -cfn-lint>=0.34 -pulumi -pulumi-aws -pulumi-aws-native diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..99badba --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,45 @@ +[build-system] +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" + +[project] +name = "cloudbender" +dynamic = ["version"] +authors = [ + { name="Stefan Reimer", email="stefan@zero-downtime.net" }, +] +description = "Toolset to render and manage AWS Cloudformation" +readme = "README.md" +license = { file="LICENSE.md" } +requires-python = ">=3.7" +dependencies = [ + "boto3", + "mock", + "Jinja2>=3.0.0", + "click", + "cfn-lint>=0.34", + "python-minifier", + "pulumi>=3.35.0", + "pulumi-aws>5.0.0", + "pulumi-aws-native", + "pulumi-policy", +] +classifiers = [ + "Development Status :: 4 - Beta", + "Environment :: Console", + "Operating System :: POSIX", + "Programming Language :: Python", + "License :: OSI Approved :: GNU Affero General Public License v3", +] + +[project.urls] +"Homepage" = "https://git.zero-downtime.net/ZeroDownTime/CloudBender" + +[project.scripts] +cloudbender = "cloudbender.cli:cli" + +[tool.hatch.version] +source = "vcs" + +[tool.isort] +profile = "black" diff --git a/requirements.txt b/requirements.txt index 9776892..5e8725f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,13 +2,12 @@ boto3 mock Jinja2>=3.0.0 click -pyminifier +python-minifier cfn-lint>=0.34 pulumi>=3.35.0 pulumi-aws>5.0.0 pulumi-aws-native pulumi-policy -# apprise # flake8 # pytest diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index ed32940..0000000 --- a/setup.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[metadata] -description_file = README.md - -[bdist_wheel] -universal=1 diff --git a/setup.py b/setup.py deleted file mode 100644 index 64b2eac..0000000 --- a/setup.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env python -import io -import os -import re -from setuptools import setup -from setuptools import find_packages -from setuptools.command.test import test as TestCommand - -class PyTest(TestCommand): - """TestCommand subclass to use pytest with setup.py test.""" - - def finalize_options(self): - """Find our package name and test options to fill out test_args.""" - - TestCommand.finalize_options(self) - self.test_args = ['-rx', '--cov', 'cloudbender', - '--cov-report', 'term-missing'] - self.test_suite = True - - def run_tests(self): - """Taken from http://pytest.org/latest/goodpractises.html.""" - - # have to import here, outside the eggs aren't loaded - import pytest - errno = pytest.main(self.test_args) - raise SystemExit(errno) - - -if os.path.isfile("README.md"): - with io.open("README.md", encoding="utf-8") as opendescr: - long_description = opendescr.read() -else: - long_description = __doc__ - -setup( - name='cloudbender', - setuptools_git_versioning={ - "enabled": True, - "dev_template": "{tag}-{ccount}", - }, - setup_requires=["setuptools-git-versioning"], - description='Toolset to render and manage AWS Cloudformation', - python_requires='>=3.8', - long_description=long_description, - long_description_content_type="text/markdown", - author='Stefan Reimer', - author_email='stefan@zero-downtime.net', - url='https://git.zero-downtime.net/ZeroDownTime/CloudBender', - packages=find_packages(), - package_data={ 'cloudbender': ['templates/*.md', 'templates/*.yaml'], }, - include_package_data=True, - entry_points={'console_scripts': [ "cloudbender = cloudbender.cli:cli" ]}, - install_requires=['boto3', 'Jinja2>=3.0.0', 'click', 'cfn-lint>=0.34', 'pyminifier', 'pulumi', 'pulumi-aws'], #'apprise' - tests_require=["pytest-cov", "moto", "mock", 'pytest'], - cmdclass={"test": PyTest}, - classifiers=[ - "Development Status :: 4 - Beta", - "Environment :: Console", - "Operating System :: POSIX", - "Programming Language :: Python", - "License :: OSI Approved :: GNU Affero General Public License v3" - ])