Python Poetry for Sceptre

on
Jun 20, 2022
in

We have been using Poetry for version pinning in Lambda Functions and Docker Images. This time we are looking at how to use Poetry for Sceptre, especially for adding custom Hooks and Resolvers. We assume you are already familiar with both Poetry and Scepter.

Hooks and Resolvers

Hooks allow you to perform actions at key moments in the Sceptre pipeline. Resolvers allow you to perform an action and use the returned value in stack parameters or user data.

Some Hooks and Resolvers are shipped with Sceptre, some are distributed by third parties, but you can also develop your own custom versions.

Registering Custom Hooks and Resolvers

The main difficulty is having Sceptre know about the existence of your custom Hooks and Resolvers. Normally this is done in the setup.py by setting entry_points, the equivalent for this in Poetry is to set tool.poetry.plugins

The main purpose of this blog post is for me to remember the following simple lines of configuration in the pyproject.toml file.

Hooks
[tool.poetry.plugins."sceptre.hooks"]
"<custom_hook_command_name>" = "<custom_hook_module_name>:CustomHookClassName"
Resolvers
[tool.poetry.plugins."sceptre.resolvers"]
"<custom_resolver_command_name>" = "<custom_resolver_module_name>:CustomResolverClassName"

Example Project

In this example, we add a custom Hook and a custom Resolver to our Sceptre setup. For this, the example custom Hook and Resolver from the Sceptre documentation are used (with the addition of logging).

File Structure
.  
├── pyproject.toml  
├── lib  
│   └── custom  
│       ├── __init__.py  
│       ├── hook.py  
│       └── resolver.py  
├── config  
│   ├── config.yaml  
│   └── test.yaml  
└── templates  
    └── test.cfn.yaml  

In the example project, we have a config and templates directory just like in any normal Sceptre project. We have added lib directory that holds the package for your custom modules, and a pyproject.toml that has the Sceptre dependency, adds our package and registers our custom Hook and Resolver. Needless to say, the __init__.py is an empty file.

Hook

lib/custom/hook.py:

from sceptre.hooks import Hook


class CustomHook(Hook):
    def __init__(self, *args, **kwargs):
        super(CustomHook, self).__init__(*args, **kwargs)

    def run(self):
        self.logger.error(f"CustomHook: {self.argument}")

Resolver

lib/custom/resolver.py:

from sceptre.resolvers import Resolver


class CustomResolver(Resolver):
    def __init__(self, argument, stack=None):
        super(CustomResolver, self).__init__(argument, stack)


    def setup(self):
        pass

    def resolve(self):
        self.logger.error(f"CustomResolver: {self.argument}")
        return self.argument

Poetry Config

pyproject.toml:

[tool.poetry]
name = "yourprojectname"
version = "0.0.1"
description = ""
authors = ["Your Name <you@example.com>"]
packages = [
    { include = "custom", from = "lib" },
]

[tool.poetry.plugins."sceptre.hooks"]
custom_hook = "custom.hook:CustomHook"

[tool.poetry.plugins."sceptre.resolvers"]
custom_resolver = "custom.resolver:CustomResolver"

[tool.poetry.dependencies]
python = "^3.10"
sceptre = "^3.1.0"

[tool.poetry.dev-dependencies]

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

Stack

config/config.yaml:

project_code: test
region: eu-central-1

config/test.yaml:

template_path: test.cfn.yaml
hooks:
  before_generate:
    - !custom_hook "Hallo, Wereld! before_generate"
  after_validate:
    - !custom_hook "Hallo, Wereld! after_validate"
parameters:
  TestParameter: !custom_resolver "Hello, World!"

All possible hook points, at time of writing, are described by the following regular expression: (before|after)_(generate|create|update|delete|launch|validate|create_change_set) Please consult the documentation for the most current options.

templates/test.cfn.yaml:

Parameters:
  TestParameter:
    Type: String

Resources:
  Topic:
    Type: AWS::SNS::Topic

(This is just a minimal CloudFormation template for testing the setup.)

Cloud Consultant with a passion for everything Cloud. Likes to automate all the things. Believes security is everyone's responsibility!
Share this article: Tweet this post / Post on LinkedIn