Ideas or code sources:
Hello, Jenkins geeks.
Once upon a time, I was writing a pipeline which had to read AWS CloudFormation template from YAML… But there was a problem – short CloudFormation functions…
And here is the short story how to do that.
Actually, this code was written at 2018:
- Original source code: github.com/jenkinsci/aws-sam-plugin
- With Jenkins related modifications from here: github.com/base2Services/ciinabox-pipelines
package com.example.package
import java.util.Arrays
import java.util.HashMap
import java.util.Map
import org.yaml.snakeyaml.constructor.AbstractConstruct
import org.yaml.snakeyaml.constructor.SafeConstructor
import org.yaml.snakeyaml.error.YAMLException
import org.yaml.snakeyaml.nodes.MappingNode
import org.yaml.snakeyaml.nodes.Node
import org.yaml.snakeyaml.nodes.ScalarNode
import org.yaml.snakeyaml.nodes.SequenceNode
import org.yaml.snakeyaml.nodes.Tag
import org.yaml.snakeyaml.LoaderOptions
import com.cloudbees.groovy.cps.NonCPS
/**
* Allows snakeyaml to parse YAML templates that contain short forms of
* CloudFormation intrinsic functions.
*
*/
public class IntrinsicsYamlConstructor extends SafeConstructor implements Serializable {
public IntrinsicsYamlConstructor() {
super(new LoaderOptions())
this.yamlConstructors.put(new Tag("!And"), new ConstructFunction(true, false))
this.yamlConstructors.put(new Tag("!Base64"), new ConstructFunction(true, false))
this.yamlConstructors.put(new Tag("!Cidr"), new ConstructFunction(true, false))
this.yamlConstructors.put(new Tag("!Condition"), new ConstructFunction(true, false))
this.yamlConstructors.put(new Tag("!Equals"), new ConstructFunction(true, false))
this.yamlConstructors.put(new Tag("!FindInMap"), new ConstructFunction(true, false))
this.yamlConstructors.put(new Tag("!GetAtt"), new ConstructFunction(true, true))
this.yamlConstructors.put(new Tag("!GetAZs"), new ConstructFunction(true, false))
this.yamlConstructors.put(new Tag("!If"), new ConstructFunction(true, false))
this.yamlConstructors.put(new Tag("!ImportValue"), new ConstructFunction(true, false))
this.yamlConstructors.put(new Tag("!Join"), new ConstructFunction(true, false))
this.yamlConstructors.put(new Tag("!Not"), new ConstructFunction(true, false))
this.yamlConstructors.put(new Tag("!Or"), new ConstructFunction(true, false))
this.yamlConstructors.put(new Tag("!Ref"), new ConstructFunction(false, false))
this.yamlConstructors.put(new Tag("!Select"), new ConstructFunction(true, false))
this.yamlConstructors.put(new Tag("!Split"), new ConstructFunction(true, false))
this.yamlConstructors.put(new Tag("!Sub"), new ConstructFunction(true, false))
}
private class ConstructFunction extends AbstractConstruct {
private final boolean attachFnPrefix
private final boolean forceSequenceValue
public ConstructFunction(boolean attachFnPrefix, boolean forceSequenceValue) {
this.attachFnPrefix = attachFnPrefix
this.forceSequenceValue = forceSequenceValue
}
@NonCPS
public Object construct(Node node) {
String key = node.getTag().getValue().substring(1)
String prefix = attachFnPrefix ? "Fn::" : ""
Map<String, Object> result = new HashMap<String, Object>()
result.put(prefix + key, constructIntrinsicValueObject(node))
return result
}
@NonCPS
protected Object constructIntrinsicValueObject(Node node) {
if (node instanceof ScalarNode) {
Object val = (String) constructScalar((ScalarNode) node)
if (forceSequenceValue) {
val = Arrays.asList(((String) val).split("\\."))
}
return val
} else if (node instanceof SequenceNode) {
return constructSequence((SequenceNode) node)
} else if (node instanceof MappingNode) {
return constructMapping((MappingNode) node)
}
throw new YAMLException("Intrisic function arguments cannot be parsed.")
}
}
}
And now how to use it:
import com.example.package.IntrinsicsYamlConstructor
String text = '''
AWSTemplateFormatVersion: "2010-09-09"
Mappings:
RegionMap:
us-east-1:
AMI: "ami-0ff8a91507f77f867"
us-west-1:
AMI: "ami-0bdb828fd58c52235"
us-west-2:
AMI: "ami-a0cfeed8"
eu-west-1:
AMI: "ami-047bb4163c506cd98"
sa-east-1:
AMI: "ami-07b14488da8ea02a0"
ap-southeast-1:
AMI: "ami-08569b978cc4dfa10"
ap-southeast-2:
AMI: "ami-09b42976632b27e9b"
ap-northeast-1:
AMI: "ami-06cd52961ce9f0d85"
Parameters:
EnvType:
Description: Environment type.
Default: test
Type: String
AllowedValues: [prod, dev, test]
ConstraintDescription: must specify prod, dev, or test.
Conditions:
CreateProdResources: !Equals [!Ref EnvType, prod]
CreateDevResources: !Equals [!Ref EnvType, "dev"]
Resources:
EC2Instance:
Type: "AWS::EC2::Instance"
Properties:
ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", AMI]
InstanceType: !If [CreateProdResources, c1.xlarge, !If [CreateDevResources, m1.large, m1.small]]
MountPoint:
Type: "AWS::EC2::VolumeAttachment"
Condition: CreateProdResources
Properties:
InstanceId: !Ref EC2Instance
VolumeId: !Ref NewVolume
Device: /dev/sdh
NewVolume:
Type: "AWS::EC2::Volume"
Condition: CreateProdResources
Properties:
Size: 100
AvailabilityZone: !GetAtt EC2Instance.AvailabilityZone
'''
Yaml yaml = new Yaml(new IntrinsicsYamlConstructor())
template = yaml.load(text)
CloudFormation template taken from AWS Sample templates
Happy Scripting!