Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Parameterize Step Function Workflow Definition
const fs = require('fs');
const YAML = require('json-to-pretty-yaml');
const definition = JSON.parse(fs.readFileSync('definition.asl.json'));
const substitutions = [];
parameterizeStates(definition.States, substitutions);
console.log(substitutions)
const stateMachineResource = {
Type: 'AWS::Serverless::StateMachine',
Properties: {
Type: 'STANDARD',
DefinitionUri: '/path/to/file',
}
}
if(substitutions.length){
const definitionSubstitutions = {};
const policyStatement = {
Effect: 'Allow',
Action: [],
Resource: []
};
for(const substitution of substitutions){
definitionSubstitutions[substitutions.name] = substitution.value
if(substitution.permission){
policyStatement.Action.push(substitution.permission)
} else {
policyStatement.Resource.push(substitution.value)
}
}
stateMachineResource.DefinitionSubstitutions = definitionSubstitutions;
const policy = {
Version: '2012-10-17',
Statement: [policyStatement]
};
stateMachineResource.Policies = [policy];
}
fs.writeFileSync('substituted.asl.json', JSON.stringify(definition, null, 2))
fs.writeFileSync('template.yaml', YAML.stringify({ MyStateMachine: stateMachineResource }));
function parameterizeStates (states, substitutions){
for ([name, state] of Object.entries(states)) {
if (state.Resource?.startsWith('arn:')) {
const resourceData = getResourceNameAndPermissionFromArn(state.Resource);
if (!substitutions.some(s => s.name == resourceData.name)) {
substitutions.push({
name: resourceData.name,
value: parameterizeArn(state.Resource),
permission: resourceData.iamPermission
})
}
state.Resource = `\${${name}}`;
if (state.Parameters) {
parameterizeStateParameters(substitutions, name, state.Parameters);
}
} else if (state.Type == 'Parallel'){
for(const branch of state.Branches){
parameterizeStates(branch.States, substitutions);
}
} else if (state.Type == 'Map'){
parameterizeStates(state.Iterator.States, substitutions);
}
}
}
function parameterizeArn(resourceArn) {
const arn = resourceArn.split(':');
arn[1] = '{AWS::Partition}';
if (arn[3]) {
arn[3] = '{AWS::Region}';
}
if (arn[4]) {
arn[4] = '{AWS::AccountId}';
}
return arn.join(':');
}
function getResourceNameAndPermissionFromArn(resourceArn) {
const arn = resourceArn.split(':');
arn[1] = '{AWS::Partition}';
const name = `${arn[arn.length - 2].charAt(0).toUpperCase()}${arn[arn.length - 2].substring(1)}${arn[arn.length - 1].charAt(0).toUpperCase()}${arn[arn.length - 1].substring(1)}`
const iamPermission = `${arn[arn.length-2]}:${arn[arn.length-1]}`;
return {name, iamPermission};
}
function parameterizeStateParameters(substitutions, stateName, parameters) {
for ([parameterName, value] of Object.entries(parameters)) {
const substitutionValues = ['Queue', 'Bucket', 'Name', 'Id'];
if (substitutionValues.some(sv => parameterName.includes(sv)) && !parameterName.endsWith('.$')) {
let name = parameterName;
if (!substitutions.some(s => s.name == parameterName && s.value == value)) {
substitutions.push({
name: parameterName,
value: getParameterizedValue(value)
});
name = parameterName;
} else if (substitutions.some(s => s.name == parameterName && s.value != value)) {
name = `${stateName.replace(/ /g, '')}${parameterName}`;
substitutions.push({
name: name,
value: getParameterizedValue(value)
});
}
parameters[parameterName] = `\${${name}}`;
}
}
return parameters;
}
function isArn(value){
return value.indexOf("arn:") === 0 && value.split(":").length >= 6
}
function getParameterizedValue (value) {
if(isArn(value)){
return parameterizeArn(value);
}
return value;
}
{
"name": "nodejs",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"fs": "^0.0.1-security",
"json-to-pretty-yaml": "^1.2.2"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment