This template and associated Lambda function and custom resource add support to AWS CloudFormation for the AWS Service Catalog Service Actions resource type.
We create this custom resource to automate service catalog action functionality. This custom resource is implemented with a lambda to allow creation, update, and deletion of SC actions resources using cloudformation. It also provides the functionality to create, update and delete SC action associations to SC products.
Due to a few AWS issues we have decided to implement a bastardized custom resource. This bastard will only create and remove SC actions. Updates will be a noop.
The idea to work around the AWS issue is to use the following workflow:
- Initially add all SC actions (stop/start/restart) to all SC product versions
- Before a sceptre update make the CI remove all SC actions from all product versions
- Let sceptre update all CFN templates, this includes updating products and it this step will also re-add all SC actions to all SC product versions.
The cloudformation AWS::ServiceCatalog::CloudFormationProduct Property ProvisioningArtifactParameters is not working correctly. It duplicates the entire list of products on an update. This only happens after a service catalog action is associated with one of the Product version (i.e. ProvisioningArtifactIds). This is bad because you can wind up with a ton of product versions that do not match what's in the cloudformation template. We are wondering if this is a know issue at AWS or if are doing something wrong here?
For example:
- start with two items in ProvisioningArtifactParameters list
- associate an action to the product(s)
- make a change to any CloudFormationProduct parameters (i.e. Owner)
- redeploy product template
NOTE: two additional versions are deployed. now we wind up with 4 versions of the product in AWS even though there are still only two version in the CFN template
Attempt workaround to disassociate actions from product versions update the product template, then re-associate with product versions again failed to work because CloudFormationProduct always create new ProvisioningArtifact IDs even though the product versions never changed.
Result: Updates to product templates create a new ProductArtifactVersion ID which means that new product versions get the actions however previously provisioned products will lose the actions because it is associated with the old ProductArtifactVersion ID.
Example:
- SC restart action associated with prod ver1 (pa-1234)
- disassociate restart action from prod ver1 (pa-1234)
- Add prod ver2
Prod ver 1 gets a new ProvisioningArtifact ID (pa-45678) Prod ver 2 gets a new ProvisioningArtifact ID (pa-90909)
- associate action to product ver1 & ver2
Now the SC restart actionm is associated with ver1 (pa-45678) & ver2 (pa-90909). SC restart was removed from prod ver1 (pa-1234) in step #2 and step #4 never put it back. This results causes any products provisioned with old ver1 (pa-1234) to lose the SC action.
Contributions are welcome.
Install the following applications:
Run pipenv install --dev
to install both production and development
requirements, and pipenv shell
to activate the virtual environment. For more
information see the pipenv docs.
After activating the virtual environment, run pre-commit install
to install
the pre-commit git hook.
First, make any needed updates to the base requirements in Pipfile
, then use
pipenv
to regenerate both Pipfile.lock
and requirements.txt
.
$ pipenv update --dev
We use pipenv
to control versions in testing, but sam
relies on
requirements.txt
directly for building the lambda artifact, so we dynamically
generate requirements.txt
from Pipfile.lock
before building the artifact.
The file must be created in the CodeUri
directory specified in
template.yaml
.
$ pipenv requirements > sc-actions-provider/requirements.txt
Additionally, pre-commit
manages its own requirements.
$ pre-commit autoupdate
Use a Lambda-like docker container to build the Lambda artifact
$ sam build --use-container
Tests are defined in the tests
folder in this project, and dependencies are
managed with pipenv
. Install the development dependencies and run the tests
using coverage
.
$ pipenv run coverage run -m pytest tests/ -svv
Automated testing will upload coverage results to Coveralls.
Running integration tests requires docker
$ sam local invoke CreateFunction --event events/create.json
sam build
This requires the correct permissions to upload to bucket
bootstrap-awss3cloudformationbucket-19qromfd235z9
and
essentials-awss3lambdaartifactsbucket-x29ftznj6pqw
sam package --template-file .aws-sam/build/template.yaml \
--s3-bucket essentials-awss3lambdaartifactsbucket-x29ftznj6pqw \
--output-template-file .aws-sam/build/cfn-cr-sc-actions-provider.yaml
aws s3 cp .aws-sam/build/cfn-cr-sc-actions-provider.yaml s3://bootstrap-awss3cloudformationbucket-19qromfd235z9/cfn-cr-sc-actions-provider/master/
Create the following sceptre file
config/prod/cfn-cr-sc-actions-provider.yaml
template_path: "remote/cfn-cr-sc-actions-provider.yaml"
stack_name: "cfn-cr-sc-actions-provider"
stack_tags:
Department: "Platform"
Project: "Infrastructure"
OwnerEmail: "[email protected]"
hooks:
before_launch:
- !cmd "curl https://s3.amazonaws.com/bootstrap-awss3cloudformationbucket-19qromfd235z9/cfn-cr-sc-actions-provider/master/cfn-cr-sc-actions-provider.yaml --create-dirs -o templates/remote/cfn-cr-sc-actions-provider.yaml"
Install the lambda using sceptre:
sceptre --var "profile=my-profile" --var "region=us-east-1" launch prod/cfn-cr-sc-actions-provider.yaml
This shows how to use the custom resource provider to create a SC service action and associate the action to a SC product. The provider contains two custom resources, one to create the SC action and one to associate the action to a product.
Create the sceptre file
config/prod/sc-restart-instance-action.yaml:
template_path: sc-action.yaml
stack_name: sc-restart-instance-action
stack_tags:
Department: "Platform"
Project: "Infrastructure"
OwnerEmail: "[email protected]"
parameters:
# Action params
Name: "RestartEC2Instance"
SsmDocName: "AWS-RestartEC2Instance"
SsmDocVersion: "1"
AssumeRole: "arn:aws:iam::563295687221:role/SCEC2LaunchRole"
# Assocation params
ProductId: "prod-oxldqdwxwxtlg" # the SC product ID
Create the AWS cloudformation template
template/sc-action.yaml:
Description: Service Catalog Service Action
AWSTemplateFormatVersion: 2010-09-09
Parameters:
SsmDocName:
Type: String
Description: The name of the SSM document providing the action
AllowedValues:
- AWS-StopEC2Instance
- AWS-StartEC2Instance
- AWS-RestartEC2Instance
Default: "AWS-RestartEC2Instance"
SsmDocVersion:
Type: String
Description: The SSM document version
Default: "1"
Name:
Type: String
Description: The SC action name
AssumeRole:
Type: String
Description: The IAM role that SC actions will use
ProductId:
Type: String
Description: The SC product Id
Resources:
# Create the SC action
EC2InstanceAction:
Type: Custom::ScActionsProvider
Properties:
ServiceToken: !ImportValue
'Fn::Sub': '${AWS::Region}-cfn-cr-sc-actions-provider-CreateFunctionArn'
SsmDocName: !Ref SsmDocName
SsmDocVersion: !Ref SsmDocVersion
Name: !Ref Name
AssumeRole: !Ref AssumeRole
# Associate the SC action to a SC product
AssociateProductAction:
Type: Custom::ScActionsProvider
Properties:
ServiceToken: !ImportValue
'Fn::Sub': '${AWS::Region}-cfn-cr-sc-actions-provider-AssociateFunctionArn'
ServiceActionId: !Ref EC2InstanceAction
ProductId: !Ref ProductId
Outputs:
EC2InstanceActionId:
Value: !Ref EC2InstanceAction
Export:
Name: !Sub '${AWS::Region}-${AWS::StackName}-EC2InstanceActionId'
Deploy the SC action:
sceptre --var "profile=my-profile" --var "region=us-east-1" launch prod/sc-restart-instance-action.yaml
The code to handle creation updating and deletion of the OIDC Identity Provider couldn't fit into the 4096 characters allowed for embedded code without heavily obfuscating the code.