Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .gitexclude
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/.idea
/venv
/venv-pc
.DS_Store
__pycache__
*.pyc
*.pyo
/build
/dist
*.egg-info
.cache
.eggs
.tox
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
## AWS Cloudformation Rpdk Python Plugin
## AWS CloudFormation Resource Provider Python Plugin

The CloudFormation Provider Development Toolkit Python Plugin allows you to autogenerate Python code based on an input schema.

## License
The CloudFormation Resource Provider Development Kit (RPDK) allows you to author your own resource providers that can be used by CloudFormation.

This library is licensed under the Apache 2.0 License.
This plugin library helps to provide runtime bindings for the execution of your providers by CloudFormation.

License
-------

This library is licensed under the Apache 2.0 License.
5 changes: 5 additions & 0 deletions python/rpdk/python/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import logging

__version__ = "0.1"

logging.getLogger(__name__).addHandler(logging.NullHandler())
2 changes: 2 additions & 0 deletions python/rpdk/python/cfn_resource/cfn_resource/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from cfn_resource import exceptions
from cfn_resource.cfn_resource import CfnResource
10 changes: 10 additions & 0 deletions python/rpdk/python/cfn_resource/cfn_resource/cfn_resource.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

class CfnResource(object):

FAILED = 'fail'

def __init__(self):
pass

def send_status(self, status, message):
pass
10 changes: 10 additions & 0 deletions python/rpdk/python/cfn_resource/cfn_resource/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class CfnResourceBaseException(Exception):
pass


class CfnResourceInitException(CfnResourceBaseException):
pass


class CfnResourceInternalError(CfnResourceBaseException):
pass
31 changes: 31 additions & 0 deletions python/rpdk/python/cfn_resource/cfn_resource/handler_wrapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import logging
from cfn_resource.exceptions import CfnResourceInitException
from cfn_resource import CfnResource

logger = logging.getLogger(__name__)


def _get_handler(action):
try:
import __handler__
except ModuleNotFoundError:
raise CfnResourceInitException("__handler__.py does not exist")
try:
return getattr(__handler__, "{}_handler".format(action.lower()))
except AttributeError:
raise CfnResourceInitException("__handler__.py does not contain a {}_handler function".format(action))


def _handler_wrapper(event, context):
cfnr = CfnResource()
try:
handler = _get_handler(event["action"])
handler(_event_parse(event, context))
except Exception as e:
logger.error(e, exc_info=True)
cfnr.send_status(status=CfnResource.FAILED, message=str(e))


def _event_parse(event, context):
# TODO: restructure event
return event
20 changes: 20 additions & 0 deletions python/rpdk/python/cfn_resource/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from setuptools import setup


setup(
name="cfn_resource",
version="0.0.1",
description="cfn_resource enables python based CloudFormation resource types",
author="Jay McConnell",
author_email="[email protected]",
license="Apache2",
packages=["cfn_resource"],
install_requires=["boto3>=1.9.108"],
tests_require=[],
test_suite="tests",
classifiers=[
'Programming Language :: Python :: 3.7',
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
],
)
Empty file.
99 changes: 99 additions & 0 deletions python/rpdk/python/codegen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import logging
import shutil
from pip._internal import main as pip
import os
import rpdk.python as pyrpdk
from rpdk.core.plugin_base import LanguagePlugin

LOG = logging.getLogger(__name__)

EXECUTABLE = "uluru-cli"


class PythonLanguagePlugin(LanguagePlugin):
MODULE_NAME = __name__
NAME = "python"
RUNTIME = "python3.7"
ENTRY_POINT = "cfn_resource.handler_wrapper.handler_wrapper"
CODE_URI = "./target/{}.zip"

def __init__(self):
self.env = self._setup_jinja_env(
trim_blocks=True, lstrip_blocks=True, keep_trailing_newline=True
)
self.package_name = None

def _package_from_project(self, project):
self.namespace = tuple(s.lower() for s in project.type_info)
self.package_name = "_".join(self.namespace)

def init(self, project):
LOG.debug("Init started")

self._package_from_project(project)

folders = [project.root / self.package_name, project.root / "tests"]

for f in folders:
LOG.debug("Making folder: %s", f)
f.mkdir(parents=True, exist_ok=True)

templates = [
[
project.root / "Handler.yaml",
self.env.get_template("Handler.yaml"),
{
'resource_type': project.type_name,
'handler_params': {
"Handler": self.ENTRY_POINT,
"Runtime": self.RUNTIME,
"CodeUri": self.CODE_URI.format(self.package_name),
}
}
],
[
project.root / self.package_name / "__handler__.py",
self.env.get_template("__handler__.py"),
{'package_name': self.package_name}
],
[
project.root / "README.md",
self.env.get_template("README.md"),
{
'type_name': project.type_name,
'schema_path': project.schema_path,
'executable': EXECUTABLE
}
]
]

for path, template, kwargs in templates:
LOG.debug("Writing file: %s", path)
contents = template.render(**kwargs)
project.safewrite(path, contents)

LOG.debug("Init complete")

def generate(self, project):
LOG.debug("Generate started")

self._package_from_project(project)

project_path = project.root / self.package_name
cfn_resource_path = project_path / "cfn_resource"

LOG.debug("Removing python rpdk package: %s", cfn_resource_path)
shutil.rmtree(cfn_resource_path, ignore_errors=True)
# cleanup .egg-info dir
for p in os.listdir(project_path):
if p.startswith('cfn_resource-') and p.endswith('.egg-info'):
shutil.rmtree(project_path / p)

LOG.debug("Installing python rpdk package into: %s", project_path)
dest_path = os.path.join(pyrpdk.__path__[0], "cfn_resource")
pip(['--log', './rpdk.log', '-qqq', 'install', '-t', str(project_path), dest_path])

LOG.debug("Generate complete")

def package(self, project):
pass
11 changes: 11 additions & 0 deletions python/rpdk/python/templates/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# {{ type_name }}

Congratulations on starting development! Next steps:

1. Write the JSON schema describing your resource, `{{ schema_path.name }}`
2. Implement your resource handlers


Please don't modify files under `cfn_resource`, as they will be
automatically overwritten.

28 changes: 28 additions & 0 deletions python/rpdk/python/templates/__handler__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
def create_handler(input_properties):
# TODO: put code here
output_properties = {}
return output_properties


def update_handler(input_properties):
# TODO: put code here
output_properties = {}
return output_properties


def delete_handler(input_properties):
# TODO: put code here
output_properties = {}
return output_properties


def read_handler(input_properties):
# TODO: put code here
output_properties = {}
return output_properties


def list_handler(input_properties):
# TODO: put code here
output_properties = {}
return output_properties
Empty file.
38 changes: 38 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[metadata]
license_file = LICENSE
description-file = README.md

[flake8]
exclude =
.git,
__pycache__,
build,
dist,
*.pyc,
*.egg-info,
.cache,
.eggs,
.tox
max-complexity = 10
max-line-length = 80
select = C,E,F,W,B,B950
# C812, C815, W503 clash with black
ignore = E501,C812,C815,W503

[isort]
line_length = 88
indent = ' '
multi_line_output = 3
default_section = FIRSTPARTY
skip = env
include_trailing_comma = true
combine_as_imports = True
force_grid_wrap = 0
known_first_party = rpdk

[tool:pytest]
# can't do anything about 3rd party modules, so don't spam us
filterwarnings =
ignore::DeprecationWarning:botocore
ignore::DeprecationWarning:werkzeug
ignore::DeprecationWarning:yaml
54 changes: 54 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env python
import os.path
import re

from setuptools import setup

HERE = os.path.abspath(os.path.dirname(__file__))


def read(*parts):

with open(os.path.join(HERE, *parts), "r", encoding="utf-8") as fp:
return fp.read()


# https://packaging.python.org/guides/single-sourcing-package-version/
def find_version(*file_paths):
version_file = read(*file_paths)
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M)
if version_match:
return version_match.group(1)
raise RuntimeError("Unable to find version string.")


setup(
name="aws-cloudformation-rpdk-python-plugin",
version=find_version("python", "rpdk", "python", "__init__.py"),
description=__doc__,
long_description=read("README.md"),
author="Amazon Web Services",
url="https://aws.amazon.com/cloudformation/",
# https://packaging.python.org/guides/packaging-namespace-packages/
packages=["rpdk.python"],
package_dir={"": "python"},
# package_data -> use MANIFEST.in instead
include_package_data=True,
zip_safe=True,
install_requires=["aws-cloudformation-rpdk>=0.1,<0.2", "pip>=10"],
entry_points={"rpdk.v1.languages": ["python = rpdk.python.codegen:PythonLanguagePlugin"]},
license="Apache License 2.0",
classifiers=(
"Development Status :: 2 - Pre-Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Natural Language :: English",
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Code Generators",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
),
keywords="Amazon Web Services AWS CloudFormation",
)