{"id":13719043,"url":"https://github.com/theonestack/cfhighlander","last_synced_at":"2025-05-07T11:30:41.314Z","repository":{"id":32484283,"uuid":"135091737","full_name":"theonestack/cfhighlander","owner":"theonestack","description":"Cloudformation DSL and component library","archived":false,"fork":false,"pushed_at":"2025-02-20T14:11:43.000Z","size":394,"stargazers_count":25,"open_issues_count":30,"forks_count":24,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-21T21:47:24.768Z","etag":null,"topics":["automation","aws","aws-sdk","cfhighlander","cfndsl","cfndsl-templates","cloudformation","cloudformation-template","dsl","modular","ruby-gem"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/theonestack.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-05-28T00:28:27.000Z","updated_at":"2025-02-20T14:11:12.000Z","dependencies_parsed_at":"2023-01-14T21:21:55.560Z","dependency_job_id":"aa16146f-8e25-4d00-92bd-f932e566890e","html_url":"https://github.com/theonestack/cfhighlander","commit_stats":{"total_commits":221,"total_committers":14,"mean_commits":"15.785714285714286","dds":0.6018099547511313,"last_synced_commit":"9eb7fb4c3acad4ba7101a4bad3183ed522ed2b2f"},"previous_names":[],"tags_count":40,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theonestack%2Fcfhighlander","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theonestack%2Fcfhighlander/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theonestack%2Fcfhighlander/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theonestack%2Fcfhighlander/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/theonestack","download_url":"https://codeload.github.com/theonestack/cfhighlander/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252868612,"owners_count":21816898,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["automation","aws","aws-sdk","cfhighlander","cfndsl","cfndsl-templates","cloudformation","cloudformation-template","dsl","modular","ruby-gem"],"created_at":"2024-08-03T01:00:41.449Z","updated_at":"2025-05-07T11:30:41.017Z","avatar_url":"https://github.com/theonestack.png","language":"Ruby","funding_links":[],"categories":["Ruby","Code Generation"],"sub_categories":["Hooks"],"readme":"![Build Status](https://github.com/theonestack/cfhighlander/actions/workflows/build-gem.yml/badge.svg) [![Join the chat at https://gitter.im/theonestack/cfhighlander](https://badges.gitter.im/theonestack/cfhighlander.svg)](https://gitter.im/theonestack/cfhighlander?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge)\n\n# Intro\n\nCfhighlander is a feature rich tool and DSL for infrastructure\ncoders working with CloudFormation templates.\n\nIt was designed to\n\n-  Abstract AWS resources or sets of resources as\n   **components** by describing them using Cfhighlander\n   DSL and [cfndsl](https://github.com/cfndsl/cfndsl).\n\n- Produce, validate and publish CloudFormation templates\n  from those components\n\n- Enable infrastructure coders to use concepts of **inheritance**\n  and **composition** when designing components. In other words\n  allowing components to be *extended*, and allowing components\n  to be built from other components.\n\n- Allow for easy **discovery** and consumption of components from\n  different sources (git repository, file system, S3 buckets)\n\n - Allow component developers and consumers to take\n   more **descriptive** approach using DSL, compared to\n   instructional approach.\n\n# Installation\n\n```\ngem install cfhighlander\n```\n\n# Example\n\nPassing output value from one substack to another substack within root stack\nhas to be done manually - either if you build JSON/YAML templates by hand,\nor if using `Cfndsl`. With cfhighlander, this code is automatically generated for you\n\n```ruby\n## place contents below in file name application.cfhighlander.rb\nCfhighlanderTemplate do\n\n  # explicit configuration for vpc component\n  vpc_config = { 'maximum_availability_zones' =\u003e 2 }\n\n  # declare vpc component, and pass some parameters\n  # to it\n  Component name: 'vpc',\n        template: 'vpc@master.snapshot',\n        config: vpc_config do\n    parameter name: 'Az0', value: FnSelect(0,FnGetAZs())\n    parameter name: 'Az1', value: FnSelect(1,FnGetAZs())\n    parameter name: 'DnsDomain', value: 'example.com'\n    parameter name: 'StackMask', value: '16'\n  end\n\n  # Compiled cloudformation template will\n  # pass Compute subnets from VPC into ECS Cluster\n  Component name: 'ecs', template:'ecs@master.snapshot' do\n    parameter name: 'DnsDomain', value: 'example.com'\n  end\n\n  # feed mapping maparameters to components\n  addMapping('EnvironmentType',{\n    'development' =\u003e {\n      'MaxNatGateways'=\u003e'1',\n      'EcsAsgMin' =\u003e 1,\n      'EcsAsgMax' =\u003e 1,\n      'KeyName' =\u003e 'default',\n      'InstanceType' =\u003e 't2.large',\n      'SingleNatGateway' =\u003e true\n    }\n  })\nend\n\n```\n\n... compile the template with ...\n\n```shell\ncfcompile application\n```\n\n... and check how the subnets are being passed around ..\n\n```shell\n$ cat out/yaml/application.compiled.yaml | grep  -A3 SubnetCompute0\n          SubnetCompute0:\n            Fn::GetAtt:\n            - vpc\n            - Outputs.SubnetCompute0\n          SubnetCompute1:\n            Fn::GetAtt:\n            - vpc\n\n```\n\n\n\n# Component library\n\nAs part of [theonestack org](https://github.com/theonestack/), there are many publicly available components.\n\n- [vpc](https://github.com/theonestack/hl-component-vpc) - Has separation of public\n   and private subnets, configurable number of NAT Gateways (per AZ or single for all\n  subnets), lays out default subnets (private - compute, storage and cache, public), as well as their route tables\n\n- [ecs](https://github.com/theonestack/hl-component-ecs) - ECS Cluster deployed in VPC Compute Subnets\n- [bastion](https://github.com/theonestack/hl-component-bastion) - Deployed into VPC Public subnets\n  Subnets, with configuration for whitelisting IP addresses to access port 22\n- [ecs-service](https://github.com/theonestack/hl-component-ecs-service) - Deploy containerised apps running on ECS Clusters\n- [loadbalancer](https://github.com/theonestack/hl-component-loadbalancer) - ALB, ELB or NLB\n- [sns](https://github.com/theonestack/hl-component-sns) - SNS Topics, with implemented\nLambda function to post Slack messages\n- [efs](https://github.com/theonestack/hl-component-efs) - Elastic File System, can be\nused in conjuction with ECS Cluster\n- [rds-mysql](https://github.com/theonestack/hl-component-rds-mysql) - RDS Component for MySQL engine\n- [rds-postgres](https://github.com/theonestack/hl-component-rds-postgres) - RDS Component for Postgres engine\n- [aurora-mysql](https://github.com/theonestack/hl-component-aurora-mysql) - Aurora component for MySQL engine\n- [aurora-postgres](https://github.com/theonestack/hl-component-aurora-postgres) - Aurora component for Postgres engine\n- [elasticache-memcache](https://github.com/theonestack/hl-component-elasticache-memcache) - Aws Elasticache - Memcache engine\n- [elasticache-memcache](https://github.com/theonestack/hl-component-elasticache-redis) - Aws Elasticache - Redis engine\n- [asg](https://github.com/theonestack/hl-component-asg) - AutoScalingGroup component\n- [cognito](https://github.com/theonestack/hl-component-cognito) - Cognito user pools, custom domain names and clients\n\nYou can easily test any of these. Automatic component resolver will default\nto 'https://github.com/theonestack/hl-component-$name' location if component\nis not found in local sources.\n\nFrom shell, command below will generate cloudformation for given component in `out` folder\n```shell\ncfcompile component_name\n```\n\nOr from outer cfhighlander template, just pull component using `Component` DSL statement\n\n```ruby\nCfhighlanderTemplate do\n  Component component_name\nend\n```\n\n\n# How it works ?\n\nHighlander DSL produces CloudFormation templates in 4 phases\n\n- Processing referenced component's configuration and resolving configuration exports\n- Wiring parameters from components to their inner components\n- Producing [CfnDsl](https://github.com/cfndsl/cfndsl) templates for all components and subcomponents as intermediary\n  step\n- Producing resulting CloudFormation templates using configuration and templates generated in two previous phases.\n\nEach phase (aside from parameter wiring) above is executable as stand-alone through CLI, making development of Highlander templates easier by enabling\ndebugging of produced configuration and cfndsl templates.\n\n\n## Highlander components\n\nHighlander component is located on local file system or S3 location with following\nfiles defining them\n\n- Highlander DSL file (`$componentname.highlander.rb`)\n- *(Optional)* Configuration files (`*.config.yaml`)\n- *(Optional)* CfnDSL file (`componentname.cfnds.rb`)\n- *(Optional)* Mappings YAML files `*.mappings.yaml` -\nthis file defines map used within component itself\n- *(Optional)* Mappings extension file `componentname.mappings.rb` - see more under Mappings section\n- *(Optional)* Ruby extensions consumed by cfndsl templates - placed in `ext/cfndsl/*.rb` - see more under\n Extensions section\n\n## Terminology\n\n**Component** is basic building block of highlander systems. Components have following roles\n\n- Define (include) other components\n- Provide values for their inner component parameters\n- Define how their configuration affects other components\n- Define sources of their inner components\n- Define publish location for both component source code and compiled CloudFormation templates\n- Define cfndsl template used for building CloudFormation resources\n\n\n**Outer component** is component that defines other component via cfhighlander dsl `Component` statement. Defined component\nis called **inner component**. Components defined under same outer component are **sibling components**\n\n## Usage\n\nYou can either pull highlander classes in your own code, or more commonly use it via command line interface (cli).\nFor both ways, highlander is distributed as ruby gem\n\n\n```bash\n$ gem install cfhighlander\n$ cfhighlander help\ncfhighlander commands:\n  cfhighlander cfcompile component[@version] -f, --format=FORMAT   # Compile Highlander component to CloudFormation templates\n  cfhighlander cfpublish component[@version] -f, --format=FORMAT   # Publish CloudFormation template for component, and it' referenced subcomponents\n  cfhighlander configcompile component[@version]                   # Compile Highlander components configuration\n  cfhighlander dslcompile component[@version] -f, --format=FORMAT  # Compile Highlander component configuration and create cfndsl templates\n  cfhighlander help [COMMAND]                                      # Describe available commands or one specific command\n  cfhighlander publish component[@version] [-v published_version]  # Publish CloudFormation template for component, and it' referenced subcomponents\n\n```\n### Working directory\n\nAll templates and configuration generated are placed in `$WORKDIR/out` directory. Optionally, you can alter working directory\nvia `CFHIGHLANDER_WORKDIR` environment variable.\n\n### Commands\n\nTo get full list of options for any of cli commands use `highlander help command_name` syntax\n\n```bash\n$ cfhighlander help publish\nUsage:\n  cfhighlander publish component[@version] [-v published_version]\n\nOptions:\n      [--dstbucket=DSTBUCKET]  # Distribution S3 bucket\n      [--dstprefix=DSTPREFIX]  # Distribution S3 prefix\n  -v, [--version=VERSION]      # Distribution component version, defaults to latest\n\nPublish CloudFormation template for component,\n            and it's referenced subcomponents\n\n```\n\n#### Silent mode\n\nCfhighlander DSL processor has built-in support for packaging and deploying AWS Lambda functions. Some of these lambda\nfunctions may require shell command to be executed (e.g. pulling library dependencies) prior their packaging in ZIP archive format.\nSuch commands are potential security risk, as they allow execution of arbitrary code, so for this reason user agreement is required\ne.g:\n\n```bash\nPackaging AWS Lambda function logMessage...\nFollowing code will be executed to generate lambda function logMessage:\n\npip install requests -t lib\n\nProceed (y/n)?\n```\n\nIn order to avoid user prompt pass `-q` or `--quiet` switch to CLI for commands that require Lambda packaging\n(`dslcompile`, `cfcompile`, `cfpublish`)\n\n\n#### cfcompile\n\n*cfcompile* will produce cloudformation templates in specified format (defaults to yaml). You can optionally validate\nproduced template via `--validate` switch. Resulting templates will be placed in `$WORKDIR/out/$format`\n\n\n#### cfpublish\n\n*cfcompile* will produce cloudformation templates in specified format (defaults to yaml), and publish them to S3 location.\nYou can optionally validate produced template via `--validate` switch. Resulting templates will be placed in `$WORKDIR/out/$format`, and\npublished to `s3://$distributionBucket/$distributionPrefix/$distributionVersion`. All S3 path components can be controlled\nvia CLI (`--dstbucket`, `--dstprefix`, `-v`). Default distribution bucket and prefix can be also be controlled via DSL using\n`DistributionBucket`, `DistributionBucket`, `DistributionPrefix` or `ComponentDistribution` statements. Check DSL specification\nfor more details on this statements. Version defaults to `latest` if not explicitly given using `-v` switch\n\nIf no distribution options is given using mentioned CLI options or DSL statements,\nbucket will be automatically created for you. Bucket name defaults to\n`$ACCOUNT.$REGION.cfhighlander.templates`, with `/published-templates`\nprefix.\n\n*cfpublish* command will give you quick launch CloudFirmation stack URL to assist\nyou in creating your stack:\n\n```bash\n$ cfpublish vpc@1.2.0\n...\n...\n...\n\nUse following url to launch CloudFormation stack\n\nhttps://console.aws.amazon.com/cloudformation/home#/stacks/create/review?filter=active\u0026templateURL=https://123456789012.ap-southeast-2.cfhighlander.templates.s3.amazonaws.com/published-templates/vpc/1.2.0/vpc.compiled.yaml\u0026stackName=vpc\n\n```\n\n\n#### configcompile\n\n*configcompile* produces configuration yamls that are passed as external configuration when processing\ncfndsl templates. Check component configuration section for more details.\n\n#### dslcompile\n\n*dslcompile* will produce intermediary cfndsl templates. This is useful for debugging cfhighlander components\n\n#### publish\n\n*publish* command publishes cfhighlander components source code to s3 location (compared to *cfpublish* which is publishing\ncompiled cloudformation templates). Same CLI / DSL options apply as for *cfpublish* command. Version defaults to `latest`\n\n\n## Component configuration\n\n### Default configuration values\n\nWithin each template, following configuration values are available by default in both cfhl and cfndsl templates\n\n- `template_name` - Name of the cfhighlander template\n- `template_verison` - Version of the cfhighlander component template used\n- `template_dir` - Disk location of the cfhighlander template. Can be used to reference local files within component, and does work with component inheritance\n\n### Defining and overriding configuration\n\nThere are 4 levels of component configuration\n\n- Component local config file `component.config.yaml` (lowest priority)\n- Outer component configuration file, under `components` key, like\n\n\n```yaml\n\n# some configuration values\n\ncomponents:\n  vpc:\n    config:\n      maximum_availibility_zones: 2\n\n```\nThis configuration level overrides component's own config file.\nAlternatively, to keep things less nested in configuration hierarchy, creating config file `vpc.config.yaml`\nfor component named `vpc` works just as well:\n\n```yaml\n\n# contents of vpc.config.yaml in outer component, defining vpc component\n\n# line below prevents component configuration file being merged with outer component configuration\nsubcomponent_config_file: true\n\n# there is no need for components/vpc/config structure, it is implied by file name\nmaximum_availibility_zones: 3\n\n\n```\n\n\n- Outer component explicit configuration. You can pass `config` named parameter to `Component` statement, such as\n\n```ruby\nCfhighlanderTemplate do\n\n# ...\n# some dsl code\n# ...\n\n   Component template:'vpc@latest',config: {'maximum_availibility_zones' =\u003e 2}\n\nend\n```\nConfiguration done this way will override any outer component config coming from configuration file\n\n\n- Exported configuration from other components. If any component exports configuration using `config_export` configuration\n  key, it may alter configuration of other components. Globally exported configuration is defined under `global`, while\n  component-oriented configuration is exported under `component` key. E.g. following configuration will export global\n  configuration defining name of ecs cluster, and targeted to vpc component configuration, defining subnets\n\n```yaml\necs_cluster_name: ApplicationCluster\n\nsubnets:\n  ecs_cluster:\n    name: ECSCluster\n    type: private\n    allocation: 20\n\nconfig_export:  \n  global:\n    - ecs_cluster_name\n\n  component:\n    vpc:\n      - subnets\n```\n\nConfiguration is exported **AFTER** component local config, and outer component configurations are loaded.\nOuter component configuration takes priority over exported configuration, as this configuration is loaded once\nmore once component exported conifgurations are applied to appropriate components.  \n\nTo change *NAME* of targeted component (e.g. from `vpc` to `vpc1`), you can use `export_config` named parameter on `Component` dsl method\nIn addition to configuration in inner component above, wiring of targeted component for export would be done like\n\n```ruby\nComponent name: 'vpc1', template: 'vpc'\nComponent name: 'ecs_cluster', template: 'ecs_cluster@latest', export_config: {'vpc' =\u003e 'vpc1'}\n```\n## CloudFormation mappings\n\n[CloudFormation Mappings](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/mappings-section-structure.html)\nsection matches a key to a corresponding set of named values. Highlander allows you to define this mappings in two ways\n\n1. By using static maps defined through YAML files. Place `*.mappings.yaml` file alongside with highlander\ntemplate to define mappings this way. Mappings defined in a static way are automatically rendered withing CloudFormation\ntemplate E.g.\n\n```yaml\n# Master component mappings\n# envtype.mappings.yaml\nEnvironmentType:\n  dev:\n    InstanceType: t2.medium\n  prod:\n    InstanceType: m4.medium\n```\n\n2. By defining mappings dynamically through Ruby code. Alongside with mappings, you can define default map name, and default\nmap key to be used when looking up value within this map. This mappings are usually rendered in outer component when inner\ncomponents pulls mapping value as parameter via `MappingParam` statement. Optionally, this mappings can be rendered within\ncomponent that defines them using `DynamicMappings` DSL statement.\n\n## Extensions\n\n### Cfndsl extensions\n\nIn order to make template more DRY, template developer may reuse ruby functions. It is possible to place\nsuch functions in separate files. Any ruby files placed within `ext/cfndsl` directory will get automatically\nincluded via Ruby `require` function in compiled Cfndsl template.\n\n## Component DSL\n\n### Inner components or subcomponents\n\nInner components or subcomponents are defined via `Component` DSL statement\n\n```ruby\nCfhighlanderTemplate do\n\n  # Example1 : Include component by template name only\n  Component 'vpc'\n\n  # Example2 : Include component by template name, version and give it a name\n  Component template: 'ecs@master.snapshot'\n\nend\n\n```\n\n**Conditional components** - If you want to add top level paramater as feature toggle for one of the inner\ncomponents, just mark it as conditional, using `conditional:` named parameter. In addition to this, default\nvalue for feature toggle can be supplied using `enabled:` named parameter. Autogenerated parameter will be named\n`Enable$COMPONENTNAME`, defaulting to `[true,false]` as set of allowed values. Autogenerated condition will \nbe named same as parameter, and checking if the parameter value equlas `true` \n\n\n```ruby\n\n# Include vpc and 2 ecs clusters with feature flags\nCfhighlanderTemplate do\n\n  # vpc component\n  Component 'vpc'\n\n  # Ecs Cluster 1 has feature toggle, enabled by default\n  Component name: 'ecs1', template: 'ecs', conditional: true\n\n  # Ecs Cluster 2 has feature toggle, and is explicitly disabled by default\n  Component name: 'ec2', template: 'ecs', conditional: true, enabled: false\n\nend\n\n```\n\n**Override default behaviour** - If you have more than 1 stack you want to control with a single parameter, or you want to change\nparameter name, you can supply the `condition:` option with a string value on each component you want to control with the condition.\nAutogenerated condition can be overriden as well, by defining your own condition with the same name on the highlander \ntemplate, as in example below. \n\n\n```ruby\nCfhighlanderTemplate do\n  \n  # [Optional - Define your own condition, instead of default true/false one]\n  Condition 'EnableDevelopmentResources', FnEquals(Ref(:EnvironmentName),'dev')\n\n  # Components always required\n  Component 'vpc'\n  Component name: 'ecs', template: 'ecs'\n  \n  # Components only required in development environments with common condition name\n  Component name: 'linux-bastion', template: 'bastion', conditional: true, condition: 'EnableDevelopmentResources', enabled: false\n  Component name: 'windows-bastion', template: 'bastion', conditional: true, condition: 'EnableDevelopmentResources', enabled: false\n\n\nend\n```\n\n**Convert config value to parameter** - In case of inner component having configuration value\nyou wish to expose as runtime parameter, it is possible to do so with limitation that configuration\nvalue is only used in *resource declarations*, as property value. If configuration value is being used\nto control the dsl flow, taking part in any control structure statements, and such gets evaluated at\n**compile** time, there is no sense of making CloudFormation stack parameter out of it.\n\nBelow example demonstrate use of `ConfigParameter` statement on simple S3 Bucket component -\nit assumes that `s3bucket` template exists with `bucketName` as configuration value for it.\n\n\n```ruby\nCfhighlanderTemplate do\n\n    Component template: 's3bucket', name: 'parameterizedBucket' do\n        ConfigParameter config_key: 'bucketName', parameter_name: '', type: 'String'\n    end\n\nend\n\n\n```\n\n### Parameters\n\nParameters block is used to define CloudFormation template parameters, and metadata on how they\nare wired with outer or sibling components.\n\n```ruby\nCfhighlanderTemplate do\n  Parameters do\n    ##\n    ##  parameter definitions here\n    ##\n  end\nend\n```\n\nParameter block supports following parameters\n\n#### ComponentParam\n\n`ComponentParam` - Component parameter exposes parameter to be wired from outer component. Cfhighlander's\nautowiring mechanism will try and find any stack outputs from other components defined by outer components with name\nmatching. If there is no explicit value provided, or autowired from outputs, parameter will be propagated to outer component.\n\nPropagated parameter will be prefixed with component name **if it is not defined as global parameter**. Otherwise,\nparameter name is kept in full.\n\nExample below demonstrates 3 different ways of providing parameter values from outer to inner component.\n\n- Provide value explicitly\n- Provide value explicitly as output of another component     \n- Autowire value from output of another component with the same name\n- Propagate parameter to outer component\n\n```ruby\n\n# Inner Component 1\nCfhighlanderTemplate do\n  Name 's3'\n  Parameters do\n     ComponentParam 'BucketName','highlander.example.com.au'\n     ComponentParam 'BucketName2',''\n     ComponentParam 'BucketName3',''\n     ComponentParam 'BucketName4','', isGlobal: false # default value is false\n     ComponentParam 'BucketName5','', isGlobal: true\n  end\n\nend\n\n```\n\n```ruby\n# Inner Component 2\nCfhighlanderTemplate do\n  Name 'nameproducer'\n\n  # has output 'bucket name defined in cfdnsl\nend\n\n\n# -- contents of cfndsl\nCloudFormation do\n\n    Condition 'AlwaysFalse', FnEquals('true','false')\n    S3_Bucket :resourcetovalidateproperly do\n      Condition 'AlwaysFalse'\n    end\n\n    Output('BucketName') do\n        Value('highlanderbucketautowired.example.com.au')\n    end\nend\n\n\n```\n\n```ruby\n# Outer component\nCfhighlanderTemplate do\n    Component 'nameproducer'\n    Component 's3' do\n      parameter name: 'BucketName2', value: 'nameproducer.BucketName'\n      parameter name: 'BucketName3', value: 'mybucket.example.cfhighlander.org'\n    end\nend\n\n```\n\n\nExample above translates to following wiring of parameters in cfndsl template\n```ruby\nCloudFormation do\n\n     # Parameter that was propagated\n    Parameter('s3BucketName4') do\n      Type 'String'\n      Default ''\n      NoEcho false\n    end\n\n    Parameter('BucketName5') do\n      Type 'String'\n      Default ''\n      NoEcho false\n    end\n\n   CloudFormation_Stack('s3') do\n       TemplateURL './s3.compiled.yaml'\n       Parameters ({\n\n          # Paramater that was auto-wired\n           'BucketName' =\u003e {\"Fn::GetAtt\":[\"nameproducer\",\"Outputs.BucketName\"]},\n\n          # Parameter that was explicitly wired as output param from another component\n           'BucketName2' =\u003e {\"Fn::GetAtt\":[\"nameproducer\",\"Outputs.BucketName\"]},\n\n          # Paramater that was explicitly provided\n           'BucketName3' =\u003e 'mybucket.example.cfhighlander.org',\n\n          # Reference to parameter that was propagated. isGlobal: false when defining\n          # parameter, so parameter name is prefixed with component name\n           'BucketName4' =\u003e {\"Ref\":\"s3BucketName4\"},\n\n          # Reference to parameter that was propagated. isGlobal: true when defining\n          # parameter, so parameter name is not prefixed, but rather propagated as-is\n          'BucketName5' =\u003e {\"Ref\":\"BucketName5\"},\n\n       })\n   end\nend\n\n```\n\n#### Overriding Parameter Properties\n\nComponents have pre defined parameters with properties such as Type, AllowedValues, NoEcho etc.\nThese properties can be overridden on the parent component.\n\n```ruby\nCfhighlanderTemplate do\n    \n    Paramater do\n      ComponentParam 'EnvironmentType', 'development', allowedValues: ['development','alpha','production']\n    end\n    \n    Component 'ecs' do\n      parameter name: 'EnvironmentType', allowedValues: ['development','alpha','production']\n      parameter name: 'Ami', value: '/ssm/path/Ami', type: 'AWS::SSM::Parameter::Value\u003cAWS::EC2::Image::Id\u003e'\n    end\n    \nend\n```\n\n\n#### MappingParam\n\n`MappingParam` - Mapping parameters value is passed as CloudFormation mapping lookup from outer component.\nThis DSL statements takes a full body, as Mapping name, Map key, and value key need to be specified. `key`,\n `attribute` and `map` methods are used to specify these properties. Mapping parameters involve ruby code execution\n\n\n\n ```ruby\n# Inner component\nCfhighlanderTemplate do\n  Name 's3'\n  Parameters do\n    MappingParam 'BucketName' do\n      map 'AccountId'\n      attribute 'DnsDomain'\n    end\n  end\nend\n ```\n\n\n### DependsOn\n\n`DependsOn` - this will include any globally exported libraries from given\ntemplate. E.g.\n\n ```ruby\nCfhighlanderTemplate do\n  Name 's3'\n  DependsOn 'vpc@1.0.3'\nend\n ```\n\nWill include any cfndsl libraries present and exported in vpc template\nso extension methods can be consumed within cfndsl template.\n\n### Component dependson\n\nIf you require dependencies between nest stacks, this can be defined in the component by using the `dependson` attribute with the value of the depend component names. This will take an array of component names or a string with a single component name. This adds the cloudformation `DependsOn` property to the stack resource in the master template.\n\n```ruby\nCfhighlanderTemplate do\n  Name 'test'\n  Component name: 'vpc', template: 'vpc'\n  Component name: 'bastion', template: 'bastion'\n  Component name: 'ecs', template: 'ecs', dependson: ['vpc','bastion']\nend\n```\n\n### LambdaFunctions\n\n\nCfhighlander supports following in terms of lambda source code management\n\n- Package and deploy lambda that has source code published to s3 as zip archive\n- Package and deploy lambda that has source code published to http(s) url\n- Package and deploy lambda with absolute source code location, or relative to component\n  root directory\n- When extending certain highlander component, of\n- Execute arbitrary 'package command' before creating final archive. This allows for downloading\ncode dependencies\n\n#### Configuration and rendering\n\nLambda functions are defined in cfhighlander templates using `LambdaFunctions` DSL statement:\n\n```ruby\nCfhighlanderTemplate do\n    Name 'my_app'\n    LambdaFunctions 'highlanderdocoexample'\nend\n```\n\nIn example above, `lambdas` - value passed to `LambdaFunctions` dsl statement is actually\ncfhighlander component configuration key, under which lambda functions, and their respective\nIAM roles are defined. Consider configuration below - all keys are commented with explanation\n\n```yaml\nhighlanderdocoexample:\n\n  # custom policies can be referenced in roles\n  custom_policies:\n    cognito:\n      action:\n        - cognito-idp:*\n      resource: '*'\n\n  # at least one role must be defined\n  roles:\n    default:\n      # using one of the default policies, or custom policies defined above\n      # defined at https://github.com/theonestack/cfhighlander/blob/develop/cfndsl_ext/config/managed_policies.yaml\n      policies_inline:\n        - cloudwatch-logs\n        - cognito\n\n      # managed IAM policies are supported as well\n      policies_managed:\n        - arn:aws:iam::aws:policy/IAMReadOnlyAccess\n        - Fn::Sub: 'arn:aws:iam::${AWS::AccountId}:policy/my_app_policy'\n\n  # you can have multiple functions defined, each as key under 'functions'\n  functions:\n    myapp:\n\n      # link to a role key above defined - mandatory\n      role: default\n\n      # code location. Can be file, archive, s3://url or http(s)://url.\n      # mandatory configuration option\n      code: src/app.py\n\n      # lambda runtime\n      runtime: python3.6\n\n      # functions that are not named are having their name auto generated via cloudformation\n      # this key defaults to false if not given\n      named: true\n\n      # if function is named, either top level key (myapp) will be used, or explicit name\n      # this key is optional, and used only if named: is set to true\n      function_name:\n        Fn::Sub: '${EnvironmentName}-myapp-${EnvironmentVersion}'\n\n      # function timeout (defaults to 30)\n      timeout: 30\n\n      # lambda function entrypoint\n      handler: app.index\n\n      # command to install any dependencies (optional)\n      # if you don't want to get prompted for every command execution use -q (quiet) option\n      package_cmd: 'pip3 install -r requirements.txt -t .'\n\n      # creates a log group with cloudformation and sets the retention period in days\n      # if not set lambda will create the log group with a unlimited retention\n      # note that if lambda has already created a log group, it will need to be deleted\n      # before this can be updated.\n      log_retention: 30\n\n      # (optional) allowed source. e.g. invocation using SNS\n      # for every allowed source, source_arn can be provided optionally\n      allowed_sources:\n       -\n         principal: sns.amazonaws.com\n       -\n         principal: sns.amazonaws.com\n         source_arn: arn:aws:sns:us-east-2:123456789012:my-topic\n\n      # (optional) invoke function on a schedule, with optional payload\n      schedules:\n       - cronExpression: 'rate(1 minute)'\n         payload: '{ \"message\": \"ping\" }'\n```\n\n\n#### Packaging and publishing\n\nDuring cfhighlander compilation process, every defined lambda functio goes through process\nof packaging:\n\n- If s3 URI or http(s) uri is given as function code, it is being downloaded\n- Temporary packaging directory is created\n- If `package_cmd` key is given, this command is being executed in temporary directory\n- Whole temporary directory is compressed and moved to `out/lambdas/$function.$timestamp.zip`\n- Sha256 hash is calculated for given file and rendered into cloudformation as function\n  version\n- Packaging information is rendered into `out/lambdas/$function.$timestamp.zip.info.yaml`\n  and added to final archive\n\nAny archive with `*.zip` extension will be uploaded to s3 with `cfpublish` command\n\n\n#### Referencing\n\nAs all of the lambda functions are rendered as cloudformation resources, they can be\nreferenced in other blocks. E.g. with example above, application component could have\nfollowing output defined in component's cfndsl file\n\n```ruby\n# myapp.cfndsl.rb\nCloudFormation do\n\n    Output('MyAppFunctionName') do\n        Value(Ref('myapp'))\n    end\n\nend\n\n\n```\n\n### Publishing Additonal Artifacts\n\nIf you need to publish additonal files along with your cloudformation templates you can use the following DSL:\n\n```ruby\nCfhighlanderTemplate do\n    Name 'my_app'\n    PublishArtifact file: \"my-apigateway-spec.yaml\"\nend\n```\n\nThis will upload the file `my-apigateway-spec.yaml` to the s3 destnation/distribution bucket using the default s3 prefix. If you want to override the s3 key you can use\n\n```ruby\nCfhighlanderTemplate do\n    Name 'my_app'\n    PublishArtifact file: \"my-apigateway-spec.yaml\", key: '/my-custom-path/my-custom-file.yaml'\nend\n```\n\n\n## Finding templates and creating components\n\n\nTemplates are located by default in following locations\n\n- `$WD`\n- `$WD/$componentname`\n- `$WD/components/$componentname`\n- `~/.cfhighlander/components/componentname/componentversion`\n- `https://github.com/cfhighlander/theonestack/hl-component-$componentname` on `master` branch\n\nLocation of component templates can be given as git/github repo:\n\n```ruby\n\nCfhighlanderTemplate do\n\n      # pulls directly from master branch of https://github.com/theonestack/hl-component-vpc\n      Component name: 'vpc0', template: 'vpc'\n\n      # specify branch github.com: or github: work. You specify branch with hash\n      Component name: 'vpc1', template: 'github:theonestack/hl-component-vpc#master'\n\n      # you can use git over ssh\n      # Component name: 'vpc2', template: 'git:git@github.com:theonestack/hl-component-vpc.git'\n\n      # use git over https\n      Component name: 'vpc3', template: 'git:https://github.com/theonestack/hl-component-sns.git'\n\n      # specify .snapshot to always clone fresh copy\n      Component name: 'vpc4', template: 'git:https://github.com/theonestack/hl-component-sns.git#master.snapshot'\n\n      # by default, if not found locally, highlander will search for https://github.com/theonestack/component-$componentname\n      # in v${version} branch (or tag for that matter)\n      Component name: 'vpc5', template: 'vpc@1.0.4'\n\nend\n\n```\n## Render mode for components\n\nRendering component resources in resulting cloudformation stack is available in 2 modes. These modes\nare controlled using `render` keyword of `Component` DSL statement\n\n`Substack` - creates additional substack for cfhighlander component and points to it using [CloudFormation\nStack](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stack.html) resource type\nThis is also default render mode - if no render mode is specified `Substack` will be used\n\n`Inline` - places all defined resources from inner component in outer component cloudformation template. Resources,\nOutputs, Conditions, Parameters and Mappings are all inlined - please note that some of the template elements may be renamed in this\nprocess in order to assure unique names.\n\nThere are some limitations when using inline components - Inlined component parameters, having values as outputs from another component (inlined or not)\ncan't be referenced in component conditions. However, conditions referencing mapping values or parameters passed as mapping values,\nare allowed.\n\n**`SIDE EFFECTS`** Side effect of moving from substack based to fully inlined stack may be revealing some of the implicit dependencies within an environment\n\n*Example:* Component A defines Hosted Zone, while component B defines Record Set for given hosted zone. Record set is defined\nby referencing Zone Name (rather than ZoneId), meaning there is no explicit dependency between the resources. When both components\nare rendered as substack, implicit dependency is created if there is at least one output from component A passed as parameter\nto component B. Rendering components inlined removes this implicitly defined dependency, as a consequence stack deletion or creation\nmay be halted, as record set is being created/deleted before prior the record set.\n\n**`WARNING`** Be aware of [resource, condition, parameter, output and mapping limits](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cloudformation-limits.html) on a single template\nwhen rendering inner components inlined.\n\n**`EXAMPLE`** All of the VPC resources will be rendered in outer component template, while bastion\nwill be referenced as substack in example below.\n\n```ruby\nCfhighlanderTemplate do\n\n    Component template:'vpc@1.5.0', name: 'vpc', render: Inline\n    Component template:'bastion@1.2.0', name: 'bastion', render: Substack\n\nend\n```\n\n\n\n## Rendering CloudFormation templates\n\n\n```bash\n$ cfhighlander cfcompile [component] [-v distributedversion]\n```\n\n## Global Extensions\n\nAny extensions placed within `cfndsl_ext` folder in core library code are\navailable in cfndsl templates of all components. Any extensions placed within `hl_ext` in core library code are\navailable in cfhighlander templates of all components.\n\n\n## Environment variables\n\n`CFHIGHLANDER_WORKDIR` - defaults to $PWD, determines location of 'out' folder where all of the\ngenerated files are placed\n\n`CFHIGHLANDER_AWS_RETRY_LIMIT` - defaults to 10. Number of retries for AWS SDK before giving up.\nAWS SDK uses exponential backoff to make the API calls\n\n\n\n## Testing components\n\nTests are designed for testing different configuration options on components.\nThey can be defined as `my_test.test.yaml` files in the `tests/` directory.\nEach test file represents a single test configuration which is then compiled\nand validated against AWS cloudformation api.\n\n### Metadata\n\ntest metadata needs to defined as bellow with at least a `name:`. Other key:values are for documentation.\n\n```yaml\ntest_metadata:\n  type: config\n  name: queues with config overrides\n  description: Create 2 queues with name and override available config\n```\n\n### Test Paramaters\n\nIf you want to test a component with a parameter input you can specify the `test-parameters:` key with key:value pairs of parameters\n\n```yaml\n# Define the test parameter\ntest_parameters:\n  SecurityGroupId: sg-123456789\n\n# Test configuration\nsecurity_group_rules:\n  -\n    from: 22\n    protocol: tcp\n    security_group_id:\n      # use the parameter within our test\n      Ref: SecurityGroupId\n    desc: ssh access from another security group\n```\n\n### Running Test\n\n```bash\n$ cfhighlander cftest [component] [options]\n```\n\n```bash\nUsage:\n  cfhighlander cftest component[@version] -f, --format=FORMAT\n\nOptions:\n  -d, [--directory=DIRECTORY]        # Tests directory\n                                     # Default: tests\n  -t, [--tests=one two three]        # Point to specific test files using the relative path\n      [--dstbucket=DSTBUCKET]        # Distribution S3 bucket\n      [--dstprefix=DSTPREFIX]        # Distribution S3 prefix\n  -f, --format=FORMAT                # CloudFormation templates output format\n                                     # Default: yaml\n                                     # Possible values: yaml, json\n      [--validate], [--no-validate]  # Optionally validate template\n                                     # Default: true\n  -q, [--quiet], [--no-quiet]        # Silently agree on user prompts (e.g. Package lambda command)\n                                     # Default: true\n  -r, [--report=REPORT]              # report output format in reports directory\n                                     # Possible values: json, xml\n\nTest Highlander component with test case config\n```\n\n### Reports\n\nBy default test will print the output to stdout. You can output to a file with a format of xml or json using the `-r` option\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftheonestack%2Fcfhighlander","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftheonestack%2Fcfhighlander","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftheonestack%2Fcfhighlander/lists"}