Skip to content

Unicorn approvals #175

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 36 commits into from
Aug 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
cfe01fb
feat: add command to list the parameters in parameters store
sliedig Jun 26, 2025
4683f3e
refactor: renamed UnicornPropertiesNamespace to UnicornApprovalsNames…
sliedig Jun 26, 2025
08b7878
refactor: updated resolve:ssm references for UnicornPropertiesNamesp…
sliedig Jun 26, 2025
6026ae8
refactor: rename unicorn properties to unicorn approvals
sliedig Jul 6, 2025
25289c7
refactor: update event bus references from UnicornProperties to Unico…
sliedig Jul 6, 2025
1e66d49
refactor: update references from UnicornProperties to UnicornApproval…
sliedig Jul 7, 2025
0fbdd59
chore: upgrade actions/upload-artifact from v3 to v4 in GitHub workflow
sliedig Jul 7, 2025
2f100ee
fixed small issues
marcobuss Jul 7, 2025
75c6706
refactor: rename properties_service to approvals_service and update r…
sliedig Jul 12, 2025
c20cf8b
chore: update Approvals readme
sliedig Jul 18, 2025
173b11b
chore: updated contracts readme
sliedig Jul 18, 2025
7479130
chore: update web readme
sliedig Jul 18, 2025
f25e57e
chore: updated main readme
sliedig Jul 18, 2025
efd46a2
chore: upgrade actions/checkout and actions/github-script to v4 and v…
sliedig Jul 18, 2025
a29b768
chore: update README to reflect new build workflow and correct image tag
sliedig Jul 18, 2025
554cfaf
chore: add GitHub Actions workflow for building Python services
sliedig Jul 18, 2025
9616e0c
chore: add ruff installation step to GitHub Actions build workflow
sliedig Jul 18, 2025
e2601eb
chore: modify GitHub Actions build workflow to use uv run for build a…
sliedig Jul 18, 2025
80a4ead
chore: upgrade CodeQL action versions to v3 in GitHub workflows
sliedig Jul 18, 2025
cb4a9e7
chore: update cfn-lint installation step in GitHub Actions workflow t…
sliedig Jul 18, 2025
7d9884e
fix: allow cfn-lint command to continue on error in Makefile
sliedig Jul 18, 2025
10f789d
some name changes to approvals. Moved example events to other folder …
marcobuss Jul 18, 2025
dd6f199
fixing unint tests
marcobuss Jul 18, 2025
f383b3d
downgrade cfn-lint serverless-rules because of an issue with v0.3.3
marcobuss Jul 18, 2025
c08309f
Revert "downgrade cfn-lint serverless-rules because of an issue with …
sliedig Jul 21, 2025
c360375
chore: update architecture diagram
sliedig Jul 22, 2025
8bde59d
refactor: update event source and rule names to use 'unicorn.approvals'
sliedig Jul 22, 2025
812cede
refactor: renames ApprovalService to PublicationManagerService for ha…
sliedig Jul 24, 2025
21d33dc
fix: remove error suppression for cfn-lint command. Removed uv build-…
sliedig Jul 29, 2025
def059c
fix: update import path for request approval function to use Publicat…
sliedig Jul 29, 2025
7162170
chore: minor update to readme file
sliedig Jul 31, 2025
ab26a38
chore: updated approvals readme
sliedig Aug 5, 2025
81521e3
chore: updated bus name in test events
sliedig Aug 5, 2025
3a35caa
chore: updated readme and spelling mistakes
sliedig Aug 5, 2025
4f52d9d
chore: bumped dependencies
sliedig Aug 5, 2025
23e4cc3
fix: syncing templates
sliedig Aug 7, 2025
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
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ updates:
- package-ecosystem: "pip" # See documentation for possible values
directories:
- "unicorn_contracts" # Location of package manifests
- "unicorn_properties"
- "unicorn_approvals"
- "unicorn_web"
schedule:
interval: "weekly"
2 changes: 1 addition & 1 deletion .github/workflows/auto_assign.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ jobs:
add-reviews:
runs-on: ubuntu-latest
steps:
- uses: kentaro-m/auto-assign-action@v1.2.5
- uses: kentaro-m/auto-assign-action@v2.0.0
80 changes: 80 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: Build Python Services

on:
push:
branches: [develop, main]
paths:
- 'unicorn_contracts/**'
- 'unicorn_approvals/**'
- 'unicorn_web/**'
pull_request:
branches: [develop, main]
paths:
- 'unicorn_contracts/**'
- 'unicorn_approvals/**'
- 'unicorn_web/**'

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
service: [unicorn_contracts, unicorn_approvals, unicorn_web]
include:
- service: unicorn_contracts
display_name: Unicorn Contracts Service
- service: unicorn_approvals
display_name: Unicorn Approvals Service
- service: unicorn_web
display_name: Unicorn Web Service

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python 3.12
uses: actions/setup-python@v4
with:
python-version: 3.12

- name: Install uv
uses: astral-sh/setup-uv@v5
with:
version: 0.7.8

- name: Install AWS SAM CLI
uses: aws-actions/setup-sam@v2

- name: Install cfn-lint and plugins
run: |
pip install cfn-lint
pip install cfn-lint-serverless

- name: Install yq
run: |
sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
sudo chmod a+x /usr/local/bin/yq

- name: Initialize dependencies for ${{ matrix.display_name }}
run: make ci_init
working-directory: ./${{ matrix.service }}

- name: Build ${{ matrix.display_name }}
run: |
# Use uv run to ensure all commands run in the virtual environment
uv run make build
working-directory: ./${{ matrix.service }}
env:
DOCKER_OPTS: --use-container

- name: Upload build artifacts for ${{ matrix.display_name }}
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.service }}-build-artifacts
path: ${{ matrix.service }}/.aws-sam/
retention-days: 7

- name: Clean up ${{ matrix.display_name }}
run: uv run make clean
working-directory: ./${{ matrix.service }}
if: always()
8 changes: 4 additions & 4 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,18 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@2ca79b6fa8d3ec278944088b4aa5f46912db5d63 #v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}

# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
# - name: Autobuild
# uses: github/codeql-action/autobuild@2ca79b6fa8d3ec278944088b4aa5f46912db5d63 #v2
# uses: github/codeql-action/autobuild@v3

# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
Expand All @@ -49,4 +49,4 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@2ca79b6fa8d3ec278944088b4aa5f46912db5d63 #v2
uses: github/codeql-action/analyze@v3
4 changes: 2 additions & 2 deletions .github/workflows/label_pr_on_title.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: "Label PR based on title"
uses: actions/github-script@v6
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ needs.get_pr_details.outputs.prNumber }}
PR_TITLE: ${{ needs.get_pr_details.outputs.prTitle }}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/on_label_added.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ jobs:
issues: write
pull-requests: write
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
# Maintenance: Persist state per PR as an artifact to avoid spam on label add
- name: "Suggest split large Pull Request"
uses: actions/github-script@v6
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ needs.get_pr_details.outputs.prNumber }}
PR_ACTION: ${{ needs.get_pr_details.outputs.prAction }}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/on_merged_pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ jobs:
runs-on: ubuntu-latest
if: needs.get_pr_details.outputs.prIsMerged == 'true'
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: "Label PR related issue for release"
uses: actions/github-script@v6
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ needs.get_pr_details.outputs.prNumber }}
PR_BODY: ${{ needs.get_pr_details.outputs.prBody }}
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/on_opened_pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ jobs:
needs: get_pr_details
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: "Ensure related issue is present"
uses: actions/github-script@v6
uses: actions/github-script@v7
env:
PR_BODY: ${{ needs.get_pr_details.outputs.prBody }}
PR_NUMBER: ${{ needs.get_pr_details.outputs.prNumber }}
Expand All @@ -36,9 +36,9 @@ jobs:
needs: get_pr_details
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: "Ensure acknowledgement section is present"
uses: actions/github-script@v6
uses: actions/github-script@v7
env:
PR_BODY: ${{ needs.get_pr_details.outputs.prBody }}
PR_NUMBER: ${{ needs.get_pr_details.outputs.prNumber }}
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/record_pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: "Extract PR details"
uses: actions/github-script@v6
uses: actions/github-script@v7
with:
script: |
const script = require('.github/scripts/save_pr_details.js')
await script({github, context, core})
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: pr
path: pr.txt
4 changes: 2 additions & 2 deletions .github/workflows/reusable_export_pr_details.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ jobs:
prIsMerged: ${{ steps.prIsMerged.outputs.prIsMerged }}
steps:
- name: Checkout repository # in case caller workflow doesn't checkout thus failing with file not found
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: "Download previously saved PR"
uses: actions/github-script@v6
uses: actions/github-script@v7
env:
WORKFLOW_ID: ${{ inputs.record_pr_workflow_id }}
# For security, we only download artifacts tied to the successful PR recording workflow
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/reusable_unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- uses: actions/setup-python@v4
with: { python-version: 3.12 }
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/services_unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ on:
branches: [develop, main]
paths:
- 'unicorn_contracts/**'
- 'unicorn_properties/**'
- 'unicorn_approvals/**'
- 'unicorn_web/**'
pull_request:
branches: [develop, main]
paths:
- 'unicorn_contracts/**'
- 'unicorn_properties/**'
- 'unicorn_approvals/**'
- 'unicorn_web/**'

jobs:
Expand All @@ -18,10 +18,10 @@ jobs:
with:
service_directory: unicorn_contracts

unicorn_properties:
unicorn_approvals:
uses: ./.github/workflows/reusable_unit_tests.yml
with:
service_directory: unicorn_properties
service_directory: unicorn_approvals

unicorn_web:
uses: ./.github/workflows/reusable_unit_tests.yml
Expand Down
40 changes: 18 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,38 @@
<img src="./docs/workshop_logo.png" alt="AWS Serverless Developer Experience Workshop Reference Architecture" width="80%" />
[![Build & Test Workflow](https://github.com/aws-samples/aws-serverless-developer-experience-workshop-python/actions/workflows/build.yml/badge.svg)](https://github.com/aws-samples/aws-serverless-developer-experience-workshop-python/actions/workflows/build.yml)

# AWS Serverless Developer Experience workshop reference architecture (Python)

This repository contains the reference architecture for the AWS Serverless Developer Experience workshop.
<img src="./docs/workshop_logo.png" alt="AWS Serverless Developer Experience Workshop Reference Architecture" width="80%" />

This repository contains the Python reference architecture for the AWS Serverless Developer Experience workshop.

The AWS Serverless Developer Experience workshop provides you with an immersive experience as a serverless developer. The goal of this workshop is to provide you with hands-on experience building a serverless solution using the [**AWS Serverless Application Model (AWS SAM)**](https://aws.amazon.com/serverless/sam/) and **AWS SAM CLI**.
The AWS Serverless Developer Experience Workshop is a comprehensive, hands-on training program designed to equip developers with practical serverless development skills using the [**AWS Serverless Application Model (AWS SAM)**](https://aws.amazon.com/serverless/sam/) and **AWS SAM CLI**.

Along the way, you will learn about principals of distributed event-driven architectures, messaging patterns, orchestration, and observability and how to apply them in code. You will explore exciting open-source tools, the core features of Powertools for AWS Lambda, and simplified CI/CD deployments supported by AWS SAM Pipelines.
The workshop employs a practical, code-centric approach, emphasizing direct implementation and real-world scenario exploration to ensure you develop serverless development skills across several critical areas including distributed event-driven architectures, messaging patterns, orchestration, and observability. You will explore open-source tools, [Powertools for AWS](https://powertools.aws.dev/), and simplified CI/CD deployments with AWS SAM Pipelines. By the end, you will be familiar with serverless developer workflows, microservice composition using AWS SAM, serverless development best practices, and applied event-driven architectures.

At the end of this workshop, you will be familiar with Serverless developer workflows and microservice composition using AWS SAM, Serverless development best practices, and applied event-driven architectures.
The 6-8 hour workshop assumes your practical development skills in Python, TypeScript, Java, or .NET, and familiarity with [Amazon API Gateway](https://aws.amazon.com/apigateway/), [AWS Lambda](https://aws.amazon.com/lambda/), [Amazon EventBridge](https://aws.amazon.com/eventbridge/), [AWS Step Functions](https://aws.amazon.com/step-functions/), and [Amazon DynamoDB](https://aws.amazon.com/dynamodb/).

## Introducing the Unicorn Properties architecture

![AWS Serverless Developer Experience Workshop Reference Architecture](./docs/architecture.png)

Our use case is based on a real estate company called **Unicorn Properties**.

As a real estate agency, **Unicorn Properties** needs to manage the publishing of new property listings and sale contracts linked to individual properties, and provide a way for their customers to view approved property listings.

To support their needs, Unicorn Properties have adopted a serverless, event-driven approach to designing their architecture. This architecture is centred around two primary domains: **Contracts** (managed by the Contracts Service) and **Properties** (managed by the Web and Properties Services).

The **Unicorn Contracts** service (namespace: `Unicorn.Contracts`) is a simplified service that manages the contractual relationship between a seller of a property and Unicorn Properties. Contracts are drawn up that define the property for sale, the terms and conditions that Unicorn Properties sets, and how much it will cost the seller to engage the services of the agency.
Real estate company **Unicorn Properties** needs to manage publishing of new property listings and sale contracts linked to individual properties, and provide a way for customers to view approved listings. They adopted a serverless, event-driven architecture with two primary domains: **Contracts** (managed by the Contracts Service) and **Properties** (managed by the Web and Approvals Services).

The **Unicorn Web** (namespace: `Unicorn.Web`) manages the details of a property listing to be published on the Unicorn Properties website. Every property listing has an address, a sale price, a description of the property, and some photos that members of the public can look at to get them interested in purchasing the property. Only properties that have been approved for publication can be made visible to the public.
**Unicorn Contracts** (using the `Unicorn.Contracts` namespace) service manages contractual relationships between property sellers and Unicorn Approvals, defining properties for sale, terms, and engagement costs.

The **Unicorn Properties** service (namespace: `Unicorn.Properties`) approves a property listings. This service implements a workflow that checks for the existence of a contract, makes sure that the content and the images are safe to publish, and finally checks that the contract has been approved. We don’t want to publish a property until we have an approved contract!
**Unicorn Approvals** (using the `Unicorn.Approvals` namespace) service approves property listings by implementing a workflow that checks for contract existence, content and image safety, and contract approval before publishing.

Have a go at building this architecture yourself! Head over to the [Serverless Developer Experience Workshop](https://catalog.workshops.aws/serverless-developer-experience) for more details.
**Unicorn Web** (using the `Unicorn.Web` namespace) manages property listing details (address, sale price, description, photos) to be published on the website, with only approved listings visible to the public.

## Credits

Throughout this workshop we wanted to introduce you to some Open Source tools that can help you build serverless applications. This is not an exhaustive list, just a small selection of what we will be using in the workshop.
This workshop introduces you to some open-source tools that can help you build serverless applications. This is not an exhaustive list, but a small selection of what you will be using in the workshop.

Many thanks to all the AWS teams and community builders who have contributed to this list:

| Tools | Description | Download / Installation Instructions |
| --------------------- | ----------- | --------------------------------------- |
| cfn-lint | Validate AWS CloudFormation yaml/json templates against the AWS CloudFormation Resource Specification and additional checks. | https://github.com/aws-cloudformation/cfn-lint |
| cfn-lint-serverless | Compilation of rules to validate infrastructure-as-code templates against recommended practices for serverless applications. | https://github.com/awslabs/serverless-rules |
| @mhlabs/iam-policies-cli| CLI for generating AWS IAM policy documents or SAM policy templates based on the JSON definition used in the AWS Policy Generator. | https://github.com/mhlabs/iam-policies-cli |
| @mhlabs/evb-cli | Pattern generator and debugging tool for Amazon EventBridge | https://github.com/mhlabs/evb-cli |
| Tools | Description | Download / Installation Instructions |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
| cfn-lint | Validate AWS CloudFormation yaml/json templates against the AWS CloudFormation Resource Specification and additional checks. | https://github.com/aws-cloudformation/cfn-lint |
| cfn-lint-serverless | Compilation of rules to validate infrastructure-as-code templates against recommended practices for serverless applications. | https://github.com/awslabs/serverless-rules |
| @mhlabs/iam-policies-cli | CLI for generating AWS IAM policy documents or SAM policy templates based on the JSON definition used in the AWS Policy Generator. | https://github.com/mhlabs/iam-policies-cli |
| @mhlabs/evb-cli | Pattern generator and debugging tool for Amazon EventBridge | https://github.com/mhlabs/evb-cli |
Binary file modified docs/architecture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[project]
name = "aws-serverless-developer-experience-workshop-python"
version = "0.1.0"
description = "Add your description here"
requires-python = ">=3.12"
dependencies = []
File renamed without changes.
File renamed without changes.
15 changes: 15 additions & 0 deletions unicorn_approvals/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Developing Unicorn Approvals

![Properties Approval Architecture](https://static.us-east-1.prod.workshops.aws/public/f273b5fc-17cd-406b-9e63-1d331b00589d/static/images/architecture-approvals.png)

## Architecture overview

**Unicorn Approvals** uses an AWS Step Functions state machine to approve property listings for Unicorn Web. The workflow checks for contract information, description sentiment and safe images, and verifies the contract is approved before approving the listing. It publishes the result via the `PublicationEvaluationCompleted` event.

A Unicorn Properties agent initiates the workflow by requesting to approve a listing, generating a `PublicationApprovalRequested` event with property information. To decouple from the Contracts Service, the Approvals service maintains a local copy of contract status by consuming the ContractStatusChanged event.

The workflow checks the contract state. If the contract is in the WaitForContractApproval state, it updates the contract status for the property with its task token, triggering a DynamoDB stream event. The Property Approval Sync function handles these events and passes the task token back to the state machine based on the contract state.

If the workflow completes successfully, it emits a PublicationEvaluationCompleted event with an **approved** or **declined** evaluation result, which Unicorn Web listens to update its publication flag.

**Note:** Upon deleting the CloudFormation stack for this service, check if the `ApprovalStateMachine` StepFunction doesn't have any executions in `RUNNING` state. If there are, cancel those execution prior to deleting the CloudFormation stack.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Resources:
Properties:
Description: 'Event schemas for Unicorn Properties'
RegistryName:
Fn::Sub: "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}-${Stage}"
Fn::Sub: "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}-${Stage}"

EventRegistryPolicy:
Type: AWS::EventSchemas::RegistryPolicy
Expand Down Expand Up @@ -52,7 +52,7 @@ Resources:
RegistryName:
Fn::GetAtt: EventRegistry.RegistryName
SchemaName:
Fn::Sub: '{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}@PublicationEvaluationCompleted'
Fn::Sub: '{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}@PublicationEvaluationCompleted'
Description: 'The schema for when a property evaluation is completed'
Content:
Fn::Sub: |
Expand Down
Loading
Loading