theonestack / cfhighlander Goto Github PK
View Code? Open in Web Editor NEWCloudformation DSL and component library
License: MIT License
Cloudformation DSL and component library
License: MIT License
I would be useful to be able to include additional artifacts in the S3 publish for components.
Use Case: The api gateway component supports loading a api swagger file from S3 so it would be useful to just include the api swagger definition is the published templates instead of requiring an additional deployment step
maybe something like
PublishArtifact file: "my-swagger.yaml", key: "my-swagger-#{component_version}.yaml"
or
S3Artifact file: "my-swagger.yaml", key: "my-swagger-#{component_version}.yaml"
The key could be optional
What i would like to be able to do is extend a template and find and replace where a Ref
is defined with the extended template and replace it with a FnImportValue
.
Example cfhighlander template
CfhighlanderTemplate do
Name 'service'
Parameters do
ComponentParam 'ServiceTag'
end
Extends 'ecs-service@latest' do
replace name: 'EcsCluster', value: FnImportValue(FnSub("${EnvironmentName}-ECSCluster"))
replace name: 'VPCId', value: FnImportValue(FnSub("${EnvironmentName}-VPC"))
replace name: 'Listener', value: FnImportValue(FnSub("${EnvironmentName}-httpsListener"))
parameter name: 'ServiceTag', value: Ref('ServiceTag')
end
end
When cfhighlander adds the CfTemplateUrl it can generate an invalid cloudformation output parameter name when a subcomponent stack is inlined. The name of the output variable needs to be correctly escaped when it get inlined
This feature is kinda similar to LambdaFunctions in that it allows custom code to be package and deployed with the generated cloudformation but with support for building and publishing container images.
Use Case: Creating more application specific components and services. For example a component for deploying a running ProxySQL Rather than relying on public container images have the component include all the dependencies (Dockerfile, config etc)
Add a new operation to the highlander dsl to build and publish a container image
Something like
ContainerImage image_name: proxysql, dockerfile: 'Dockerfile', ecr_repo: '1234567890.dkr.ecr.eu-central-1.amazonaws.com'
During the compile stage highlander would run a docker build and docker push to ecr and would create the ecr repo if it didn't exist with options for setting the permissions for the ecr_repo
Right now when we have component like
CfhighlanderTemplate do
Parameters do
ComponentParam 'RegionCode', 'eu', allowedValues: ['eu', 'us', 'ap']
end
Component template: 'a', name: 'a1', render: Inline do
parameter name: 'RegionCode', value: Ref('RegionCode')
end
Component template: 'a', name: 'a2', dependson: ['a1'] do
parameter name: 'RegionCode', value: Ref('RegionCode')
end
end
We end up with an invalid DependsOn
referencing the name of the inlined stack which has been removed.
Ideally it would be good to be able to reference a specific resource within the inlined stack maybe we could support for following syntax for dependson
Component template: 'a', name: 'a2', dependson: ['a1.SnsTopic'] do
parameter name: 'RegionCode', value: Ref('RegionCode')
end
and the resulting DependsOn
would just contain the reference to the inlined resource
@Guslington @toshke thoughts????
Trying to deploy cloudformation update to development stack using jenkins, from a master branch. I can eventually get it to build after a few runs but this error does appear quite often.
/usr/local/bundle/gems/aws-sdk-core-3.21.0/lib/seahorse/client/plugins/raise_response_errors.rb:15:in `call': Rate exceeded (Aws::CloudFormation::Errors::Throttling)
from /usr/local/bundle/gems/aws-sdk-core-3.21.0/lib/aws-sdk-core/plugins/jsonvalue_converter.rb:20:in `call'
from /usr/local/bundle/gems/aws-sdk-core-3.21.0/lib/aws-sdk-core/plugins/idempotency_token.rb:17:in `call'
from /usr/local/bundle/gems/aws-sdk-core-3.21.0/lib/aws-sdk-core/plugins/param_converter.rb:24:in `call'
from /usr/local/bundle/gems/aws-sdk-core-3.21.0/lib/aws-sdk-core/plugins/response_paging.rb:10:in `call'
from /usr/local/bundle/gems/aws-sdk-core-3.21.0/lib/seahorse/client/plugins/response_target.rb:23:in `call'
from /usr/local/bundle/gems/aws-sdk-core-3.21.0/lib/seahorse/client/request.rb:70:in `send_request'
from /usr/local/bundle/gems/aws-sdk-cloudformation-1.4.0/lib/aws-sdk-cloudformation/client.rb:3504:in `validate_template'
from /usr/local/bundle/gems/highlander-0.1.2/lib/highlander.validator.rb:40:in `validate_local'
from /usr/local/bundle/gems/highlander-0.1.2/lib/highlander.validator.rb:29:in `block in validate'
from /usr/local/bundle/gems/highlander-0.1.2/lib/highlander.validator.rb:17:in `each'
from /usr/local/bundle/gems/highlander-0.1.2/lib/highlander.validator.rb:17:in `validate'
from /usr/local/bundle/gems/highlander-0.1.2/bin/highlander.rb:84:in `cfcompile'
from /usr/local/bundle/gems/highlander-0.1.2/bin/highlander.rb:105:in `cfpublish'
from /usr/local/bundle/gems/thor-0.20.0/lib/thor/command.rb:27:in `run'
from /usr/local/bundle/gems/thor-0.20.0/lib/thor/invocation.rb:126:in `invoke_command'
from /usr/local/bundle/gems/thor-0.20.0/lib/thor.rb:387:in `dispatch'
from /usr/local/bundle/gems/thor-0.20.0/lib/thor/base.rb:466:in `start'
from /usr/local/bundle/gems/highlander-0.1.2/bin/highlander.rb:158:in `<top (required)>'
from /usr/local/bundle/gems/highlander-0.1.2/bin/highlander:3:in `require_relative'
from /usr/local/bundle/gems/highlander-0.1.2/bin/highlander:3:in `<top (required)>'
from /usr/local/bundle/bin/highlander:23:in `load'
from /usr/local/bundle/bin/highlander:23:in `<main>'
Right now conditional components only works for nested stacks but Ideally you would be able to define something as conditional and if it gets inlined the condition gets added to each resource that gets inlined.
Currently it correct adds the Parameter and Condition definitions in the inlined CloudFormation
Considering the following here component
CfhighlanderTemplate do
Parameters do
ComponentParam 'RegionCode', 'eu', allowedValues: ['eu', 'us', 'ap']
end
Component template: 'a', name: 'a1', render: Inline, conditional: true, enabled: false do
parameter name: 'RegionCode', value: Ref('RegionCode')
end
Component template: 'a', name: 'a2', conditional: true, enabled: false do
parameter name: 'RegionCode', value: Ref('RegionCode')
end
end
and the cfndsl for component a
CloudFormation do
Condition(:EURegion, FnEquals(Ref(:RegionCode), 'eu'))
SNS_Topic(:SnsTopic) do
DisplayName FnSub('topic-${RegionCode}')
end
SNS_Topic(:SnsTopic2) do
Condition(:EURegion)
DisplayName FnSub('topic2-${RegionCode}')
end
end
The compiled template for b should be
---
AWSTemplateFormatVersion: '2010-09-09'
Conditions:
Enablea1:
Fn::Equals:
- Ref: Enablea1
- 'true'
Enablea2:
Fn::Equals:
- Ref: Enablea2
- 'true'
EURegion:
Fn::And:
- Condtion: Enablea1
- Fn::Equals:
- Ref: RegionCode
- eu
Resources:
a2:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: "./a2.compiled.yaml"
Parameters:
RegionCode:
Ref: RegionCode
Condition: Enablea2
SnsTopic:
Type: AWS::SNS::Topic
Properties:
DisplayName:
Fn::Sub: topic-${RegionCode}
Condition: Enablea1
SnsTopic2:
Type: AWS::SNS::Topic
Properties:
DisplayName:
Fn::Sub: topic2-${RegionCode}
Condition: EURegion
Parameters:
RegionCode:
Type: String
Default: eu
NoEcho: false
AllowedValues:
- eu
- us
- ap
Enablea1:
Type: String
Default: 'false'
NoEcho: false
AllowedValues:
- 'true'
- 'false'
Enablea2:
Type: String
Default: 'false'
NoEcho: false
AllowedValues:
- 'true'
- 'false'
Description: b@latest - vlatest
Outputs:
CfTemplateUrl:
Value: "/b.compiled.yaml"
CfTemplateVersion:
Value: latest
a1CfTemplateVersion:
Value: latest
Add support for outer component parameter wiring to allow for simpler components with no external component dependencies.
Support automatic output parameter wiring based output parameter discover removing the need to explicitly define a component parameter as an output parameter.
Also we should be able override this behaviour with something like
Component template: 'ecs', name: 'myecscluster' do
parameter name: 'SecurityGroupBastion' value: 'bation2.SGRandomName'
end
We should allow for following syntax
CfhighlanderTemplate do
CloudFormation do
# aws Cfndsl resource definition goes here
end
end
Couple of useful SO topics to assist in implementation
https://stackoverflow.com/questions/1675053/printing-the-source-code-of-a-ruby-block
The issue is when you use the same component multiple times that is referencing an upstream component.
For example take a project that manages 2 s3-websites that includes the s3-cloudfront
component twice.
CfhighlanderTemplate do
Name 's3-website'
Description "s3-website"
Component name: 's3cloudfront1', template: 's3-cloudfront'
Component name: 's3cloudfront2', template: 's3-cloudfront'
end
The s3-cloudfront
component inlines the cloudfront
component with the name hardcoded to cloudfront
. https://github.com/theonestack/hl-component-s3-cloudfront/blob/master/s3-cloudfront.cfhighlander.rb#L11
There for the last cloudfront config will overwrite them all causing each s3-website to have the same configuration.
I think the way to resolve this would be to prefix the sub component names with the component name in the project. The same way we handled the config files
s3cloudfront1-cloudfront
s3cloudfront2-cloudfront
If my domain name is prefixed with the same same as one of my components, that value is turned into a Fn::GetAtt from the output of the component.
DnsDomain:
Fn::GetAtt:
- sftp
- Outputs.mydomain
My work around for the moment is to apply that parameter at runtime. Do we need to allow for the functionality if we have the cfout()
function to generate the Fn::GetAtt?
CfhighlanderTemplate do
Name 'my-sftp'
Description "my-sftp"
Component name: 'vpc', template: 'vpc' do
parameter name: 'DnsDomain', value: "sftp.mydomain.com"
parameter name: 'NetworkPrefix', value: '10'
parameter name: 'StackMask', value: '16'
end
Component name: 's3', template: 's3'
Component name: 'sftp', template: 'sftpDnsDomain' do
parameter name: 'VpcId', value: cfout('vpc.VPCId')
parameter name: 'SubnetIds', value: cfout('vpc.ComputeSubnets')
parameter name: 'DnsDomain', value: "sftp.mydomain.com"
parameter name: 'VPCCidr', value: cfout('vpc.VPCCidr')
end
Component name: 'loadbalancer', template: '[email protected]' do
parameter name: 'VpcId', value: cfout('vpc.VPCId')
parameter name: 'SubnetIds', value: cfout('vpc.PublicSubnets')
parameter name: 'DnsDomain', value: "sftp.mydomain.com"
parameter name: 'VpcEndpointIPs', value: cfout('sftp.VpcEndpointIPs')
end
end
Validate template /Users/gus/src/ss/ss-sftp-cloudformation/out/yaml/ss-sftp.compiled.yaml locally
Traceback (most recent call last):
21: from /Users/gus/.rbenv/versions/2.5.3/bin/cfcompile:23:in `<main>'
20: from /Users/gus/.rbenv/versions/2.5.3/bin/cfcompile:23:in `load'
19: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.9.0/bin/cfcompile:4:in `<top (required)>'
18: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.9.0/bin/cfcompile:4:in `require_relative'
17: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.9.0/bin/cfhighlander.rb:301:in `<top (required)>'
16: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/thor-0.20.3/lib/thor/base.rb:466:in `start'
15: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/thor-0.20.3/lib/thor.rb:387:in `dispatch'
14: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/thor-0.20.3/lib/thor/invocation.rb:126:in `invoke_command'
13: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/thor-0.20.3/lib/thor/command.rb:27:in `run'
12: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.9.0/bin/cfhighlander.rb:113:in `cfcompile'
11: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.9.0/lib/cfhighlander.validator.rb:17:in `validate'
10: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.9.0/lib/cfhighlander.validator.rb:17:in `each'
9: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.9.0/lib/cfhighlander.validator.rb:29:in `block in validate'
8: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.9.0/lib/cfhighlander.validator.rb:40:in `validate_local'
7: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/aws-sdk-cloudformation-1.19.0/lib/aws-sdk-cloudformation/client.rb:4203:in `validate_template'
6: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/aws-sdk-core-3.51.0/lib/seahorse/client/request.rb:70:in `send_request'
5: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/aws-sdk-core-3.51.0/lib/seahorse/client/plugins/response_target.rb:23:in `call'
4: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/aws-sdk-core-3.51.0/lib/aws-sdk-core/plugins/response_paging.rb:10:in `call'
3: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/aws-sdk-core-3.51.0/lib/aws-sdk-core/plugins/param_converter.rb:24:in `call'
2: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/aws-sdk-core-3.51.0/lib/aws-sdk-core/plugins/idempotency_token.rb:17:in `call'
1: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/aws-sdk-core-3.51.0/lib/aws-sdk-core/plugins/jsonvalue_converter.rb:20:in `call'
/Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/aws-sdk-core-3.51.0/lib/seahorse/client/plugins/raise_response_errors.rb:15:in `call': Circular dependency between resources: [loadbalancer, vpc, sftp] (Aws::CloudFormation::Errors::ValidationError)
Configure aws sdk for exponential backoff by default, when validating and uploading templates.
If there is componentname.config.yaml
configuration file bystanding outer component template, this configuration should be automatically exported to componentname
component, if such component exists, rather than having to indent such configuration under components / componentname / config
key
Cfhighlander templates should not necessarily rely on cfndsl templates to produce cloudformation templates. Raw YAML and JSON templates should be allowed as well with little help of cfn2dsl
Thinking about creating test cases with config to run in travis-ci builds. For example the sqs component has some default config to create some demo queues, where as that should be handled with a tests case defined in sqs.tests.yaml
. Then maybe a seperate command cftest
or cfcompile --config sqs.tests.yaml
.
When a sub component inlines another component all templates compiled are validated which can sometimes lead to validation errors of templates that are not required as they've been inlined.
for example the fargate-v2 component inlines the ecs-task component. If add a custom parameter to the fargate-v2 component that's used in the task definition, validation for the ecs-task template fails because it doesn't have the parameter.
However there is no point in validating the ecs-task template as it's been inlined into the fargate template.
I think we should delete all inlined templates before validation
If two components, say A , and B have following defined
A - has an output defined as
# a.cfndsl.rb
Output(:InstanceId) { Value(Ref(:Instance)) }
B - has an input defined as
# b.cfhighlander.rb
Parameters { ComponentParam :InstanceId,'', type: 'String }
If C defines both A and B, output-input auto-wiring does not work. However if :InstanceId
is replaced with 'InstanceId'
this works.
Security group and other helpers are now in vpc component where they belong. This functionality should be removed from core module.
cfhighlander version: 0.12.5
Project files
.
โโโ README.md
โโโ extend-error.cfhighlander.rb
โโโ extend-error.config.yaml
โโโ mysql
โโโ mysql.cfhighlander.rb
โโโ mysql.cfndsl.rb
โโโ mysql.config.yaml
extend-error.cfhighlander.rb
CfhighlanderTemplate do
Name 'extend-error'
Description "extend-error"
Component name: 'mysql', template: 'mysql'
end
mysql/mysql.cfhighlander.rb
CfhighlanderTemplate do
Name 'mysql'
Extends '[email protected]'
end
StackTrace
Config for mysql written to /Users/gus/src/testbed/hl/extend-error/out/config/mysql.config.yaml
cfndsl template for mysql written to /Users/gus/src/testbed/hl/extend-error/out/cfndsl/mysql.compiled.cfndsl.rb
Traceback (most recent call last):
28: from /Users/gus/.rbenv/versions/2.7.2/bin/cfcompile:23:in `<main>'
27: from /Users/gus/.rbenv/versions/2.7.2/bin/cfcompile:23:in `load'
26: from /Users/gus/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/cfhighlander-0.12.4/bin/cfcompile:4:in `<top (required)>'
25: from /Users/gus/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/cfhighlander-0.12.4/bin/cfcompile:4:in `require_relative'
24: from /Users/gus/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/cfhighlander-0.12.4/bin/cfhighlander.rb:305:in `<top (required)>'
23: from /Users/gus/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/thor-0.20.3/lib/thor/base.rb:466:in `start'
22: from /Users/gus/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/thor-0.20.3/lib/thor.rb:387:in `dispatch'
21: from /Users/gus/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/thor-0.20.3/lib/thor/invocation.rb:126:in `invoke_command'
20: from /Users/gus/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/thor-0.20.3/lib/thor/command.rb:27:in `run'
19: from /Users/gus/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/cfhighlander-0.12.4/bin/cfhighlander.rb:96:in `cfcompile'
18: from /Users/gus/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/cfhighlander-0.12.4/bin/cfhighlander.rb:292:in `build_component'
17: from /Users/gus/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/cfhighlander-0.12.4/lib/cfhighlander.model.component.rb:156:in `load'
16: from /Users/gus/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/cfhighlander-0.12.4/lib/cfhighlander.model.component.rb:234:in `evaluateHiglanderTemplate'
15: from /Users/gus/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/cfhighlander-0.12.4/lib/cfhighlander.model.component.rb:234:in `eval'
14: from /Users/gus/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/cfhighlander-0.12.4/lib/cfhighlander.model.component.rb:248:in `evaluateHiglanderTemplate'
13: from /Users/gus/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/cfhighlander-0.12.4/lib/cfhighlander.dsl.template.rb:527:in `CfhighlanderTemplate'
12: from /Users/gus/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/cfhighlander-0.12.4/lib/cfhighlander.dsl.template.rb:238:in `loadComponents'
11: from /Users/gus/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/cfhighlander-0.12.4/lib/cfhighlander.dsl.template.rb:238:in `each'
10: from /Users/gus/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/cfhighlander-0.12.4/lib/cfhighlander.dsl.template.rb:269:in `block in loadComponents'
9: from /Users/gus/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/cfhighlander-0.12.4/lib/cfhighlander.model.component.rb:255:in `eval_cfndsl'
8: from /Users/gus/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/cfhighlander-0.12.4/lib/cfhighlander.compiler.rb:141:in `evaluateCloudFormation'
7: from /Users/gus/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/cfndsl-1.3.1/lib/cfndsl/cloudformation.rb:58:in `eval_file_with_extras'
6: from /Users/gus/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/cfndsl-1.3.1/lib/cfndsl/cloudformation.rb:58:in `eval'
5: from /Users/gus/src/testbed/hl/extend-error/out/cfndsl/mysql.compiled.cfndsl.rb:30:in `eval_file_with_extras'
4: from /Users/gus/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/cfndsl-1.3.1/lib/cfndsl/cloudformation.rb:114:in `CloudFormation'
3: from /Users/gus/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/cfndsl-1.3.1/lib/cfndsl/jsonable.rb:164:in `declare'
2: from /Users/gus/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/cfndsl-1.3.1/lib/cfndsl/jsonable.rb:164:in `instance_eval'
1: from /Users/gus/src/testbed/hl/extend-error/out/cfndsl/mysql.compiled.cfndsl.rb:57:in `block in eval_file_with_extras'
/Users/gus/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/cfndsl-1.3.1/lib/cfndsl/module.rb:79:in `block (4 levels) in dsl_content_object': wrong number of arguments (given 2, expected 1) as Mapping(EnvironmentType) already exists (ArgumentError)
This is my example
I have my base component which is cloud front
https://github.com/theonestack/hl-component-cloudfront
I then have another component that includes the cloudfront component and renders inline.
This is a s3 bucket and bucket policy
https://github.com/theonestack/hl-component-s3-cloudfront
i then have my project that specifies the s3-cloudfront component called s3-website
CfhighlanderTemplate do
Name 's3-website'
Description "s3-website"
Component name: 's3cloudfront', template: 's3-cloudfront', render: Inline
end
I then want add config to the cloudfront component, so i create a cloudfront.config.yaml
with my cloudfront config. However when i compile the config is not picked up. The compiled cloudfront config only contains config set by the cloudfront
and s3-cloudfront
components
I have tried specifying
component:
cloudfront:
proving the config in a 's3-cloudfront.config.yaml' file
The only way i can get this to work is by vendoring the s3-cloudfront
component in my s3-website
project and proving the cloudfront config with that vendored component.
@toshke @aaronwalker any ideas why it might not be applying the config?
Component
dsl statement should accept two new named parameters
conditional: boolean
enabled: boolean
If conditional is set to true
, cfhighlander should generate condition Enable$componentname
and appropriate parameter. enabled
just defines default value if conditional
is enabled.
Prototype component can be found at
https://github.com/toshke/cfhighlander-conditionals-demo
When doing cfhighlander cfpublish template[@version]
command output should contain url for launching stack within same region where distribution bucket is, allowing an easy flow from cli to aws console
In case of cfndsl compilation failure, it may be worthwhile adding begin / rescue
block, with informative message that possible case of compilation failure are outdated cfndsl resources defintions, and suggestion to user that this can be resolved by executing cfndsl -u
Cfhighlander should support building components as extensions of other components. Config, highlander template and cloudformation template are inherited from parent component. Child component should be able to override config and parameters. Cfndsl template of child component should be appended to the end of cfndsl template belonging to inherited component. This will results in ability to override cloudformation resources in child template by giving them same resource names.
I have a use case where I need to configure the components for CloudFront and ACM based on their environment type. This can be achieved for each component individually by vendoring tem and then placing a Condition
statement inside their templates. However, this is not a dynamic approach as it requires hard-coding the condition.
What would be nice is to have a dynamic Condition that is passed down to the desired templates. This can then be referenced in a component's config.
An example of this may look like:
is_prod = Condition('IsProd', FnEquals(Ref('EnvironmentType'), 'production'))
Component name: 'acm', template: 'acm' do
parameter name: 'DnsDomain', value: 'hello.com'
condition name: 'IsProd', value: is_prod
end
As the default branch is now main cfhighlander needs to default to both master and main to handle this change.
@toshke the removal of compiler.process_lambdas = false
from eval_cfndsl()
has lead to lambdas being built during the build component phase before the flag is set causing highline to always prompt with a (y/n) option.
A change to the subnet helper method seems to cause intermittent issues with components requiring an earlier version of VPC. See stack trace below.
Even though we are specifying component [email protected], bastion component has a dependson [email protected]. The addition of tags to the helper methods seems to throw an error.
Component name:'vpc', template: '[email protected]' do
parameter name: 'DnsDomain', value: dns_domain
end
Component name:'bastion', template: 'bastion' do
parameter name: 'DnsDomain', value: dns_domain
parameter name: 'InstanceType', value: 't2.micro'
end
VPC helper method changes: theonestack/hl-component-vpc#18
Traceback (most recent call last):
24: from /Users/jh/.rbenv/versions/2.5.3/bin/cfcompile:23:in `<main>'
23: from /Users/jh/.rbenv/versions/2.5.3/bin/cfcompile:23:in `load'
22: from /Users/jh/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.8.2/bin/cfcompile:4:in `<top (required)>'
21: from /Users/jh/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.8.2/bin/cfcompile:4:in `require_relative'
20: from /Users/jh/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.8.2/bin/cfhighlander.rb:298:in `<top (required)>'
19: from /Users/jh/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/thor-0.20.3/lib/thor/base.rb:466:in `start'
18: from /Users/jh/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/thor-0.20.3/lib/thor.rb:387:in `dispatch'
17: from /Users/jh/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/thor-0.20.3/lib/thor/invocation.rb:126:in `invoke_command'
16: from /Users/jh/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/thor-0.20.3/lib/thor/command.rb:27:in `run'
15: from /Users/jh/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.8.2/bin/cfhighlander.rb:107:in `cfcompile'
14: from /Users/jh/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.8.2/lib/cfhighlander.compiler.rb:151:in `compileCloudFormation'
13: from /Users/jh/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.8.2/lib/cfhighlander.compiler.rb:151:in `each'
12: from /Users/jh/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.8.2/lib/cfhighlander.compiler.rb:152:in `block in compileCloudFormation'
11: from /Users/jh/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.8.2/lib/cfhighlander.compiler.rb:147:in `compileCloudFormation'
10: from /Users/jh/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.8.2/lib/cfhighlander.compiler.rb:123:in `evaluateCloudFormation'
9: from /Users/jh/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfndsl-0.16.12/lib/cfndsl.rb:87:in `eval_file_with_extras'
8: from /Users/jh/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfndsl-0.16.12/lib/cfndsl.rb:87:in `eval'
7: from /Users/jh/clients/gcs-cloudformation/out/cfndsl/vpc.compiled.cfndsl.rb:14:in `eval_file_with_extras'
6: from /Users/jh/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfndsl-0.16.12/lib/cfndsl.rb:93:in `CloudFormation'
5: from /Users/jh/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfndsl-0.16.12/lib/cfndsl/jsonable.rb:204:in `declare'
4: from /Users/jh/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfndsl-0.16.12/lib/cfndsl/jsonable.rb:204:in `instance_eval'
3: from /Users/jh/clients/gcs-cloudformation/out/cfndsl/vpc.compiled.cfndsl.rb:179:in `block in eval_file_with_extras'
2: from /Users/jh/clients/gcs-cloudformation/out/cfndsl/vpc.compiled.cfndsl.rb:179:in `each'
1: from /Users/jh/clients/gcs-cloudformation/out/cfndsl/vpc.compiled.cfndsl.rb:184:in `block (2 levels) in eval_file_with_extras'
/Users/jh/.cfhighlander/components/vpc/1.2.0/ext/cfndsl/az.rb:104:in `az_create_subnets': wrong number of arguments (given 6, expected 2..5) (ArgumentError) ```
If I try and use component_name inside a highlander component and when rendering the component directly (not nested) I get the following error
$ ../cfhighlander/bin/cfhighlander cfcompile ecs-services --validate
Trying to load ecs-services@latest from /Users/aaronwalker/Workspaces/theonestack/hl-component-ecs-services/ecs-services ...
Trying to load ecs-services@latest from /Users/aaronwalker/Workspaces/theonestack/hl-component-ecs-services ...
Processing higlander component ecs-services
Location:/Users/aaronwalker/Workspaces/theonestack/hl-component-ecs-services/ecs-services.highlander.rb
Config:{"log_retention"=>7, "cpu"=>1024, "memory"=>256, "task_definition"=>{"nginx"=>{"image"=>"nginx", "ports"=>[80]}}, "targetgroup"=>{"name"=>"DefaultHTTP", "container"=>"nginx", "port"=>80}}
/Users/aaronwalker/Workspaces/theonestack/cfhighlander/lib/highlander.dsl.base.rb:18:in `method_missing': #<Highlander::Dsl::Template:0x007fbb72de12c0>Unknown method or variable component_name in Highlander template (StandardError)
from /Users/aaronwalker/Workspaces/theonestack/cfhighlander/lib/highlander.factory.rb:106:in `block in load'
from /Users/aaronwalker/Workspaces/theonestack/cfhighlander/lib/highlander.dsl.rb:419:in `instance_eval'
from /Users/aaronwalker/Workspaces/theonestack/cfhighlander/lib/highlander.dsl.rb:419:in `HighlanderComponent'
from /Users/aaronwalker/Workspaces/theonestack/cfhighlander/lib/highlander.factory.rb:102:in `load'
from /Users/aaronwalker/Workspaces/theonestack/cfhighlander/lib/highlander.factory.rb:102:in `eval'
from /Users/aaronwalker/Workspaces/theonestack/cfhighlander/lib/highlander.factory.rb:102:in `load'
from /Users/aaronwalker/Workspaces/theonestack/cfhighlander/bin/highlander.rb:154:in `build_component'
from /Users/aaronwalker/Workspaces/theonestack/cfhighlander/bin/highlander.rb:75:in `cfcompile'
from /Users/aaronwalker/.chefdk/gem/ruby/2.4.0/gems/thor-0.20.0/lib/thor/command.rb:27:in `run'
from /Users/aaronwalker/.chefdk/gem/ruby/2.4.0/gems/thor-0.20.0/lib/thor/invocation.rb:126:in `invoke_command'
from /Users/aaronwalker/.chefdk/gem/ruby/2.4.0/gems/thor-0.20.0/lib/thor.rb:387:in `dispatch'
from /Users/aaronwalker/.chefdk/gem/ruby/2.4.0/gems/thor-0.20.0/lib/thor/base.rb:466:in `start'
from /Users/aaronwalker/Workspaces/theonestack/cfhighlander/bin/highlander.rb:158:in `<top (required)>'
from ../cfhighlander/bin/cfhighlander:3:in `require_relative'
from ../cfhighlander/bin/cfhighlander:3:in `<main>'
Traceback (most recent call last):
18: from /Users/gus/.rbenv/versions/2.5.3/bin/cfcompile:23:in `<main>'
17: from /Users/gus/.rbenv/versions/2.5.3/bin/cfcompile:23:in `load'
16: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.10.1/bin/cfcompile:4:in `<top (required)>'
15: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.10.1/bin/cfcompile:4:in `require_relative'
14: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.10.1/bin/cfhighlander.rb:301:in `<top (required)>'
13: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/thor-0.20.3/lib/thor/base.rb:466:in `start'
12: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/thor-0.20.3/lib/thor.rb:387:in `dispatch'
11: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/thor-0.20.3/lib/thor/invocation.rb:126:in `invoke_command'
10: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/thor-0.20.3/lib/thor/command.rb:27:in `run'
9: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.10.1/bin/cfhighlander.rb:110:in `cfcompile'
8: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.10.1/lib/cfhighlander.compiler.rb:163:in `compileCloudFormation'
7: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.10.1/lib/util/cloudformation.util.rb:26:in `flattenCloudformation'
6: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.10.1/lib/util/cloudformation.util.rb:280:in `collect_replacements'
5: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.10.1/lib/util/cloudformation.util.rb:280:in `each'
4: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.10.1/lib/util/cloudformation.util.rb:285:in `block in collect_replacements'
3: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.10.1/lib/util/cloudformation.util.rb:285:in `each'
2: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.10.1/lib/util/cloudformation.util.rb:290:in `block (2 levels) in collect_replacements'
1: from /Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.10.1/lib/util/cloudformation.util.rb:290:in `each'
/Users/gus/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/cfhighlander-0.10.1/lib/util/cloudformation.util.rb:293:in `block (3 levels) in collect_replacements': undefined method `inlined' for nil:NilClass (NoMethodError)
cfhighlander.rb
Component template: 'vpc', name: 'vpc' do
parameter name: 'NetworkPrefix', value: '10'
parameter name: 'StackMask', value: '16'
parameter name: 'DnsDomain', value: dns_domain
parameter name: 'dnszoneAddNSRecords', value: 'true'
end
Component template: 'ecs', name: 'ecs' do
parameter name: 'SubnetIds', value: cfout('vpc.ComputeSubnets')
parameter name: 'AsgMin', value: '1'
parameter name: 'AsgMax', value: '2'
end
Component template: 'aurora-postgrest', name: 'postgres' do
parameter name: 'DnsDomain', value: dns_domain
parameter name: 'SubnetIds', value: cfout('vpc.PersistenceSubnets')
parameter name: 'EnableReader', value: 'false'
parameter name: 'ReaderInstanceType', value: ''
parameter name: 'SecurityGroupBastion', value: cfout('bastion.SecurityGroupBastion')
parameter name: 'SecurityGroupECS', value: cfout('ecs.EcsSecurityGroup')
end
problem line
parameter name: 'SecurityGroupBastion', value: cfout('bastion.SecurityGroupBastion')
where bastion
component does not exist.
Opening this ticket so users can report failed compilations. This should allow us to improve error messaging system - #39
cfndsl/cfndsl#420 has changed the way the config is evaled into the template which no longer works for cfhighlander.
cfndsl/cfndsl#422 didn't quite solve the issue so the alternative suggested is to move to using the external_parameters
config and disable the binding.
I have locked to 0.17.2 #120 for the time being, but we should do some testing with the pre 1.0 release
For example
CfhighlanderTemplate do
Component template: 'c2', name: 'c3-test', render: Inline
end
renders
---
AWSTemplateFormatVersion: '2010-09-09'
Resources:
c3test:
Properties:
TemplateURL: "./c3-test.compiled.yaml"
Parameters:
c1OutParam:
Ref: c3testc1OutParam
Condition: Enablec3test
Type: AWS::CloudFormation::Stack
Bucket:
Properties:
BucketName:
Fn::Join:
- ''
- - Ref: c3testc1OutParam
- "-c2"
Type: AWS::S3::Bucket
Parameters:
Enablec3test:
Type: String
Default: 'false'
NoEcho: false
AllowedValues:
- 'true'
- 'false'
c3testc1OutParam:
Type: String
Default: ''
NoEcho: false
Description: c4@latest - vlatest
Conditions:
Enablec3test:
Fn::Equals:
- Ref: Enablec3test
- 'true'
I think there needs to be functionality in highlander core to manage tagging on an individual resource level through configuration.
My example is say the ecs template has a Name tag of Name: ${Environment}-ecs-xx
but i want that to be Name: ${Environment}-${cluster}-ecs-xx
. We could build that functionality into the template but i feel it would be something we'd repeat in a lot of templates.
Some example config for setting tags
Tags:
Project: myApp # Tag is added to all resources that can have tags
EcsAsg: # Resource Name
Name:
Sub: ${Environment}-${Cluster}-ecs-xx
Also the ability to set global tags across all resources that could contain tags would be a good thing to have as well. However that maybe a difficult task as not all cloudformation resources use the Tag
property to declare tags
CfhighlanderTemplate do
Name 'myProject'
Component template: 'vpc', name: 'vpc', render: Inline
end
the following project produces 2 compiled artefacts and when cfpublish
is run, both compiled templates are published to the s3 buckets. It would be nice to exclude the templates not referenced as a sub stack.
Having two inner components in outer component should optionally result in producing flattened cloudformation, composited from cloudformation templates of inner components. Complexity around this task is actual input/output wiring as cloudformation model produced from cfndsl
needs to be altered.
Syntax proposal
CfhighlanderTemplate do
Name 'myapp'
Component template:'vpc', cfinline: true
Component template: 'asg', cfinline: true
Component template: 'alb', cfinline: true
end
When changing a component's version, we have no mechanism to verify that the compiled output will also be changed. For small changes to components, it's usually easy for a developer to verify that the new code is safe to deploy, simply by visiting GitHub. But for large changes, verifying can involve more lengthy processes, such as using diff
, updates to CloudFormation, etc, which can be inefficient and error prone.
It would be nice to have an option to cfcompile
that would allow us to see the diff if we were to use a new component's version in the project. For example, a command like
cfhighlander cfcompile --diff vpc1.0.0=vpc1.0.1
.
Note that this is separate from showing the actual diff of the components themselves -- this covers only the generated output.
This could also serve as a testing tool, as we can verify that a config can work across multiple versions of a particular component, and vice versa.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.