├── .cody
├── README.md
├── acceptance
│ ├── bin
│ │ └── build.sh
│ ├── buildspec.yml
│ ├── project.rb
│ └── role.rb
└── shared
│ └── script
│ ├── install.sh
│ └── install
│ └── lono.sh
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE.md
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── documentation.md
│ ├── feature_request.md
│ └── question.md
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .rspec
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── exe
└── lono
├── lib
├── lono.rb
├── lono
│ ├── api
│ │ ├── client.rb
│ │ ├── proxy.rb
│ │ ├── repos.rb
│ │ └── verify.rb
│ ├── app.rb
│ ├── app
│ │ ├── callable_option.rb
│ │ ├── callable_option
│ │ │ └── concern.rb
│ │ └── inits.rb
│ ├── autoloader.rb
│ ├── aws_services.rb
│ ├── aws_services
│ │ └── helper.rb
│ ├── blueprint.rb
│ ├── booter.rb
│ ├── builder.rb
│ ├── builder
│ │ ├── allow.rb
│ │ ├── allow
│ │ │ ├── base.rb
│ │ │ ├── env.rb
│ │ │ └── region.rb
│ │ ├── configset
│ │ │ ├── combiner.rb
│ │ │ ├── definition.rb
│ │ │ ├── definition
│ │ │ │ ├── base.rb
│ │ │ │ ├── context.rb
│ │ │ │ ├── dsl.rb
│ │ │ │ ├── dsl
│ │ │ │ │ ├── syntax.rb
│ │ │ │ │ └── syntax
│ │ │ │ │ │ ├── auth.rb
│ │ │ │ │ │ ├── content.rb
│ │ │ │ │ │ ├── core.rb
│ │ │ │ │ │ └── package.rb
│ │ │ │ └── erb.rb
│ │ │ ├── evaluator.rb
│ │ │ └── registration.rb
│ │ ├── context.rb
│ │ ├── dsl.rb
│ │ ├── dsl
│ │ │ ├── evaluator.rb
│ │ │ ├── finalizer.rb
│ │ │ ├── finalizer
│ │ │ │ ├── base.rb
│ │ │ │ ├── configsets.rb
│ │ │ │ ├── files.rb
│ │ │ │ ├── files
│ │ │ │ │ ├── base.rb
│ │ │ │ │ ├── build.rb
│ │ │ │ │ └── replace.rb
│ │ │ │ └── parameter_groups.rb
│ │ │ ├── helpers.rb
│ │ │ ├── helpers
│ │ │ │ ├── ec2.rb
│ │ │ │ ├── files.rb
│ │ │ │ ├── partials.rb
│ │ │ │ ├── s3.rb
│ │ │ │ ├── ssm.rb
│ │ │ │ ├── ssm
│ │ │ │ │ └── fetcher.rb
│ │ │ │ ├── stack.rb
│ │ │ │ ├── tags.rb
│ │ │ │ └── template_file.rb
│ │ │ ├── syntax.rb
│ │ │ └── syntax
│ │ │ │ ├── core.rb
│ │ │ │ ├── core
│ │ │ │ ├── base.rb
│ │ │ │ ├── condition.rb
│ │ │ │ ├── mapping.rb
│ │ │ │ ├── output.rb
│ │ │ │ ├── parameter.rb
│ │ │ │ ├── resource.rb
│ │ │ │ ├── resource
│ │ │ │ │ └── property_mover.rb
│ │ │ │ ├── section.rb
│ │ │ │ └── squeezer.rb
│ │ │ │ ├── fn.rb
│ │ │ │ └── parameter_group.rb
│ │ ├── param.rb
│ │ ├── template.rb
│ │ ├── template
│ │ │ ├── aws_service.rb
│ │ │ ├── bashify.rb
│ │ │ ├── output.rb
│ │ │ └── upload.rb
│ │ └── util
│ │ │ └── stringify.rb
│ ├── bundler.rb
│ ├── bundler
│ │ ├── cli.rb
│ │ ├── cli
│ │ │ ├── base.rb
│ │ │ ├── bundle.rb
│ │ │ ├── clean.rb
│ │ │ ├── help.rb
│ │ │ └── help
│ │ │ │ └── bundle.md
│ │ ├── component.rb
│ │ ├── component
│ │ │ ├── concerns
│ │ │ │ ├── local_concern.rb
│ │ │ │ ├── notation_concern.rb
│ │ │ │ ├── path_concern.rb
│ │ │ │ └── stack_concern.rb
│ │ │ ├── fetcher.rb
│ │ │ ├── fetcher
│ │ │ │ ├── base.rb
│ │ │ │ ├── gcs.rb
│ │ │ │ ├── git.rb
│ │ │ │ ├── local.rb
│ │ │ │ └── s3.rb
│ │ │ ├── http
│ │ │ │ ├── concern.rb
│ │ │ │ └── source.rb
│ │ │ ├── org_repo.rb
│ │ │ ├── props.rb
│ │ │ ├── props
│ │ │ │ ├── extension.rb
│ │ │ │ └── typer.rb
│ │ │ └── registry.rb
│ │ ├── config.rb
│ │ ├── dsl.rb
│ │ ├── dsl
│ │ │ └── syntax.rb
│ │ ├── exporter.rb
│ │ ├── exporter
│ │ │ ├── base.rb
│ │ │ └── copy.rb
│ │ ├── extract
│ │ │ ├── tar.rb
│ │ │ └── zip.rb
│ │ ├── info.rb
│ │ ├── list.rb
│ │ ├── lockfile.rb
│ │ ├── lockfile
│ │ │ ├── version_comparer.rb
│ │ │ └── yamler.rb
│ │ ├── lonofile.rb
│ │ ├── runner.rb
│ │ ├── syncer.rb
│ │ └── util
│ │ │ ├── git.rb
│ │ │ ├── logging.rb
│ │ │ └── sure.rb
│ ├── cfn
│ │ ├── base.rb
│ │ ├── cancel.rb
│ │ ├── concerns.rb
│ │ ├── concerns
│ │ │ ├── build.rb
│ │ │ └── template_output.rb
│ │ ├── delete.rb
│ │ ├── deploy.rb
│ │ ├── deploy
│ │ │ ├── base.rb
│ │ │ ├── iam.rb
│ │ │ ├── notification.rb
│ │ │ ├── operable.rb
│ │ │ ├── opts.rb
│ │ │ ├── rollback.rb
│ │ │ └── tags.rb
│ │ ├── download.rb
│ │ ├── output.rb
│ │ ├── plan.rb
│ │ ├── plan
│ │ │ ├── base.rb
│ │ │ ├── changeset.rb
│ │ │ ├── changeset
│ │ │ │ ├── base.rb
│ │ │ │ ├── notifications.rb
│ │ │ │ ├── outputs.rb
│ │ │ │ ├── resources.rb
│ │ │ │ └── tags.rb
│ │ │ ├── concerns.rb
│ │ │ ├── delete.rb
│ │ │ ├── diff
│ │ │ │ ├── base.rb
│ │ │ │ ├── data.rb
│ │ │ │ └── file.rb
│ │ │ ├── new.rb
│ │ │ ├── param.rb
│ │ │ ├── summary.rb
│ │ │ └── template.rb
│ │ ├── show.rb
│ │ └── status.rb
│ ├── cli.rb
│ ├── cli
│ │ ├── abstract.rb
│ │ ├── base.rb
│ │ ├── bundle.rb
│ │ ├── cfn.rb
│ │ ├── cfn
│ │ │ └── opts.rb
│ │ ├── clean.rb
│ │ ├── completion.rb
│ │ ├── help.rb
│ │ ├── help
│ │ │ ├── build.md
│ │ │ ├── cfn.md
│ │ │ ├── cfn
│ │ │ │ ├── cancel.md
│ │ │ │ ├── download.md
│ │ │ │ ├── preview.md
│ │ │ │ └── status.md
│ │ │ ├── code
│ │ │ │ ├── convert.md
│ │ │ │ └── import.md
│ │ │ ├── completion.md
│ │ │ ├── completion_script.md
│ │ │ ├── configsets.md
│ │ │ ├── down.md
│ │ │ ├── new
│ │ │ │ ├── helper.md
│ │ │ │ ├── hook.md
│ │ │ │ └── project.md
│ │ │ ├── param.md
│ │ │ ├── param
│ │ │ │ └── generate.md
│ │ │ ├── pro
│ │ │ │ ├── blueprints.md
│ │ │ │ └── configsets.md
│ │ │ ├── script
│ │ │ │ ├── build.md
│ │ │ │ └── upload.md
│ │ │ ├── seed.md
│ │ │ ├── summary.md
│ │ │ ├── template.md
│ │ │ ├── template
│ │ │ │ ├── bashify.md
│ │ │ │ └── generate.md
│ │ │ ├── up.md
│ │ │ └── user_data.md
│ │ ├── iam.rb
│ │ ├── list.rb
│ │ ├── new.rb
│ │ ├── new
│ │ │ ├── blueprint.rb
│ │ │ ├── concerns.rb
│ │ │ ├── configset.rb
│ │ │ ├── helper.rb
│ │ │ ├── hook.rb
│ │ │ ├── project.rb
│ │ │ ├── sequence.rb
│ │ │ ├── shim.rb
│ │ │ ├── test.rb
│ │ │ └── test
│ │ │ │ ├── blueprint.rb
│ │ │ │ └── sequence.rb
│ │ ├── opts.rb
│ │ ├── s3.rb
│ │ ├── script.rb
│ │ ├── script
│ │ │ ├── base.rb
│ │ │ ├── build.rb
│ │ │ └── upload.rb
│ │ ├── seed.rb
│ │ ├── status.rb
│ │ ├── summary.rb
│ │ └── test.rb
│ ├── command.rb
│ ├── completer.rb
│ ├── completer
│ │ ├── script.rb
│ │ └── script.sh
│ ├── component.rb
│ ├── concerns
│ │ ├── aws_info.rb
│ │ └── names.rb
│ ├── configset.rb
│ ├── core.rb
│ ├── ext.rb
│ ├── ext
│ │ ├── bundler.rb
│ │ └── core
│ │ │ ├── module.rb
│ │ │ ├── object.rb
│ │ │ └── string.rb
│ ├── files.rb
│ ├── files
│ │ ├── base.rb
│ │ ├── builder.rb
│ │ ├── builder
│ │ │ ├── lambda_layer.rb
│ │ │ └── lambda_layer
│ │ │ │ ├── rsync.rb
│ │ │ │ ├── ruby_packager.rb
│ │ │ │ └── ruby_version.rb
│ │ ├── compressor.rb
│ │ ├── concerns
│ │ │ ├── post_processing.rb
│ │ │ └── registration.rb
│ │ └── registry.rb
│ ├── hooks
│ │ ├── builder.rb
│ │ ├── concern.rb
│ │ ├── dsl.rb
│ │ └── runner.rb
│ ├── importer.rb
│ ├── layering.rb
│ ├── layering
│ │ └── layer.rb
│ ├── logger.rb
│ ├── logger
│ │ └── formatter.rb
│ ├── md5.rb
│ ├── names.rb
│ ├── plugin.rb
│ ├── plugin
│ │ ├── meta.rb
│ │ └── tester.rb
│ ├── registration.rb
│ ├── s3
│ │ ├── aws_setup.rb
│ │ ├── bucket.rb
│ │ ├── rollback.rb
│ │ └── uploader.rb
│ ├── script
│ │ ├── base.rb
│ │ ├── build.rb
│ │ └── upload.rb
│ ├── seeder.rb
│ ├── user_data.rb
│ ├── utils.rb
│ ├── utils
│ │ ├── call_line.rb
│ │ ├── logging.rb
│ │ ├── pretty.rb
│ │ ├── quit.rb
│ │ ├── sh.rb
│ │ └── sure.rb
│ ├── version.rb
│ └── yamler
│ │ ├── loader.rb
│ │ └── validator.rb
└── templates
│ ├── blueprint
│ └── template.rb
│ ├── configset
│ └── configset.rb
│ ├── examples
│ ├── blueprint
│ │ └── template.rb
│ └── configset
│ │ └── configset.rb
│ ├── helper
│ └── %underscore_name%_helper.rb.tt
│ ├── hook
│ └── config
│ │ └── hooks.rb.tt
│ ├── project
│ ├── .gitignore
│ ├── Gemfile.tt
│ ├── README.md
│ └── config
│ │ └── app.rb
│ └── shim
│ └── lono
├── lono.gemspec
└── spec
├── fixtures
├── configsets
│ ├── dsl
│ │ └── httpd
│ │ │ └── configset.rb
│ ├── erb
│ │ └── httpd
│ │ │ ├── configset.yml
│ │ │ └── vars.rb
│ ├── snippets
│ │ ├── config1.json
│ │ ├── config2.json
│ │ └── single.yml
│ └── templates
│ │ ├── ec2-and-sg-metadata.yml
│ │ ├── ec2-and-sg.yml
│ │ ├── ec2-multiple.yml
│ │ ├── ec2-no-metadata.yml
│ │ └── ec2-single.yml
├── md5
│ ├── a.rb
│ ├── folder
│ │ └── a.rb
│ └── folder1
│ │ └── folder2
│ │ └── a.rb
└── validator
│ └── bad.yml
├── lono
├── builder
│ └── configset
│ │ ├── combiner_spec.rb
│ │ └── definition
│ │ ├── dsl_spec.rb
│ │ └── erb_spec.rb
├── cfn
│ └── plan
│ │ └── diff
│ │ └── data_spec.rb
├── cli_spec.rb
├── completion_spec.rb
├── md5_spec.rb
└── yamler
│ └── validator_spec.rb
├── spec_helper.rb
└── spec_helper
├── execute.rb
└── logging.rb
/.cody/README.md:
--------------------------------------------------------------------------------
1 | # Overview
2 |
3 | CodeBuild is used to run **acceptance-level tests**.
4 |
5 | ## Deploy Project
6 |
7 | To update the CodeBuild project that handles deployment:
8 |
9 | cody deploy lono -t acceptance
10 |
11 | ## Start Build
12 |
13 | To start a CodeBuild build:
14 |
15 | cody start lono -t acceptance
16 |
17 | To specify a branch:
18 |
19 | cody start lono -t acceptance -b feature
20 |
--------------------------------------------------------------------------------
/.cody/acceptance/bin/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | final_status=0
4 | function capture_status {
5 | if [ "$?" -ne "0" ] && [ $final_status -ne 1 ] ; then
6 | final_status=1
7 | fi
8 | }
9 |
10 | set -eu
11 | # will build from /tmp because terraspace/Gemfile may interfere
12 | cd /tmp
13 | export PATH=~/bin:$PATH # ~/bin/lono wrapper
14 |
15 | lono new project infra
16 | cd infra
17 |
18 | # Rewrite the Gemfile to use the local lono gem for testing
19 | cat << EOF > Gemfile
20 | source "https://rubygems.org"
21 | gem "lono", path: "$CODEBUILD_SRC_DIR", submodules: true
22 | gem "rspec-lono", git: "https://github.com/boltops-tools/rspec-lono", branch: "master"
23 | EOF
24 | cat Gemfile
25 |
26 | bundle # install lono gem in the infra project
27 |
28 | export LONO_ENV="test-$(date +%Y%m%d%H%M%S)"
29 | lono new blueprint demo --examples
30 |
31 | # Continue on error and capture final exit status so lono down always and cleans up stack
32 | set +e
33 | # Test new stack creation
34 | lono up demo -y
35 | capture_status
36 |
37 | lono seed demo # just to test it. will overwrite file
38 | capture_status
39 |
40 | # Test stack update
41 | cat << EOF > config/blueprints/demo/params/$LONO_ENV.txt
42 | AccessControl=PublicRead
43 | EOF
44 | lono up demo -y
45 | capture_status
46 |
47 | # Clean up resources
48 | lono down demo -y
49 | capture_status
50 | set -e
51 |
52 | exit $final_status
53 |
--------------------------------------------------------------------------------
/.cody/acceptance/buildspec.yml:
--------------------------------------------------------------------------------
1 | version: 0.2
2 |
3 | phases:
4 | install:
5 | runtime-versions:
6 | ruby: latest
7 | build:
8 | commands:
9 | - .cody/shared/script/install.sh
10 | - .cody/acceptance/bin/build.sh
11 |
--------------------------------------------------------------------------------
/.cody/acceptance/project.rb:
--------------------------------------------------------------------------------
1 | github_url("https://github.com/boltops-tools/lono")
2 | linux_image("aws/codebuild/amazonlinux2-x86_64-standard:3.0")
3 | # triggers(
4 | # webhook: true,
5 | # filter_groups: [[{type: "EVENT", pattern: "PUSH"}]]
6 | # )
7 |
--------------------------------------------------------------------------------
/.cody/acceptance/role.rb:
--------------------------------------------------------------------------------
1 | iam_policy(
2 | "cloudformation",
3 | "ec2",
4 | "logs",
5 | "s3",
6 | "ssm",
7 | )
--------------------------------------------------------------------------------
/.cody/shared/script/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -eu
4 |
5 | .cody/shared/script/install/lono.sh
6 |
--------------------------------------------------------------------------------
/.cody/shared/script/install/lono.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -eux
4 |
5 | export PATH=~/bin:$PATH
6 |
7 | cat << 'EOF' > ~/.gemrc
8 | ---
9 | :backtrace: false
10 | :bulk_threshold: 1000
11 | :sources:
12 | - https://rubygems.org
13 | :update_sources: true
14 | :verbose: true
15 | benchmark: false
16 | install: "--no-ri --no-rdoc --no-document"
17 | update: "--no-ri --no-rdoc --no-document"
18 | EOF
19 |
20 | gem install bundler # upgrade bundler
21 |
22 | # In original lono source and install lono
23 | cd $CODEBUILD_SRC_DIR # lono folder - in case code is added later above this that uses cd
24 | bundle install
25 | bundle exec rake install
26 |
27 | mkdir -p ~/bin
28 | cat << EOF > ~/bin/lono
29 | #!/bin/bash
30 | # If there's a Gemfile, assume we're in a lono project with a Gemfile for lono
31 | if [ -f Gemfile ]; then
32 | exec bundle exec $CODEBUILD_SRC_DIR/exe/lono "\$@"
33 | else
34 | exec $CODEBUILD_SRC_DIR/exe/lono "\$@"
35 | fi
36 | EOF
37 |
38 | cat ~/bin/lono
39 |
40 | chmod a+x ~/bin/lono
41 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: boltops-tools
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | Please fill out one of the templates on https://github.com/boltops-tools/lono/issues/new/choose
2 |
3 | If you want to ask a question please do so in the Lono category in the BoltOps Community forum: https://community.boltops.com
4 |
5 | To be sensitive to everyone's time, we may close issues asking questions without comment. Posting your questions in the Lono community forum is the best place. It also benefits others by making the questions easier to find. Here are some additional options also https://lono.cloud/support/ 👌
6 |
7 | Thank you!
8 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/documentation.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Documentation
3 | about: Found a typo or something that isn't crystal clear in the docs?
4 | title: ''
5 | labels: docs
6 | assignees: ''
7 |
8 | ---
9 |
10 | The Lono Docs are in the [lono-docs repo](https://github.com/boltops-tools/lono-docs). Please submit a PR there. Thanks!
11 |
12 | For documentation changes to the lono code base itself, like code comments. Please submit a PR here. Thanks!
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: New Feature Suggestion
3 | about: Want to add a feature to Lono?
4 | title: ''
5 | labels: feature
6 | assignees: ''
7 |
8 | ---
9 |
10 |
15 |
16 | ## Summary
17 |
18 |
21 |
22 | ## Motivation
23 |
24 |
29 |
30 | ## Guide-level explanation
31 |
32 |
41 |
42 | ## Reference-level explanation
43 |
44 |
53 |
54 | ## Drawbacks
55 |
56 |
59 |
60 | ## Unresolved Questions
61 |
62 |
65 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Question
3 | about: Have any questions about how Lono works?
4 | title: ''
5 | labels: 'question'
6 | assignees: ''
7 |
8 | ---
9 |
10 | The Lono issue tracker IS NOT for usage questions! Please post your question on our dedicated forum at https://community.boltops.com
11 |
12 | To be sensitive to everyone's time, we may close issues asking questions without comment. If you repeatedly post questions in the issues tracker, you may be blocked from ever submitting issues to Lono again. Please use your best judgment. 👍
13 |
14 | Posting your questions in the Lono community forum benefits others by grouping questions in a dedicated place. Here are some additional options also https://lono.cloud/support/ 😁
15 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
6 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
20 |
21 | - [ ] I've added tests (if it's a bug, feature or enhancement)
22 | - [ ] I've adjusted the documentation (if it's a feature or enhancement)
23 | - [ ] The test suite passes (run `bundle exec rspec` to verify this)
24 |
25 | ## Summary
26 |
27 |
30 |
31 | ## Context
32 |
33 |
36 |
37 | ## How to Test
38 |
39 |
42 |
43 |
44 | ## Version Changes
45 |
46 |
50 |
51 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | *.rbc
3 | .bundle
4 | .config
5 | .DS_Store
6 | .yardoc
7 | _yardoc
8 | coverage
9 | doc/
10 | Gemfile.lock
11 | InstalledFiles
12 | lib/bundler/man
13 | pkg
14 | rdoc
15 | spec/fixtures/my_project/templates/aws-waf-security-automations.yml
16 | spec/project
17 | spec/reports
18 | test/tmp
19 | test/version_tmp
20 |
21 | /infra
22 | /output
23 | /tmp
24 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --format documentation
3 | --require spec_helper
4 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | # Specify your gem dependencies in lono.gemspec
4 | gemspec
5 |
6 | group :test do
7 | gem "rspec-lono", git: "https://github.com/boltops-tools/rspec-lono", branch: "master"
8 | end
9 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "./lib/lono"
2 | require "bundler/gem_tasks"
3 | require "rspec/core/rake_task"
4 |
5 | task :default => :spec
6 |
7 | RSpec::Core::RakeTask.new
8 |
9 | require_relative "lib/lono"
10 | require "cli_markdown"
11 | desc "Generates cli reference docs as markdown"
12 | task :docs do
13 | CliMarkdown::Creator.create_all(cli_class: Lono::CLI, cli_name: "lono")
14 | end
15 |
--------------------------------------------------------------------------------
/exe/lono:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # Trap ^C
4 | Signal.trap("INT") {
5 | puts "\nCtrl-C detected. Exiting..."
6 | sleep 0.1
7 | exit
8 | }
9 |
10 | require_relative "../lib/lono"
11 | Lono::CLI.start
12 |
--------------------------------------------------------------------------------
/lib/lono.rb:
--------------------------------------------------------------------------------
1 | $stdout.sync = true unless ENV["LONO_STDOUT_SYNC"] == "0"
2 |
3 | require 'active_support'
4 | require 'active_support/core_ext/class'
5 | require 'active_support/core_ext/hash'
6 | require 'active_support/core_ext/string'
7 | require 'aws_data'
8 | require 'cfn_camelizer'
9 | require 'cfn_status'
10 | require 'cli-format'
11 | require 'dsl_evaluator'
12 | require 'fileutils'
13 | require 'json'
14 | require 'lono/ext'
15 | require 'memoist'
16 | require 'plissken'
17 | require 'rainbow/ext/string'
18 | require 'render_me_pretty'
19 | require 'singleton'
20 | require 'yaml'
21 |
22 | require "lono/autoloader"
23 | Lono::Autoloader.setup
24 |
25 | module Lono
26 | API_DEFAULT = 'https://api.lono.cloud/v1'
27 | API = ENV['LONO_API'] || API_DEFAULT
28 | extend Core
29 | end
30 |
31 | DslEvaluator.configure do |config|
32 | config.backtrace.select_pattern = Lono.root.to_s
33 | config.logger = Lono.logger
34 | config.on_exception = :exit
35 | config.root = Lono.root
36 | end
37 |
38 | Lono::Booter.boot
39 |
--------------------------------------------------------------------------------
/lib/lono/api/client.rb:
--------------------------------------------------------------------------------
1 | module Lono::Api
2 | class Client
3 | extend Memoist
4 | include Verify
5 | include Repos
6 | include Lono::Utils::Logging
7 |
8 | def http
9 | Proxy.new
10 | end
11 | memoize :http
12 |
13 | def load_json(res)
14 | if res.code == "200"
15 | data = JSON.load(res.body)
16 | case data
17 | when Array
18 | data.map(&:deep_symbolize_keys)
19 | when Hash
20 | data.deep_symbolize_keys
21 | end
22 | else
23 | if ENV['LONO_DEBUG_API']
24 | logger.info "Error: Non-successful http response status code: #{res.code}"
25 | logger.info "headers: #{res.each_header.to_h.inspect}"
26 | end
27 | nil
28 | end
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/lib/lono/api/proxy.rb:
--------------------------------------------------------------------------------
1 | # To allow a pretty interface:
2 | #
3 | # res = http.get("blueprints")
4 | #
5 | module Lono::Api
6 | class Proxy
7 | extend Memoist
8 |
9 | def get(path)
10 | request(Net::HTTP::Get, path)
11 | end
12 |
13 | def post(path, data={})
14 | request(Net::HTTP::Post, path, data)
15 | end
16 |
17 | def http(url)
18 | uri = URI(url)
19 | http = Net::HTTP.new(uri.host, uri.port)
20 | http.open_timeout = http.read_timeout = 30
21 | http.use_ssl = true if uri.scheme == 'https'
22 | http
23 | end
24 | memoize :http
25 |
26 | def request(klass, path, data={})
27 | url = url(path)
28 | http = http(url)
29 | req = send_request(klass, url, data)
30 | http.request(req) # send request
31 | end
32 |
33 | def send_request(klass, url, data={})
34 | data.merge!(
35 | lono_version: Lono::VERSION,
36 | lono_command: lono_command,
37 | )
38 | req = klass.new(url) # url includes query string and uri.path does not, must used url
39 | if [Net::HTTP::Post, Net::HTTP::Put].include?(klass)
40 | text = JSON.dump(data)
41 | req.body = text
42 | req.content_length = text.bytesize
43 | end
44 | req
45 | end
46 |
47 | # Lono::API does not include the /. IE: localhost:8888
48 | # path includes the /. IE: "/blueprints"
49 | def url(path)
50 | "#{Lono::API}/#{path}"
51 | end
52 |
53 | private
54 | def lono_command
55 | "#{$0} #{ARGV.join(' ')}"
56 | end
57 | end
58 | end
--------------------------------------------------------------------------------
/lib/lono/api/repos.rb:
--------------------------------------------------------------------------------
1 | module Lono::Api
2 | module Repos
3 | def repos(type)
4 | res = http.get("repos?type=#{type}")
5 | load_json(res)
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/lib/lono/api/verify.rb:
--------------------------------------------------------------------------------
1 | module Lono::Api
2 | module Verify
3 | def verify(data)
4 | res = http.post("verify", data)
5 | load_json(res)
6 | end
7 |
8 | def temp_key
9 | res = http.post("temp_keys")
10 | load_json(res)
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/lono/app/callable_option/concern.rb:
--------------------------------------------------------------------------------
1 | class Lono::App::CallableOption
2 | module Concern
3 | def callable_option(options={})
4 | callable_option = Lono::App::CallableOption.new(
5 | config_name: options[:config_name],
6 | config_value: options[:config_value],
7 | passed_args: options[:passed_args],
8 | )
9 | callable_option.object
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/lib/lono/app/inits.rb:
--------------------------------------------------------------------------------
1 | class Lono::App
2 | class Inits
3 | class << self
4 | include DslEvaluator
5 |
6 | def run_all
7 | Dir.glob("#{Lono.root}/config/inits/*.rb").each do |path|
8 | evaluate_file(path)
9 | end
10 | end
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/lono/autoloader.rb:
--------------------------------------------------------------------------------
1 | require "lono/bundler"
2 | Lono::Bundler.setup
3 | require "zeitwerk"
4 |
5 | module Lono
6 | class Autoloader
7 | class Inflector < Zeitwerk::Inflector
8 | def camelize(basename, _abspath)
9 | map = { cli: "CLI", version: "VERSION" }
10 | map[basename.to_sym] || super
11 | end
12 | end
13 |
14 | class << self
15 | def setup
16 | loader = Zeitwerk::Loader.new
17 | loader.inflector = Inflector.new
18 | loader.push_dir(File.dirname(__dir__)) # lib
19 | loader.log! if ENV["LONO_AUTOLOAD_LOG"]
20 | loader.ignore("#{__dir__}/ext.rb")
21 | loader.setup
22 | end
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/lib/lono/blueprint.rb:
--------------------------------------------------------------------------------
1 | module Lono
2 | class Blueprint < Component
3 | def output_path
4 | "#{Lono.root}/output/#{@name}/template.yml"
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/lib/lono/booter.rb:
--------------------------------------------------------------------------------
1 | module Lono
2 | module Booter
3 | def boot
4 | run_hooks
5 | Lono::Bundler.require # load plugins
6 | end
7 |
8 | # Special boot hooks run super early.
9 | # Useful for setting env vars and other early things.
10 | #
11 | # config/boot.rb
12 | # config/boot/dev.rb
13 | #
14 | def run_hooks
15 | run_hook
16 | run_hook(Lono.env)
17 | Lono::App::Inits.run_all
18 | end
19 |
20 | def run_hook(env=nil)
21 | name = env ? "boot/#{env}" : "boot"
22 | path = "#{Lono.root}/config/#{name}.rb"
23 | require path if File.exist?(path)
24 | end
25 |
26 | extend self
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/lono/builder.rb:
--------------------------------------------------------------------------------
1 | module Lono
2 | class Builder < Lono::CLI::Base
3 | include Lono::Hooks::Concern
4 |
5 | def all
6 | check_allow!
7 |
8 | parameters = nil
9 | run_hooks("build") do
10 | clean
11 | template_builder.run # build with placeholders IE: LONO://app/files/index.rb
12 | parameters = param_builder.build # Writes the json file in CamelCase keys format
13 | end
14 |
15 | logger.info "" # newline
16 | parameters
17 | end
18 | memoize :all
19 | alias_method :parameters, :all
20 |
21 | def clean
22 | Lono::CLI::Clean.new(@options.merge(mute: true)).run
23 | end
24 |
25 | def check_allow!
26 | Lono::Builder::Allow.new(@options).check!
27 | end
28 |
29 | def param_builder
30 | Lono::Builder::Param.new(@options)
31 | end
32 | memoize :param_builder
33 |
34 | def template_builder
35 | Lono::Builder::Template.new(@options) # write templates to disk
36 | end
37 | memoize :template_builder
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/lib/lono/builder/allow.rb:
--------------------------------------------------------------------------------
1 | class Lono::Builder
2 | class Allow < Lono::CLI::Base
3 | def check!
4 | Env.new(@options).check!
5 | Region.new(@options).check!
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/lib/lono/builder/allow/base.rb:
--------------------------------------------------------------------------------
1 | class Lono::Builder::Allow
2 | class Base < Lono::CLI::Base
3 | include Lono::App::CallableOption::Concern
4 |
5 | def check!
6 | messages = []
7 | unless allowed?
8 | messages << message # message is interface method
9 | end
10 | unless messages.empty?
11 | puts "ERROR: The configs do not allow this.".color(:red)
12 | puts messages
13 | exit 1
14 | end
15 | end
16 |
17 | def allowed?
18 | if allows.nil? && denys.nil?
19 | true
20 | elsif denys.nil?
21 | allows.include?(check_value)
22 | elsif allows.nil?
23 | !denys.include?(check_value)
24 | else
25 | allows.include?(check_value) && !denys.include?(check_value)
26 | end
27 | end
28 |
29 | def allows
30 | callable_option(
31 | config_name: "config.allow.#{config_name}",
32 | config_value: config.dig(:allow, config_name),
33 | passed_args: [@blueprint],
34 | )
35 | end
36 |
37 | def denys
38 | callable_option(
39 | config_name: "config.deny.#{config_name}",
40 | config_value: config.dig(:deny, config_name),
41 | passed_args: [@blueprint],
42 | )
43 | end
44 |
45 | private
46 | def config
47 | Lono.config
48 | end
49 |
50 | def config_name
51 | self.class.to_s.split('::').last.underscore.pluralize.to_sym # ActiveSuport::HashWithIndifferentAccess#dig requires symbol
52 | end
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/lib/lono/builder/allow/env.rb:
--------------------------------------------------------------------------------
1 | class Lono::Builder::Allow
2 | class Env < Base
3 | # interface method
4 | def message
5 | messages = []
6 | messages << "This env is not allowed to be used: LONO_ENV=#{Lono.env}"
7 | messages << "Allow envs: #{allows.join(', ')}" if allows
8 | messages << "Deny envs: #{denys.join(', ')}" if denys
9 | messages.join("\n")
10 | end
11 |
12 | # interface method
13 | def check_value
14 | Lono.env
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/lono/builder/allow/region.rb:
--------------------------------------------------------------------------------
1 | class Lono::Builder::Allow
2 | class Region < Base
3 | include Lono::Concerns::AwsInfo
4 |
5 | # interface method
6 | def message
7 | messages = []
8 | word = config_name.to_s # IE: regions or locations
9 | messages << "This #{word.singularize} is not allowed to be used: Detected current #{word.singularize}=#{current_region}"
10 | messages << "Allow #{word}: #{allows.join(', ')}" if allows
11 | messages << "Deny #{word}: #{denys.join(', ')}" if denys
12 | messages.join("\n")
13 | end
14 |
15 | # interface method
16 | def check_value
17 | region
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/lono/builder/configset/definition.rb:
--------------------------------------------------------------------------------
1 | module Lono::Builder::Configset
2 | class Definition < Lono::CLI::Base
3 | attr_reader :configset
4 | def initialize(options={})
5 | super
6 | @meta = options[:meta]
7 | @configset = Lono::Configset.new(@meta)
8 | end
9 |
10 | def evaluate
11 | strategy_class = configset.path.include?('.rb') ? Dsl : Erb
12 | strategy = strategy_class.new(@options.merge(path: configset.path))
13 | metadata = strategy.evaluate
14 | @configset.metadata = metadata
15 | @configset
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/lono/builder/configset/definition/base.rb:
--------------------------------------------------------------------------------
1 | class Lono::Builder::Configset::Definition
2 | class Base < Lono::CLI::Base
3 | include DslEvaluator
4 | include Lono::Builder::Dsl::Syntax
5 | include Lono::Utils::Pretty
6 | include Context
7 |
8 | # Really only use @path in Configset DSL and ERB evaluation.
9 | # However, configsets are built within the CloudFormation template and can use
10 | # things instrinic functions like `ref` like would normally have access to.
11 | # So configsets need the same context
12 | #
13 | # Configset::Definition::Base < Lono::CLI::Base
14 | #
15 | # for
16 | #
17 | # include Lono::Builder::Dsl::Syntax
18 | #
19 | def initialize(options={})
20 | super
21 | @meta = @options[:meta]
22 | @configset = Lono::Configset.new(@meta)
23 | expose_instance_variables
24 | end
25 |
26 | # This context is used by the DSL evaluation. Expose variables so user can use them in configset definitions.
27 | # Example:
28 | #
29 | # "/etc/cfn/hooks.d/cfn-auto-reloader.conf":
30 | # content:
31 | # Fn::Sub:
32 | # ...
33 | # path=Resources.<%= @resource %>.Metadata.AWS::CloudFormation::Init
34 | #
35 | def expose_instance_variables
36 | @name = @meta[:name]
37 | @resource = @meta[:resource]
38 | end
39 |
40 | def wrap_with_metadata(cloudformation_init)
41 | full = {"Metadata" => {}}
42 | full["Metadata"]["AWS::CloudFormation::Init"] = cloudformation_init["AWS::CloudFormation::Init"]
43 | full["Metadata"]["AWS::CloudFormation::Authentication"] = authentication if authentication # only on dsl
44 | full.deep_stringify_keys.dup # metadata
45 | end
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/lib/lono/builder/configset/definition/dsl.rb:
--------------------------------------------------------------------------------
1 | class Lono::Builder::Configset::Definition
2 | class Dsl < Base
3 | include Syntax
4 |
5 | def initialize(options={})
6 | super
7 | @current = "main" # current configset
8 | @tracked = []
9 | @structure = {} # holds in memory the configset hash structure to build AWS::CloudFormation::Init
10 | @command_counts = Hash.new(0)
11 | # Also support ability to add AWS::CloudFormation::Authentication
12 | @authentication = nil # holds IAM policy info to build AWS::CloudFormation::Authentication
13 | end
14 |
15 | def evaluate
16 | load_context
17 | evaluate_file(@configset.path)
18 | configsets = configsets || @tracked.uniq
19 | configsets = ["main"] if configsets.empty?
20 | configsets_structure = {"configSets" => {"default" => configsets}}.merge(@structure)
21 | cloudformation_init = { "AWS::CloudFormation::Init" => configsets_structure }
22 | wrap_with_metadata(cloudformation_init)
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/lib/lono/builder/configset/definition/dsl/syntax.rb:
--------------------------------------------------------------------------------
1 | class Lono::Builder::Configset::Definition::Dsl
2 | module Syntax
3 | include_modules "syntax"
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/lib/lono/builder/configset/definition/dsl/syntax/auth.rb:
--------------------------------------------------------------------------------
1 | module Lono::Builder::Configset::Definition::Dsl::Syntax
2 | module Auth
3 | def authentication(data=nil, force: false)
4 | if data.nil?
5 | authentication_reader
6 | else
7 | authentication_setter(data, force)
8 | end
9 | end
10 |
11 | # data can be either:
12 | #
13 | # 1. logical id - String
14 | # 2. Full AWS::CloudFormation::Authentication value structure
15 | #
16 | def authentication_reader
17 | # AWS::CloudFormation::Authentication
18 | case @authentication
19 | when String
20 | logical_id = @authentication
21 | {
22 | rolebased: {
23 | type: "S3",
24 | buckets: [lono_bucket_name],
25 | roleName: {Ref: logical_id}, # currently ref meth is not available
26 | }
27 | }
28 | when Hash
29 | @authentication
30 | end
31 | end
32 |
33 | def authentication_setter(data, force=false)
34 | @authentication = data unless @authentication || force
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/lib/lono/builder/configset/definition/dsl/syntax/content.rb:
--------------------------------------------------------------------------------
1 | module Lono::Builder::Configset::Definition::Dsl::Syntax
2 | module Content
3 | def content_file(path)
4 | content_path = "#{content_file_root}/content"
5 | file = "#{content_path}/#{path}"
6 | if File.exist?(file)
7 | IO.read(file)
8 | else
9 | "File not found: #{file}"
10 | end
11 | end
12 |
13 | private
14 | def content_file_root
15 | if @configset
16 | @configset.root
17 | else
18 | @blueprint.root
19 | end
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/lib/lono/builder/configset/definition/dsl/syntax/package.rb:
--------------------------------------------------------------------------------
1 | module Lono::Builder::Configset::Definition::Dsl::Syntax
2 | module Package
3 | # In recent versions of the AmazonLinux2 cfnbootstrap, , the package command doesnt work with updated versions of rubygems.
4 | # Get this error
5 | #
6 | # invalid option: --no-ri
7 | #
8 | # More details: https://gist.github.com/tongueroo/569878afdc7eb904490b9ee8b03f304f
9 | #
10 | # Found the cfnbootstrap version by looking at the source on 2020-03-21 in
11 | #
12 | # $ cat /usr/lib/python2.7/site-packages/cfnbootstrap/public_constants.py
13 | # _release = '31'
14 | # _version = '1.4-' + _release
15 | #
16 | # There is no way to get the version from the /opt/aws/bin/cfn-init command.
17 | #
18 | # We work around this be using the command instruction and use the gem install and list commands.
19 | #
20 | # $ gem list tilt -e -i -v 1.4.0
21 | # false # also $? is 1
22 | # $ gem list tilt -e -i -v 1.4.1
23 | # true # also $? is 0
24 | # $
25 | #
26 | def gem_package(name, version=nil)
27 | unless_clause = "gem list #{name} -e -i "
28 | unless_clause += "-v #{version}" if version
29 | command("#{name}-gem-install",
30 | command: "gem install #{name} #{version}",
31 | unless: unless_clause
32 | )
33 | end
34 |
35 | def yum_package(name, version=nil)
36 | versions = [version].compact
37 | package("yum", name => versions)
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/lono/builder/configset/definition/erb.rb:
--------------------------------------------------------------------------------
1 | class Lono::Builder::Configset::Definition
2 | class Erb < Base
3 | def evaluate
4 | load_context
5 | cloudformation_init = build
6 | wrap_with_metadata(cloudformation_init)
7 | end
8 |
9 | def build
10 | content = RenderMePretty.result(@configset.path, context: self)
11 | if File.extname(@configset.path) == ".yml"
12 | load_yaml(content)
13 | else
14 | JSON.load(content)
15 | end
16 | end
17 |
18 | def load_yaml(content)
19 | # init structure
20 | # Write to file so can use Yamler::Validator
21 | path = "/tmp/lono/configset.yml"
22 | FileUtils.mkdir_p(File.dirname(path))
23 | IO.write(path, content)
24 | Lono::Yamler::Validator.new(path).validate!
25 | Lono::Yamler::Loader.new(content).load
26 | end
27 |
28 | def authentication
29 | # noop
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/lib/lono/builder/configset/evaluator.rb:
--------------------------------------------------------------------------------
1 | # Dsl or Erb
2 | module Lono::Builder::Configset
3 | class Evaluator < Lono::CLI::Base
4 | def evaluate
5 | Registration.new(@blueprint).evaluate
6 | combiner = Combiner.new(@options.merge(metas: Registration.metas))
7 | combiner.combine
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/lib/lono/builder/configset/registration.rb:
--------------------------------------------------------------------------------
1 | module Lono::Builder::Configset
2 | class Registration
3 | include DslEvaluator
4 |
5 | def initialize(blueprint)
6 | @blueprint = blueprint
7 | end
8 |
9 | cattr_accessor :metas, default: []
10 | def metas
11 | self.class.metas
12 | end
13 |
14 | def evaluate
15 | path = "#{@blueprint.root}/configsets.rb" # plural
16 | evaluate_file(path)
17 | end
18 |
19 | # Only one syntax method so not in separate module.
20 | #
21 | # Register configset for later processing.
22 | # By registering them all up front, can aggregate errors and show them together
23 | # for a user-friendly experience.
24 | #
25 | # The configset method is different with configset registration vs configset definition
26 | #
27 | # definition: app/configsets/httpd/configset.rb
28 | # registration: app/blueprints/demo/configsets.rb
29 | #
30 | def configset(name, options={})
31 | found = metas.detect { |i| i[:name] == name && i[:resource] == options[:resource] }
32 | metas << options.merge(name: name) unless found
33 | end
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/lib/lono/builder/context.rb:
--------------------------------------------------------------------------------
1 | class Lono::Builder
2 | module Context
3 | include DslEvaluator
4 |
5 | def load_context
6 | load_helpers # load helpers before variable so user custom helpers are available in vars files
7 | load_variables
8 | end
9 |
10 | # Variables in base.rb are overridden by their environment specific variables
11 | # file. Example, file LONO_ENV=dev:
12 | #
13 | # config/vars/base.rb
14 | # config/vars/dev.rb - will override any variables in base.rb
15 | # config/vars/base.rb
16 | # config/vars/dev.rb - will override any variables in base.rb
17 | #
18 | def load_variables
19 | return if seed?
20 | layers = Lono::Layering::Layer.new(@blueprint, "vars").paths
21 | layers.each do |layer|
22 | evaluate_file(layer)
23 | end
24 | end
25 |
26 | # Load blueprint helpers
27 | # blueprint helpers override extension helpers
28 | def load_helpers
29 | load_helper_files("#{Lono.root}/vendor/helpers", type: :project)
30 | load_helper_files("#{Lono.root}/app/helpers", type: :project)
31 | load_helper_files("#{@blueprint.root}/helpers", type: :blueprint) # takes higher precedence
32 | end
33 |
34 | # Dont want any existing files to prevent building the blueprint.
35 | # This means that parameters cannot be based on vars. It's a trade-off.
36 | def seed?
37 | ARGV[0] == "seed"
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl.rb:
--------------------------------------------------------------------------------
1 | class Lono::Builder
2 | class Dsl < Lono::CLI::Base
3 | attr_reader :results
4 | def run
5 | logger.info "Building template" unless @options[:quiet]
6 | build_template
7 | end
8 |
9 | def build_template
10 | evaluator = Evaluator.new(@options)
11 | evaluator.evaluate
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/evaluator.rb:
--------------------------------------------------------------------------------
1 | class Lono::Builder::Dsl
2 | class Evaluator < Lono::CLI::Base
3 | include DslEvaluator
4 | include Lono::Builder::Context
5 | include Lono::Builder::Dsl::Syntax
6 | include Lono::Utils::Pretty
7 |
8 | def initialize(options={})
9 | super
10 | @template_path = "#{@blueprint.root}/template.rb"
11 | @parameters = [] # built by parameter_groups.rb
12 | @cfn = {}
13 | end
14 |
15 | def evaluate
16 | load_context
17 | evaluate_template_paths(@template_path) # modifies @cfn
18 | finalize
19 | to_yaml
20 | @cfn
21 | end
22 |
23 | def finalize
24 | o = @options.merge(parameters: @parameters)
25 | @cfn = Finalizer.new(@cfn, o).run
26 | end
27 |
28 | def to_yaml
29 | # https://stackoverflow.com/questions/24508364/how-to-emit-yaml-in-ruby-expanding-aliases
30 | # Trick to prevent YAML from emitting aliases
31 | @cfn = YAML.load(@cfn.to_json)
32 | end
33 |
34 | # Example path: /full/path/to/project/app/blueprints/demo/template.rb
35 | def evaluate_template_paths(path)
36 | ext = File.extname(path)
37 | folder = path.sub(ext, '')
38 | expr = "#{folder}/**/*.rb"
39 | evaluate_file(path) # process top-level template.rb first
40 | Dir.glob(expr).each do |path|
41 | evaluate_file(path)
42 | end
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/finalizer.rb:
--------------------------------------------------------------------------------
1 | class Lono::Builder::Dsl
2 | class Finalizer
3 | def initialize(cfn, options={})
4 | @cfn, @options = cfn, options
5 | end
6 |
7 | def run
8 | o = @options.merge(cfn: @cfn)
9 | @cfn = ParameterGroups.new(o).run
10 | @cfn = Configsets.new(o).run
11 | @cfn = Files.new(o).run
12 | @cfn
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/finalizer/base.rb:
--------------------------------------------------------------------------------
1 | class Lono::Builder::Dsl::Finalizer
2 | class Base < Lono::CLI::Base
3 | def initialize(options={})
4 | super
5 | @cfn = options[:cfn]
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/finalizer/configsets.rb:
--------------------------------------------------------------------------------
1 | class Lono::Builder::Dsl::Finalizer
2 | class Configsets < Base
3 | # Replaces metadata under each logical id resource.
4 | def run
5 | dsl = Lono::Builder::Configset::Evaluator.new(@options.merge(cfn: @cfn))
6 | metadata_map = dsl.evaluate
7 | metadata_map.each do |logical_id, cs|
8 | resource = @cfn["Resources"][logical_id]
9 | unless resource
10 | puts "WARN: Resources.#{logical_id} not found in the template. Are you sure you specified the correct resource logical id in your configsets.rb?".color(:yellow)
11 | next
12 | end
13 |
14 | resource["Metadata"] = cs["Metadata"]
15 | end
16 |
17 | @cfn
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/finalizer/files.rb:
--------------------------------------------------------------------------------
1 | class Lono::Builder::Dsl::Finalizer
2 | class Files < Base
3 | # Replaces metadata under each logical id resource.
4 | def run
5 | Build.new(@options).run
6 | @cfn = Replace.new(@options).run
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/finalizer/files/base.rb:
--------------------------------------------------------------------------------
1 | class Lono::Builder::Dsl::Finalizer::Files
2 | class Base < Lono::Builder::Dsl::Finalizer::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/finalizer/files/build.rb:
--------------------------------------------------------------------------------
1 | class Lono::Builder::Dsl::Finalizer::Files
2 | class Build < Base
3 | def initialize(options={})
4 | super
5 | @output_path = "#{Lono.root}/.output/files"
6 | end
7 |
8 | def run
9 | return if Lono::Files.empty?
10 | logger.debug "Building files"
11 | clean
12 | validate!
13 | build_files
14 | end
15 |
16 | def build_files
17 | Lono::Files.files.each do |file| # using singular file, but is like a "file_list"
18 | file.build
19 | file.build_lambda_layer(@cfn)
20 | file.compress
21 | # UploadNote: Instead of part of the build process here, uploading happen as part
22 | # of create_stack or execute_change_set after user confirms action. IE:
23 | # Cfn::Deploy#create and Cfn::Deploy#update
24 | end
25 | end
26 |
27 | def clean
28 | FileUtils.rm_rf(@output_path)
29 | end
30 |
31 | def validate!
32 | missing = Lono::Files.files.select do |file|
33 | !File.exist?(file.full_path)
34 | end
35 | return if missing.empty?
36 |
37 | logger.info "ERROR: These files are missing".color(:red)
38 | logger.info <<~EOL
39 | The file helper is calling them.
40 | They need to exist to be uploaded to s3.
41 | EOL
42 | missing.each do |file|
43 | logger.info " #{pretty_path(file.full_path)}"
44 | logger.info " Called at #{file.call_line}"
45 | end
46 | logger.info "Please double check that they exist."
47 | exit 1
48 | end
49 | end
50 | end
51 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/finalizer/files/replace.rb:
--------------------------------------------------------------------------------
1 | class Lono::Builder::Dsl::Finalizer::Files
2 | class Replace < Base
3 | def run
4 | replacements.each do |placeholder, replacement|
5 | update_template!(@cfn)
6 | end
7 | @cfn
8 | end
9 |
10 | def replacements
11 | data = Lono::Files.files.inject({}) do |result, file|
12 | result.merge(file.marker => file.s3_key)
13 | end
14 | # Edge case when bucket is created for the first time and files_bucket is
15 | # not available yet. So we call it at this point.
16 | Lono::S3::Bucket.ensure_exist
17 | data["LONO://S3_BUCKET"] = Lono::S3::Bucket.name
18 | data
19 | end
20 |
21 | def update_template!(hash)
22 | hash.each do |k, v|
23 | if v.is_a?(String)
24 | if v =~ %r{^LONO://}
25 | v.replace(replacements[v]) # replace the placeholder
26 | end
27 | elsif v.is_a?(Hash)
28 | update_template!(v) # recurse
29 | elsif v.is_a?(Array)
30 | v.each { |x| update_template!(x) if x.is_a?(Hash) }
31 | end
32 | end
33 | hash
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/finalizer/parameter_groups.rb:
--------------------------------------------------------------------------------
1 | class Lono::Builder::Dsl::Finalizer
2 | class ParameterGroups < Base
3 | extend Memoist
4 | include Lono::Builder::Util::Stringify
5 |
6 | def initialize(options={})
7 | super
8 | @parameters = options[:parameters]
9 | end
10 |
11 | def run
12 | return @cfn if parameter_groups.empty?
13 | @cfn["Metadata"] ||= {}
14 | @cfn["Metadata"]["AWS::CloudFormation::Interface"] = stringify!(interface)
15 | @cfn
16 | end
17 |
18 | def interface
19 | interface = {}
20 | parameter_groups.each do |group_label, parameters|
21 | group = {
22 | Label: { default: group_label},
23 | Parameters: parameters,
24 | }
25 | interface[:ParameterGroups] ||= []
26 | interface[:ParameterGroups] << group
27 | end
28 | parameter_labels.each do |name, label|
29 | l = { name => { default: label } }
30 | interface[:ParameterLabels] ||= {}
31 | interface[:ParameterLabels].merge!(l)
32 | end
33 | interface
34 | end
35 |
36 | def parameter_groups
37 | parameter_groups = {}
38 | @parameters.each do |p|
39 | next unless p.group_label
40 | parameter_groups[p.group_label] ||= []
41 | parameter_groups[p.group_label] << p.name
42 | end
43 | parameter_groups
44 | end
45 | memoize :parameter_groups
46 |
47 | def parameter_labels
48 | parameter_labels = {}
49 | @parameters.each do |p|
50 | next unless p.label
51 | parameter_labels[p.name] = p.label
52 | end
53 | parameter_labels
54 | end
55 | memoize :parameter_labels
56 | end
57 | end
58 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/helpers.rb:
--------------------------------------------------------------------------------
1 | class Lono::Builder::Dsl
2 | module Helpers
3 | include_modules("helpers")
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/helpers/ec2.rb:
--------------------------------------------------------------------------------
1 | module Lono::Builder::Dsl::Helpers
2 | module Ec2
3 | extend Memoist
4 |
5 | # Returns vpc object
6 | def vpc(name)
7 | filters = name == "default" ?
8 | [name: "isDefault", values: ["true"]] :
9 | [name: "tag:Name", values: [name]]
10 | resp = ec2.describe_vpcs(filters: filters)
11 | resp.vpcs.first
12 | end
13 | memoize :vpc
14 |
15 | def default_vpc
16 | vpc = vpc("default")
17 | vpc ? vpc.vpc_id : "no default vpc found"
18 | end
19 |
20 | def default_vpc_cidr
21 | vpc = vpc("default")
22 | vpc.cidr_block
23 | end
24 |
25 | def default_subnets
26 | return "no default subnets because no default vpc found" if default_vpc == "no default vpc found"
27 | resp = ec2.describe_subnets(filters: [name: "vpc-id", values: [default_vpc]])
28 | subnets = resp.subnets
29 | subnets.map(&:subnet_id)
30 | end
31 | memoize :default_subnets
32 |
33 | def key_pairs(regexp=nil)
34 | resp = ec2.describe_key_pairs
35 | key_names = resp.key_pairs.map(&:key_name)
36 | key_names.select! { |k| k =~ regexp } if regexp
37 | key_names
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/helpers/files.rb:
--------------------------------------------------------------------------------
1 | module Lono::Builder::Dsl::Helpers
2 | module Files
3 | def files(path, o={})
4 | Lono::Files.register(@options.merge(path: path).merge(o))
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/helpers/s3.rb:
--------------------------------------------------------------------------------
1 | module Lono::Builder::Dsl::Helpers
2 | module S3
3 | def s3_bucket
4 | Lono::S3::Bucket.name
5 | end
6 | alias_method :lono_bucket, :s3_bucket
7 |
8 | # Edge case when bucket is created for the first time and files_bucket is
9 | # not available yet to be use by Finalizer::Files::Replace#replacements
10 | def files_bucket
11 | "LONO://S3_BUCKET"
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/helpers/ssm.rb:
--------------------------------------------------------------------------------
1 | module Lono::Builder::Dsl::Helpers
2 | module Ssm
3 | extend Memoist
4 |
5 | def ssm(name)
6 | ssm_fetcher.get(name)
7 | end
8 |
9 | def ssm_fetcher
10 | Fetcher.new
11 | end
12 | memoize :ssm_fetcher
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/helpers/ssm/fetcher.rb:
--------------------------------------------------------------------------------
1 | require 'aws-sdk-ssm'
2 |
3 | module Lono::Builder::Dsl::Helpers::Ssm
4 | class Fetcher
5 | extend Memoist
6 | include Lono::Utils::Logging
7 |
8 | def get(name)
9 | fetch_ssm_value(name)
10 | end
11 |
12 | def fetch_ssm_value(name)
13 | resp = ssm.get_parameter(name: name, with_decryption: true)
14 | resp.parameter.value
15 | rescue Aws::SSM::Errors::ParameterNotFound
16 | logger.warn 'WARN: SSM-PARAM-NOT-FOUND'
17 | nil
18 | end
19 |
20 | def ssm
21 | Aws::SSM::Client.new
22 | end
23 | memoize :ssm
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/helpers/stack.rb:
--------------------------------------------------------------------------------
1 | module Lono::Builder::Dsl::Helpers
2 | module Stack
3 | extend Memoist
4 | include Lono::AwsServices
5 |
6 | def stack_output(name)
7 | stack_name, key = name.split(".")
8 | stack_name = stack_name(stack_name)
9 | resp = describe_stacks(stack_name: stack_name)
10 | stack = resp.stacks.first
11 | if stack
12 | o = stack.outputs.detect { |h| h.output_key == key }
13 | end
14 |
15 | if o
16 | o.output_value
17 | else
18 | logger.info "WARN: STACK OUTPUT NOT FOUND: output #{key} for stack #{stack_name}"
19 | nil
20 | end
21 | end
22 |
23 | def stack_resource(name)
24 | stack_name, logical_id = name.split(".")
25 | stack_name = stack_name(stack_name)
26 | resp = describe_stack_resources(stack_name: stack_name)
27 | resources = resp.stack_resources
28 | resource = resources.find { |r| r.logical_resource_id == logical_id }
29 | if resource
30 | resource.physical_resource_id
31 | else
32 | logger.info "WARN: NOT FOUND: logical_id #{logical_id} for stack #{stack_name}"
33 | nil
34 | end
35 | end
36 |
37 | def stack_name(blueprint)
38 | names = Lono::Names.new(blueprint: blueprint)
39 | pattern = blueprint # blueprint can be a pattern provided by user. IE: :BLUEPRINT-:ENV
40 | names.expansion(pattern)
41 | end
42 |
43 | private
44 | def describe_stacks(options={})
45 | cfn.describe_stacks(options)
46 | end
47 | memoize :describe_stacks
48 |
49 | def describe_stack_resources(options={})
50 | cfn.describe_stack_resources(options)
51 | end
52 | memoize :describe_stack_resources
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/helpers/tags.rb:
--------------------------------------------------------------------------------
1 | module Lono::Builder::Dsl::Helpers
2 | module Tags
3 | def tags(hash={})
4 | if hash.empty?
5 | tag_list(@tags) if @tags # when hash is empty, use @tags variable. If not set then return nil
6 | else
7 | tag_list(hash)
8 | end
9 | end
10 |
11 | def tag_list(hash)
12 | raise "tags hash cannot be empty" if hash == nil
13 |
14 | if hash.is_a?(Array)
15 | hash = hash.inject({}) do |h,i|
16 | i.symbolize_keys!
17 | h[i[:Key]] = i[:Value]
18 | h
19 | end
20 | return tag_list(hash) # recursive call tag_list to normalized the argument with a Hash
21 | end
22 |
23 | propagate = hash[:PropagateAtLaunch] # special treatment
24 | list = hash.map do |k,v|
25 | h = {Key: k.to_s, Value: v}
26 | h[:PropagateAtLaunch] = propagate unless propagate.nil?
27 | h
28 | end
29 | list.reject { |h| h[:Key] == "PropagateAtLaunch" }
30 | end
31 |
32 | def dimensions(hash)
33 | tag_list(hash).map { |h|
34 | h[:Name] = h.delete(:Key) || h.delete(:key)
35 | h
36 | }
37 | end
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/syntax.rb:
--------------------------------------------------------------------------------
1 | # Encapsulates syntax methods so they can be included in both the Evaluator and Context scope
2 | class Lono::Builder::Dsl
3 | module Syntax
4 | include Fn
5 | include Core
6 | include ParameterGroup
7 | include Helpers
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/syntax/core/base.rb:
--------------------------------------------------------------------------------
1 | module Lono::Builder::Dsl::Syntax::Core
2 | class Base
3 | include Lono::Builder::Util::Stringify
4 | include Lono::Builder::Dsl::Syntax::Fn
5 |
6 | def initialize(blueprint, *definition)
7 | @blueprint = blueprint
8 | @definition = definition.flatten
9 | end
10 |
11 | private
12 | def camelize(attributes)
13 | data = stringify!(attributes)
14 | clean(data)
15 | end
16 |
17 | # Remove items with nil value automatically
18 | def clean(data)
19 | Squeezer.new(data).squeeze
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/syntax/core/condition.rb:
--------------------------------------------------------------------------------
1 | # Implements:
2 | #
3 | # template - uses @definition to build a CloudFormation template section
4 | #
5 | module Lono::Builder::Dsl::Syntax::Core
6 | class Condition < Base
7 | def template
8 | camelize(standarize(@definition))
9 | end
10 |
11 | # There are only 2 forms for condition: long and medium
12 | #
13 | # Type is the only required property: https://amzn.to/2x8W5aD
14 | def standarize(definition)
15 | first, second, _ = definition
16 | if definition.size == 1 && first.is_a?(Hash) # long form
17 | first # pass through
18 | elsif definition.size == 2 && second.is_a?(Hash) # medium form
19 | logical_id, properties = first, second
20 | { logical_id => properties }
21 | else # I dont know what form
22 | raise "Invalid form provided. definition #{definition.inspect}"
23 | end
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/syntax/core/mapping.rb:
--------------------------------------------------------------------------------
1 | # Implements:
2 | #
3 | # template - uses @definition to build a CloudFormation template section
4 | #
5 | module Lono::Builder::Dsl::Syntax::Core
6 | class Mapping < Base
7 | def template
8 | camelize(standarize(@definition))
9 | end
10 |
11 | # Type is the only required property: https://amzn.to/2x8W5aD
12 | def standarize(definition)
13 | first, second, _ = definition
14 | if definition.size == 1 && first.is_a?(Hash) # long form
15 | first # pass through
16 | elsif definition.size == 2 && second.is_a?(Hash) # medium form
17 | logical_id, maps = first, second
18 | { logical_id => maps }
19 | else # I dont know what form
20 | raise "Invalid form provided. definition #{definition.inspect}"
21 | end
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/syntax/core/output.rb:
--------------------------------------------------------------------------------
1 | # Implements:
2 | #
3 | # template - uses @definition to build a CloudFormation template section
4 | #
5 | module Lono::Builder::Dsl::Syntax::Core
6 | class Output < Base
7 | def template
8 | camelize(standarize(@definition))
9 | end
10 |
11 | # Value is the only required property: https://amzn.to/2xbhmk3
12 | def standarize(definition)
13 | first, second, _ = definition
14 | if definition.size == 1 && first.is_a?(Hash) # long form
15 | first # pass through
16 | elsif definition.size == 2 && second.is_a?(Hash) # medium form
17 | if second.key?(:value) || second.key?("Value")
18 | logical_id, properties = first, second
19 | else
20 | logical_id = first
21 | properties = {Value: second}
22 | end
23 | { logical_id => properties }
24 | elsif definition.size == 2 && (second.is_a?(String) || second.nil?) # short form
25 | logical_id = first
26 | properties = second.is_a?(String) ? { Value: second } : {}
27 | { logical_id => properties }
28 | elsif definition.size == 1
29 | logical_id = first.to_s
30 | properties = { Value: ref(logical_id) }
31 | { logical_id => properties }
32 | else # I dont know what form
33 | raise "Invalid form provided. definition #{definition.inspect}"
34 | end
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/syntax/core/resource.rb:
--------------------------------------------------------------------------------
1 | # Implements:
2 | #
3 | # template - uses @definition to build a CloudFormation template section
4 | #
5 | module Lono::Builder::Dsl::Syntax::Core
6 | class Resource < Base
7 | def template
8 | camelize(standarize(@definition))
9 | end
10 |
11 | # Type is the only required property: https://amzn.to/2x8W5aD
12 | def standarize(definition)
13 | first, second, third, _ = definition
14 | if definition.size == 1 && first.is_a?(Hash) # long form
15 | first # pass through
16 | elsif definition.size == 2 && second.is_a?(Hash) # medium form
17 | logical_id, attributes = first, second
18 | attributes.delete(:properties) if attributes[:properties].blank?
19 | attributes.delete("Properties") if attributes["Properties"].blank?
20 | { logical_id => attributes }
21 | elsif definition.size == 2 && second.is_a?(String) # short form with no properties
22 | logical_id, type = first, second
23 | { logical_id => {
24 | Type: type
25 | }}
26 | elsif definition.size == 3 && (second.is_a?(String) || second.is_a?(NilClass)) # short form
27 | logical_id, type, properties = first, second, third
28 | resource = { logical_id => {
29 | Type: type
30 | }}
31 |
32 | attributes = resource.values.first
33 |
34 | property_mover = PropertyMover.new(resource, logical_id, properties)
35 | property_mover.move!
36 |
37 | attributes["Properties"] = properties unless properties.empty?
38 | resource
39 | else # Dont understand this form
40 | raise "Invalid form provided. definition #{definition.inspect}"
41 | end
42 | end
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/syntax/core/resource/property_mover.rb:
--------------------------------------------------------------------------------
1 | class Lono::Builder::Dsl::Syntax::Core::Resource
2 | # Moves the property to the top-level attributes *destructively*
3 | class PropertyMover
4 | def initialize(resource, logical_id, properties)
5 | @resource, @logical_id, @properties = resource, logical_id, properties
6 | end
7 |
8 | # AWS Docs: Resource attribute reference
9 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-product-attribute-reference.html
10 | def move!
11 | %w[
12 | Condition
13 | CreationPolicy
14 | DeletionPolicy
15 | DependsOn
16 | Metadata
17 | UpdatePolicy
18 | UpdateReplacePolicy
19 | ].each do |attribute_name|
20 | # Account for camelize, underscore, String, and Symbol
21 | move(attribute_name.to_sym)
22 | move(attribute_name.camelize.to_sym)
23 | move(attribute_name)
24 | move(attribute_name.camelize)
25 | end
26 | end
27 |
28 | def move(attribute_name)
29 | attribute_value = @properties.delete(attribute_name)
30 | @resource[@logical_id][attribute_name] = attribute_value if attribute_value
31 | end
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/syntax/core/section.rb:
--------------------------------------------------------------------------------
1 | # Implements:
2 | #
3 | # template - uses @definition to build a CloudFormation template section
4 | #
5 | module Lono::Builder::Dsl::Syntax::Core
6 | class Section < Base
7 | def template
8 | hash, _ = @definition
9 | camelize(hash)
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/syntax/core/squeezer.rb:
--------------------------------------------------------------------------------
1 | module Lono::Builder::Dsl::Syntax::Core
2 | class Squeezer
3 | def initialize(data)
4 | @data = data
5 | end
6 |
7 | def squeeze(new_data=nil, previous_key=nil)
8 | data = new_data.nil? ? @data : new_data
9 |
10 | case data
11 | when Array
12 | # .compact prevents infinite loop when data = [nil] on accident
13 | # IE: data[:key] = [nil]
14 | data.compact.map! { |v| squeeze(v) }
15 | when Hash
16 | data.each_with_object({}) do |(k,v), squeezed|
17 | # only remove nil and empty Array values within Hash structures
18 | squeezed[k] = squeeze(v, k) unless v.nil? # || empty_array?(v, previous_key)
19 | squeezed
20 | end
21 | else
22 | data # do not transform
23 | end
24 | end
25 |
26 | # Special case do not squeeze empty Array when previous_key is "yum". Handles:
27 | #
28 | # Metadata:
29 | # AWS::CloudFormation::Init:
30 | # config:
31 | # packages:
32 | # yum:
33 | # httpd: []
34 | #
35 | def empty_array?(v, previous_key)
36 | return false if previous_key.to_s == "yum"
37 | v.is_a?(Array) && v.empty?
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/lono/builder/dsl/syntax/parameter_group.rb:
--------------------------------------------------------------------------------
1 | module Lono::Builder::Dsl::Syntax
2 | module ParameterGroup
3 | def parameter_group(label)
4 | @group_label = label
5 | yield
6 | @group_label = nil
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/lono/builder/template.rb:
--------------------------------------------------------------------------------
1 | class Lono::Builder
2 | class Template < Lono::CLI::Base
3 | extend Memoist
4 |
5 | def run
6 | check_blueprint_exist!
7 | data = dsl.run # The generator strategy classes write templates to disk
8 | write_output(data)
9 | end
10 |
11 | private
12 | def check_blueprint_exist!
13 | return if @blueprint.exist?
14 | logger.error "ERROR: Blueprint #{@blueprint.name} not found.".color(:red)
15 | logger.error <<~EOL
16 | Searched:
17 |
18 | app/blueprints
19 | vendor/blueprints
20 |
21 | Are you sure it exists? To check, you can run:
22 |
23 | lono list
24 |
25 | EOL
26 | exit 1
27 | end
28 |
29 | def write_output(data)
30 | path = @blueprint.output_path
31 | ensure_parent_dir(path)
32 | text = YAML.dump(data)
33 | IO.write(path, text)
34 |
35 | Lono::Yamler::Validator.new(path).validate!
36 |
37 | unless @options[:quiet]
38 | pretty_path = path.sub("#{Lono.root}/",'')
39 | logger.info " #{pretty_path}"
40 | end
41 | end
42 |
43 | def ensure_parent_dir(path)
44 | dir = File.dirname(path)
45 | FileUtils.mkdir_p(dir) unless File.exist?(dir)
46 | end
47 |
48 | def dsl
49 | Dsl.new(@options)
50 | end
51 | memoize :dsl
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/lib/lono/builder/template/aws_service.rb:
--------------------------------------------------------------------------------
1 | require "aws-sdk-s3"
2 |
3 | class Lono::Builder::Template::AwsService
4 | def s3
5 | return @s3 if @s3
6 |
7 | options = {}
8 | endpoint = ENV['S3_ENDPOINT'] if ENV['S3_ENDPOINT']
9 | options[:endpoint] = endpoint if endpoint
10 | if options[:endpoint]
11 | options[:region] = options[:endpoint].split('.')[1]
12 | end
13 | @s3 = Aws::S3::Client.new(options)
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/lono/builder/template/bashify.rb:
--------------------------------------------------------------------------------
1 | require 'open-uri'
2 |
3 | class Lono::Builder::Template
4 | class Bashify
5 | include Lono::Utils::Logging
6 |
7 | def initialize(options={})
8 | @options = options
9 | @path = options[:path]
10 | end
11 |
12 | def user_data_paths(data,path="")
13 | paths = []
14 | paths << path
15 | data.each do |key,value|
16 | if value.is_a?(Hash)
17 | paths += user_data_paths(value,"#{path}/#{key}")
18 | else
19 | paths += ["#{path}/#{key}"]
20 | end
21 | end
22 | paths.select {|p| p =~ /UserData/ && p =~ /Fn::Join/ }
23 | end
24 |
25 | def run
26 | raw = open(@path).read
27 | json = JSON.load(raw)
28 | paths = user_data_paths(json)
29 | if paths.empty?
30 | logger.info "No UserData script found"
31 | return
32 | end
33 | paths.each do |path|
34 | logger.info "UserData script for #{path}:"
35 | key = path.sub('/','').split("/").map {|x| "['#{x}']"}.join('')
36 | user_data = eval("json#{key}")
37 | delimiter = user_data[0]
38 | script = user_data[1]
39 | logger.info script.join(delimiter)
40 | end
41 | end
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/lib/lono/builder/template/upload.rb:
--------------------------------------------------------------------------------
1 | class Lono::Builder::Template
2 | class Upload < Lono::CLI::Base
3 | def run
4 | path = "#{Lono.root}/output/#{@blueprint.name}/template.yml"
5 | Lono::S3::Uploader.new(path).upload
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/lib/lono/builder/util/stringify.rb:
--------------------------------------------------------------------------------
1 | module Lono::Builder::Util
2 | module Stringify
3 | # Accounts for Arrays also. ActiveSupport only works for Hashes.
4 | def stringify!(data)
5 | case data
6 | when Array
7 | data.map! { |i| stringify!(i) }
8 | when Hash
9 | data.deep_transform_keys! { |k| k.to_s }
10 | else
11 | data # do not transform
12 | end
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/lono/bundler/cli.rb:
--------------------------------------------------------------------------------
1 | module Lono::Bundler
2 | class CLI < Lono::Command
3 | desc "bundle SUBCOMMAND", "bundle subcommands"
4 | long_desc Help.text(:bundle)
5 | subcommand "bundle", Bundle
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/lib/lono/bundler/cli/base.rb:
--------------------------------------------------------------------------------
1 | class Lono::Bundler::CLI
2 | class Base
3 | include LB::Util::Logging
4 | include LB::Util::Sure
5 |
6 | def initialize(options={})
7 | @options = options
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/lib/lono/bundler/cli/bundle.rb:
--------------------------------------------------------------------------------
1 | class Lono::Bundler::CLI
2 | class Bundle < Lono::Command
3 | lonofile_option = Proc.new {
4 | option :lonofile, default: ENV['LONOFILE'] || "Lonofile", desc: "Lonofile to use"
5 | }
6 |
7 | desc "list", "List bundled blueprints included by Lonofile"
8 | long_desc Help.text("bundle/list")
9 | lonofile_option.call
10 | def list
11 | Lono::Bundler::List.new(options).run
12 | end
13 |
14 | desc "info COMPONENT", "Provide info about a bundled component"
15 | long_desc Help.text("bundle/info")
16 | lonofile_option.call
17 | option :type, aliases: :t, desc: "Type. IE: blueprint, configset, extension"
18 | def info(component)
19 | Lono::Bundler::Info.new(options.merge(component: component)).run
20 | end
21 |
22 | desc "install", "Install blueprints from the Lonofile"
23 | long_desc Help.text("bundle/install")
24 | lonofile_option.call
25 | def install
26 | Lono::Bundler::Runner.new(options).run
27 | end
28 |
29 | desc "clean", "Clean cache"
30 | long_desc Help.text("bundle/clean")
31 | option :yes, aliases: :y, type: :boolean, desc: "bypass are you sure prompt"
32 | def clean
33 | Clean.new(options).run
34 | end
35 |
36 | desc "update [COMPONENT]", "Update bundled blueprints"
37 | long_desc Help.text("bundle/update")
38 | lonofile_option.call
39 | def update(*components)
40 | Lono::Bundler.update_mode = true
41 | Lono::Bundler::Runner.new(options.merge(components: components)).run
42 | end
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/lib/lono/bundler/cli/clean.rb:
--------------------------------------------------------------------------------
1 | class Lono::Bundler::CLI
2 | class Clean < Base
3 | include LB::Component::Concerns::PathConcern
4 |
5 | def run
6 | paths = [tmp_root]
7 | are_you_sure?(paths)
8 | paths.each do |path|
9 | FileUtils.rm_rf(path)
10 | logger.info "Removed #{path}"
11 | end
12 |
13 | end
14 |
15 | def are_you_sure?(paths)
16 | pretty_paths = paths.map { |p| " #{p}" }.join("\n")
17 | message = <<~EOL.chomp
18 | Will remove these folders and all their files:
19 |
20 | #{pretty_paths}
21 |
22 | Are you sure?
23 | EOL
24 | sure?(message) # from Util::Sure
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/lib/lono/bundler/cli/help.rb:
--------------------------------------------------------------------------------
1 | class Lono::Bundler::CLI
2 | module Help
3 | class << self
4 | def text(namespaced_command)
5 | path = namespaced_command.to_s.gsub(':','/')
6 | path = File.expand_path("../help/#{path}.md", __FILE__)
7 | IO.read(path) if File.exist?(path)
8 | end
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/lib/lono/bundler/cli/help/bundle.md:
--------------------------------------------------------------------------------
1 | ## Examples
2 |
3 | lono bundle
4 |
--------------------------------------------------------------------------------
/lib/lono/bundler/component.rb:
--------------------------------------------------------------------------------
1 | module Lono::Bundler
2 | class Component
3 | extend Memoist
4 | extend Props::Extension
5 | props :export_to, :name, :sha, :source, :subfolder, :source_type, :type, :url, :clone_with
6 | delegate :repo, :org, :repo_folder, :org_folder, to: :org_repo
7 |
8 | include Concerns::StackConcern
9 | include Concerns::LocalConcern
10 |
11 | attr_reader :props, :version, :ref, :tag, :branch
12 | def initialize(options={})
13 | @props = Component::Props.new(options.deep_symbolize_keys).build
14 | # These props are used for version comparing by VersionComparer
15 | @version, @ref, @tag, @branch = @props[:version], @props[:ref], @props[:tag], @props[:branch]
16 | end
17 |
18 | # support variety of options, prefer version
19 | def checkout_version
20 | @version || @ref || @tag || @branch
21 | end
22 |
23 | def latest_sha
24 | fetcher = Fetcher.new(self).instance
25 | fetcher.run
26 | fetcher.sha
27 | end
28 |
29 | def vcs_provider
30 | if url.include?('http')
31 | # "https://github.com/org/repo" => github.com
32 | url.match(%r{http[s]?://(.*?)/})[1]
33 | elsif url.include?('http') # git@
34 | # "git@github.com:org/repo" => github.com
35 | url.match(%r{git@(.*?):})[1]
36 | else # ssh://user@domain.com/path/to/repo
37 | 'none'
38 | end
39 | end
40 |
41 | def export_path
42 | export_to = self.export_to || LB.config.export_to
43 | "#{export_to}/#{type.pluralize}/#{name}"
44 | end
45 |
46 | private
47 | def org_repo
48 | OrgRepo.new(url)
49 | end
50 | memoize :org_repo
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/lib/lono/bundler/component/concerns/local_concern.rb:
--------------------------------------------------------------------------------
1 | module Lono::Bundler::Component::Concerns
2 | module LocalConcern
3 | def local?
4 | source.starts_with?("/") ||
5 | source.starts_with?(".") ||
6 | source.starts_with?("..") ||
7 | source.starts_with?("~")
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/lib/lono/bundler/component/concerns/notation_concern.rb:
--------------------------------------------------------------------------------
1 | require 'uri'
2 |
3 | module Lono::Bundler::Component::Concerns
4 | module NotationConcern
5 | def remove_notations(source)
6 | remove_subfolder_notation(remove_ref_notation(source))
7 | end
8 |
9 | def remove_ref_notation(source)
10 | source.sub(/\?.*/,'')
11 | end
12 |
13 | def remove_subfolder_notation(source)
14 | clean = clean_notation(source)
15 | parts = clean.split('//')
16 | if parts.size == 2 # has subfolder
17 | source.split('//')[0..-2].join('//') # remove only subfolder, keep rest of original source
18 | else
19 | source
20 | end
21 | end
22 |
23 | def subfolder(source)
24 | clean = clean_notation(source)
25 | parts = clean.split('//')
26 | if parts.size == 2 # has subfolder
27 | remove_ref_notation(parts.last)
28 | end
29 | end
30 |
31 | def ref(source)
32 | url = clean_notation(source)
33 | uri = URI(url)
34 | if uri.query
35 | params = URI::decode_www_form(uri.query).to_h # if you are in 2.1 or later version of Ruby
36 | params['ref']
37 | end
38 | end
39 |
40 |
41 | def clean_notation(source)
42 | clean = source
43 | .sub(/.*::/,'') # remove git::
44 | .sub(%r{http[s?]://},'') # remove https://
45 | .sub(%r{git@(.*?):},'') # remove git@word
46 | remove_host(clean)
47 | end
48 |
49 | def remove_host(clean)
50 | return clean unless clean.include?('ssh://')
51 | if clean.count(':') == 1
52 | uri = URI(clean)
53 | uri.path
54 | else
55 | clean.split(':').last
56 | end
57 | end
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/lib/lono/bundler/component/fetcher.rb:
--------------------------------------------------------------------------------
1 | # Delegates to:
2 | #
3 | # 1. Local
4 | # 2. Git
5 | #
6 | class Lono::Bundler::Component
7 | class Fetcher
8 | def initialize(component)
9 | @component = component
10 | end
11 |
12 | def instance
13 | type = @component.source_type == "registry" ? "git" : @component.source_type
14 | klass = "Lono::Bundler::Component::Fetcher::#{type.camelize}".constantize
15 | klass.new(@component) # IE: Local.new(@component), Git.new(@component), S3.new(@component), etc
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/lono/bundler/component/fetcher/base.rb:
--------------------------------------------------------------------------------
1 | # Interface of subclasses should implement
2 | #
3 | # run
4 | # switch_version(component.sha)
5 | # sha
6 | #
7 | class Lono::Bundler::Component::Fetcher
8 | class Base
9 | include LB::Util::Git
10 | include LB::Util::Logging
11 | include LB::Component::Concerns::PathConcern
12 |
13 | attr_reader :sha # returns nil for Local
14 | def initialize(component)
15 | @component = component
16 | end
17 |
18 | def switch_version(*)
19 | # noop
20 | end
21 |
22 | def extract(archive, dest)
23 | Lono::Bundler::Extract.extract(archive, dest)
24 | end
25 |
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/lib/lono/bundler/component/fetcher/local.rb:
--------------------------------------------------------------------------------
1 | class Lono::Bundler::Component::Fetcher
2 | class Local < Base
3 | def run
4 | stage_path = stage_path(rel_dest_dir)
5 | source = @component.source
6 | src = source.sub(/^~/, ENV['HOME']) # allow ~/ notation
7 | FileUtils.rm_rf(stage_path)
8 | FileUtils.mkdir_p(File.dirname(stage_path))
9 | logger.debug "Local: cp -r #{src} #{stage_path}"
10 | # copy from stage area to vendor/blueprints/NAME
11 | # IE: cp -r /tmp/lono/bundler/stage/local/local1 vendor/blueprints/local1
12 | FileUtils.cp_r(src, stage_path)
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/lono/bundler/component/http/concern.rb:
--------------------------------------------------------------------------------
1 | require 'net/http'
2 | require 'open-uri'
3 |
4 | module Lono::Bundler::Component::Http
5 | module Concern
6 | include LB::Util::Logging
7 |
8 | def http_request(url, auth_domain: nil)
9 | uri = URI(url)
10 | http = Net::HTTP.new(uri.host, uri.port)
11 | http.use_ssl = uri.scheme == "https"
12 | # Total time will be 40s = 20 x 2
13 | http.max_retries = 1 # Default is already 1, just being explicit
14 | http.read_timeout = 20 # Sites that dont return in 20 seconds are considered down
15 | request = Net::HTTP::Get.new(uri)
16 |
17 | if auth_domain
18 | path = "#{ENV['HOME']}/.terraform.d/credentials.tfrc.json"
19 | if File.exist?(path)
20 | data = JSON.load(IO.read(path))
21 | token = data['credentials'][auth_domain]['token']
22 | request.add_field 'Authorization', "Bearer #{token}"
23 | else
24 | auth_error_exit!
25 | end
26 | end
27 |
28 | begin
29 | http.request(request) # response
30 | rescue Net::OpenTimeout => e # internal ELB but VPC is not configured for Lambda function
31 | http_request_error_message(e)
32 | rescue Exception => e
33 | # Net::ReadTimeout - too slow
34 | # Errno::ECONNREFUSED - completely down
35 | # SocketError - improper url "dsfjsl" instead of example.com
36 | http_request_error_message(e)
37 | end
38 | end
39 |
40 | def http_request_error_message(e)
41 | logger.info "ERROR: #{e.message}\n#{e.message}".color(:red)
42 | exit 1
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/lib/lono/bundler/component/http/source.rb:
--------------------------------------------------------------------------------
1 | require 'net/http'
2 | require 'open-uri'
3 |
4 | module Lono::Bundler::Component::Http
5 | class Source
6 | include LB::Util::Logging
7 | include Concern
8 |
9 | def initialize(params)
10 | @params = params
11 | @options = params[:options]
12 | @source = @options[:source]
13 | end
14 |
15 | def url
16 | @source
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/lono/bundler/component/props/extension.rb:
--------------------------------------------------------------------------------
1 | class Lono::Bundler::Component::Props
2 | module Extension
3 | def props(*names)
4 | names.each { |n| prop(n) }
5 | end
6 |
7 | def prop(name)
8 | name = name.to_sym
9 | define_method(name) do
10 | @props[name]
11 | end
12 |
13 | define_method("#{name}=") do |v|
14 | @props[name] = v
15 | end
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/lono/bundler/component/props/typer.rb:
--------------------------------------------------------------------------------
1 | class Lono::Bundler::Component::Props
2 | class Typer
3 | include Lono::Bundler::Component::Concerns::LocalConcern
4 | include Lono::Bundler::Component::Concerns::NotationConcern
5 |
6 | delegate :source, to: :props
7 |
8 | attr_reader :props
9 | def initialize(props)
10 | @props = props # Props.new object
11 | end
12 |
13 | # IE: git or registry
14 | def source_type
15 | if source.include?('ssh://')
16 | "git"
17 | elsif source.include?('::')
18 | source.split('::').first # IE: git:: s3:: gcs::
19 | elsif local?
20 | "local"
21 | elsif registry?
22 | "registry"
23 | else
24 | "git"
25 | end
26 | end
27 |
28 | private
29 | # dont use registry? externally. instead use type since it can miss local detection
30 | def registry?
31 | if source.nil? ||
32 | source.starts_with?('git@') || # git@github.com:tongueroo/pet
33 | source.starts_with?('http') || # https://github.com/tongueroo/pet
34 | source.include?('::') # git::https:://git.example.com/pet
35 | return false
36 | end
37 | s = remove_notations(@props.source)
38 | s.split('/').size == 3 || s.split('/').size == 4
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/lib/lono/bundler/config.rb:
--------------------------------------------------------------------------------
1 | module Lono::Bundler
2 | class Config
3 | extend Memoist
4 | include Singleton
5 |
6 | def config
7 | config = ActiveSupport::OrderedOptions.new
8 | config.base_clone_url = "https://github.com/"
9 | config.export_to = ENV['LB_EXPORT_TO'] || "vendor"
10 | config.export_purge = ENV['LB_EXPORT_PRUNE'] == '0' ? false : true
11 | config.lonofile = ENV['LB_LONOFILE'] || "Lonofile"
12 | config.lockfile = "#{config.lonofile}.lock"
13 | config.logger = Lono.config.logger
14 | config
15 | end
16 | memoize :config
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/lono/bundler/dsl.rb:
--------------------------------------------------------------------------------
1 | module Lono::Bundler
2 | class Dsl
3 | include DslEvaluator
4 | include Syntax
5 |
6 | class_attribute :meta, default: {global: {}, components: []}
7 |
8 | def run
9 | evaluate_file(LB.config.lonofile)
10 | self
11 | end
12 |
13 | def meta
14 | self.class.meta
15 | end
16 |
17 | def global
18 | meta[:global]
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/lib/lono/bundler/dsl/syntax.rb:
--------------------------------------------------------------------------------
1 | class Lono::Bundler::Dsl
2 | module Syntax
3 | def org(value)
4 | config.org = value
5 | end
6 | alias_method :user, :org
7 |
8 | def base_clone_url(value)
9 | config.base_clone_url = value
10 | end
11 |
12 | def export_to(path)
13 | config.export_to = path
14 | end
15 |
16 | def export_purge(value)
17 | config.export_purge = value
18 | end
19 |
20 | def stack_options(value={})
21 | config.stack_options.merge!(value)
22 | end
23 |
24 | def clone_with(value)
25 | config.clone_with = value
26 | end
27 |
28 | def config
29 | LB.config
30 | end
31 |
32 | def component(name, options={})
33 | meta[:components] << options.merge(name: name)
34 | end
35 |
36 | def self.friendly_method(meth)
37 | define_method meth do |name, options={}|
38 | component(name, options.merge(type: meth.to_s))
39 | end
40 | end
41 |
42 | friendly_method :blueprint
43 | friendly_method :configset
44 | friendly_method :extension
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/lib/lono/bundler/exporter.rb:
--------------------------------------------------------------------------------
1 | module Lono::Bundler
2 | class Exporter
3 | include LB::Component::Concerns::PathConcern
4 | include LB::Util::Logging
5 |
6 | def initialize(options={})
7 | @options = options
8 | end
9 |
10 | def run
11 | purge_all
12 | components.each do |component|
13 | logger.info "Exporting #{component.export_path}"
14 | purge(component)
15 | export(component)
16 | end
17 | end
18 |
19 | def components
20 | components = lockfile.components
21 | if LB.update_mode? && !@options[:components].empty?
22 | components.select! { |component| @options[:components].include?(component.name) }
23 | end
24 | components
25 | end
26 |
27 | def export(component)
28 | fetcher = Component::Fetcher.new(component).instance
29 | fetcher.run
30 | fetcher.switch_version(component.sha)
31 | copy = Copy.new(component)
32 | copy.component
33 | end
34 |
35 | private
36 | def purge_all
37 | return if LB.update_mode?
38 | return unless LB.config.export_purge
39 | FileUtils.rm_rf(LB.config.export_to)
40 | end
41 |
42 | def purge(component)
43 | FileUtils.rm_rf(component.export_path)
44 | end
45 |
46 | def lockfile
47 | Lockfile.instance
48 | end
49 | end
50 | end
51 |
--------------------------------------------------------------------------------
/lib/lono/bundler/exporter/base.rb:
--------------------------------------------------------------------------------
1 | class Lono::Bundler::Exporter
2 | class Base
3 | include LB::Component::Concerns::PathConcern
4 | include LB::Util::Logging
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/lib/lono/bundler/exporter/copy.rb:
--------------------------------------------------------------------------------
1 | class Lono::Bundler::Exporter
2 | class Copy < Base
3 | def initialize(component)
4 | @component = component
5 | end
6 |
7 | def component
8 | FileUtils.rm_rf(@component.export_path)
9 | FileUtils.mkdir_p(File.dirname(@component.export_path))
10 | logger.debug "Copy: cp -r #{src_path} #{@component.export_path}"
11 | FileUtils.cp_r(src_path, @component.export_path)
12 | FileUtils.rm_rf("#{@component.export_path}/.git")
13 | end
14 |
15 | # src path is from the stage area
16 | def src_path
17 | path = stage_path(rel_dest_dir)
18 | path = "#{path}/#{@component.subfolder}" if @component.subfolder
19 | path
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/lib/lono/bundler/extract/tar.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems/package'
2 | require 'zlib'
3 | require 'fileutils'
4 |
5 | # Thanks: https://gist.github.com/ForeverZer0/2adbba36fd452738e7cca6a63aee2f30
6 | class Lono::Bundler::Extract
7 | class Tar
8 | # tar_gz_archive = '/tmp/lono/bundler/stage/s3-us-west-2.amazonaws.com/demo-terraform-test/blueprints/example-module.tgz'
9 | # destination = '/tmp/lono/where/extract/to'
10 | def self.extract(tar_gz_archive, destination)
11 | reader = Zlib::GzipReader.open(tar_gz_archive)
12 | Gem::Package::TarReader.new(reader) do |tar|
13 | dest = nil
14 | tar.each do |entry|
15 | dest ||= File.join destination, entry.full_name
16 | if entry.directory?
17 | FileUtils.rm_rf dest unless File.directory? dest
18 | FileUtils.mkdir_p dest, :mode => entry.header.mode, :verbose => false
19 | elsif entry.file?
20 | FileUtils.rm_rf dest unless File.file? dest
21 | File.open dest, "wb" do |f|
22 | f.print entry.read
23 | end
24 | FileUtils.chmod entry.header.mode, dest, :verbose => false
25 | elsif entry.header.typeflag == '2' #Symlink!
26 | File.symlink entry.header.linkname, dest
27 | end
28 | dest = nil
29 | end
30 | end
31 | end
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/lib/lono/bundler/extract/zip.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'zip'
3 |
4 | class Lono::Bundler::Extract
5 | class Zip
6 | def self.extract(file, dest)
7 | ::Zip::File.open(file) { |zip_file|
8 | zip_file.each { |f|
9 | f_path=File.join(dest, f.name)
10 | FileUtils.mkdir_p(File.dirname(f_path))
11 | zip_file.extract(f, f_path) unless File.exist?(f_path)
12 | }
13 | }
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/lono/bundler/info.rb:
--------------------------------------------------------------------------------
1 | module Lono::Bundler
2 | class Info < CLI::Base
3 | def run
4 | file = LB.config.lockfile
5 | unless File.exist?(file)
6 | logger.info "No #{file} found".color(:red)
7 | logger.info "Maybe run: lono bundle"
8 | return
9 | end
10 |
11 | name = @options[:component]
12 | all = lockfile.components.find_all { |c| c.name == @options[:component] }
13 | all = all.select { |c| c.type == @options[:type] } if @options[:type]
14 | if all.size > 1
15 | logger.info "Multiple components found with different types"
16 | end
17 |
18 | all.each do |found|
19 | show(found)
20 | end
21 | unless all.size > 0
22 | logger.info "Could not find component in #{LB.config.lockfile}: #{name}".color(:red)
23 | end
24 | end
25 |
26 | def show(component)
27 | props = component.props.reject { |k,v| k == :name }.stringify_keys # for sort
28 | puts "#{component.name}:"
29 | props.keys.sort.each do |k|
30 | v = props[k]
31 | puts " #{k}: #{v}" unless v.blank?
32 | end
33 | end
34 |
35 | def lockfile
36 | Lockfile.instance
37 | end
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/lib/lono/bundler/list.rb:
--------------------------------------------------------------------------------
1 | module Lono::Bundler
2 | class List < CLI::Base
3 | def run
4 | file = LB.config.lockfile
5 | unless File.exist?(file)
6 | logger.info "No #{file} found".color(:red)
7 | logger.info "Maybe run: lono bundle"
8 | return
9 | end
10 |
11 | logger.info "Components included by #{file}\n\n"
12 | presenter = CliFormat::Presenter.new(@options.merge(format: "table"))
13 | presenter.header = ["Name", "Type"]
14 | lockfile.components.each do |component|
15 | row = [component.name, component.type]
16 | presenter.rows << row
17 | end
18 | presenter.show
19 | logger.info "\nUse `lono bundle info` to print more detailed information about a component"
20 | end
21 |
22 | def lockfile
23 | Lockfile.instance
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/lib/lono/bundler/lockfile/yamler.rb:
--------------------------------------------------------------------------------
1 | require "yaml"
2 |
3 | class Lono::Bundler::Lockfile
4 | class Yamler
5 | def initialize(components)
6 | @components = components.sort_by(&:name)
7 | end
8 |
9 | def dump
10 | YAML.dump(data)
11 | end
12 |
13 | def data
14 | @components.map do |component|
15 | item(component)
16 | end
17 | end
18 |
19 | def item(component)
20 | props = component.props.dup # passthrough: name, url, version, ref, tag, branch etc
21 | props[:sha] ||= component.latest_sha
22 | props.delete_if { |k,v| v.nil? }
23 | props.deep_stringify_keys
24 | end
25 |
26 | def write
27 | IO.write(LB.config.lockfile, dump)
28 | end
29 |
30 | class << self
31 | def write(components)
32 | new(components).write
33 | end
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/lib/lono/bundler/lonofile.rb:
--------------------------------------------------------------------------------
1 | module Lono::Bundler
2 | class Lonofile
3 | include Singleton
4 | extend Memoist
5 | include LB::Util::Logging
6 |
7 | # dsl meta example:
8 | # {:global=>{{:org=>"boltopspro"},
9 | # :components=>[{:source=>"git@github.com:boltopspro/vpc", :type=>"blueprint", :name=>"vpc"}]
10 | # }
11 | def components
12 | LB.dsl.meta[:components].map do |options|
13 | Component.new(options)
14 | end
15 | end
16 | memoize :components
17 |
18 | # Checks if any of the components defined in Lonofile has an inferred an org
19 | # In this case the org must be set
20 | # When a source is set with an inferred org and org is not set it looks like this:
21 | #
22 | # dsl.meta has {:source=>"terraform-random-pet"}
23 | # component.source = "terraform-random-pet"
24 | #
25 | # Using info to detect that the org is missing and the Lonofile definition
26 | # has at least one component line that has an inferred org.
27 | #
28 | def missing_org?
29 | components.detect { |component| component.source.split('/').size == 1 } && LB.config.org.nil?
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/lib/lono/bundler/runner.rb:
--------------------------------------------------------------------------------
1 | module Lono::Bundler
2 | class Runner < CLI::Base
3 | def run
4 | Syncer.new(@options).run
5 | Exporter.new(@options).run
6 | # finish_message
7 | end
8 |
9 | def finish_message
10 | no_components_found = true
11 | export_paths.each do |path|
12 | found = Dir.exist?(path) && !Dir.empty?(path)
13 | next unless found
14 | logger.info "components saved to #{path}"
15 | no_components_found = false
16 | end
17 |
18 | logger.info("No components were found.") if no_components_found
19 | end
20 |
21 | def export_paths
22 | export_paths = Lonofile.instance.mods.map(&:export_to).compact.uniq
23 | export_paths << LB.config.export_to
24 | export_paths
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/lib/lono/bundler/util/git.rb:
--------------------------------------------------------------------------------
1 | module Lono::Bundler::Util
2 | module Git
3 | include Logging
4 | include Lono::Utils::Sh
5 |
6 | def git(command)
7 | sh("git #{command}")
8 | rescue LB::GitError => e
9 | action, version = command.split(' ')
10 | if action == "checkout" && version !~ /^v/
11 | command = "checkout v#{version}"
12 | retry
13 | else
14 | logger.error "ERROR: There was a git error".color(:red)
15 | logger.error "Current dir: #{Dir.pwd}"
16 | logger.error "The error occur when running:"
17 | logger.error e.message
18 | end
19 | exit 1
20 | end
21 |
22 | # Different from Lono::Utils::Sh
23 | def sh(command)
24 | command = "#{command} 2>&1" # always need output for the sha
25 | logger.debug "=> #{command}"
26 | out = `#{command}`
27 | unless $?.success?
28 | if command.include?("git")
29 | raise LB::GitError.new("#{command}\n#{out}")
30 | else
31 | logger.error "ERROR: running #{command}".color(:red)
32 | logger.error out
33 | exit $?.exitstatus
34 | end
35 | end
36 | out
37 | end
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/lib/lono/bundler/util/logging.rb:
--------------------------------------------------------------------------------
1 | module Lono::Bundler::Util
2 | module Logging
3 | def logger
4 | Lono::Bundler.logger
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/lib/lono/bundler/util/sure.rb:
--------------------------------------------------------------------------------
1 | module Lono::Bundler::Util
2 | module Sure
3 | include Lono::Utils::Sure
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/lib/lono/cfn/base.rb:
--------------------------------------------------------------------------------
1 | module Lono::Cfn
2 | class Base < Lono::CLI::Base
3 | extend Memoist
4 | include Lono::AwsServices
5 | include Lono::Hooks::Concern
6 | include Lono::Utils::Logging
7 | include Lono::Utils::Pretty
8 | include Lono::Utils::Quit
9 | include Lono::Utils::Sure
10 |
11 | include Concerns
12 |
13 | delegate :template_path, to: :build
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/lono/cfn/cancel.rb:
--------------------------------------------------------------------------------
1 | module Lono::Cfn
2 | class Cancel < Base
3 | def run
4 | stack = find_stack(@stack_name)
5 | unless stack
6 | logger.info "The '#{@stack_name}' stack does not exist. Unable to cancel"
7 | quit 1
8 | end
9 |
10 | logger.info "Canceling updates to #{@stack_name}."
11 | logger.info "Current stack status: #{stack.stack_status}"
12 | if stack.stack_status == "CREATE_IN_PROGRESS"
13 | cfn.delete_stack(stack_name: @stack_name)
14 | logger.info "Canceling stack creation."
15 | elsif stack.stack_status == "UPDATE_IN_PROGRESS"
16 | cfn.cancel_update_stack(stack_name: @stack_name)
17 | logger.info "Canceling stack update."
18 | status.wait if @options[:wait]
19 | else
20 | logger.info "The stack is not in a state to that is cancelable: #{stack.stack_status}"
21 | end
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/lib/lono/cfn/concerns.rb:
--------------------------------------------------------------------------------
1 | module Lono::Cfn
2 | module Concerns
3 | extend Memoist
4 |
5 | include Concerns::Build
6 | include Concerns::TemplateOutput
7 |
8 | def iam
9 | Deploy::Iam.new(@options)
10 | end
11 | memoize :iam
12 |
13 | def notification
14 | Deploy::Notification.new(@options)
15 | end
16 | memoize :notification
17 |
18 | def rollback
19 | Deploy::Rollback.new(@options)
20 | end
21 | memoize :rollback
22 |
23 | def operable
24 | Deploy::Operable.new(@options)
25 | end
26 | memoize :operable
27 |
28 | def plan
29 | Plan.new(@options.merge(build: build, iam: iam))
30 | end
31 | memoize :plan
32 |
33 | def status
34 | Status.new(@stack)
35 | end
36 | memoize :status
37 |
38 | def tags
39 | Deploy::Tags.new(@options)
40 | end
41 | memoize :tags
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/lib/lono/cfn/concerns/build.rb:
--------------------------------------------------------------------------------
1 | module Lono::Cfn::Concerns
2 | module Build
3 | extend Memoist
4 |
5 | def build
6 | Lono::Builder.new(@options)
7 | end
8 | memoize :build
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/lib/lono/cfn/concerns/template_output.rb:
--------------------------------------------------------------------------------
1 | module Lono::Cfn::Concerns
2 | module TemplateOutput
3 | extend Memoist
4 |
5 | def template_output
6 | Lono::Builder::Template::Output.new(@blueprint)
7 | end
8 | memoize :template_output
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/lib/lono/cfn/delete.rb:
--------------------------------------------------------------------------------
1 | module Lono::Cfn
2 | class Delete < Base
3 | def run
4 | logger.info "Will delete stack #{@stack.color(:green)}"
5 | plan.for_delete
6 | sure?("Are you sure you want to delete the stack?")
7 | run_hooks("down") do
8 | cfn.delete_stack(stack_name: @stack)
9 | end
10 | if @options[:wait]
11 | status.wait
12 | else
13 | logger.info "Deleting stack #{@stack}"
14 | end
15 | end
16 | end
17 | end
--------------------------------------------------------------------------------
/lib/lono/cfn/deploy/base.rb:
--------------------------------------------------------------------------------
1 | class Lono::Cfn::Deploy
2 | class Base < Lono::Cfn::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/lib/lono/cfn/deploy/iam.rb:
--------------------------------------------------------------------------------
1 | class Lono::Cfn::Deploy
2 | class Iam < Base
3 | attr_reader :options
4 |
5 | def rerun?(e)
6 | # e.message is "Requires capabilities : [CAPABILITY_IAM]"
7 | # grab CAPABILITY_IAM with regexp
8 | capabilities = e.message.match(/\[(.*)\]/)[1]
9 | confirm = prompt(capabilities)
10 | if confirm =~ /^y/
11 | logger.info "Re-running: #{command_with_iam(capabilities).color(:green)}"
12 | @retry_capabilities = [capabilities]
13 | true
14 | else
15 | logger.info "Exited"
16 | quit 1
17 | end
18 | end
19 |
20 | def prompt(capabilities)
21 | logger.info <<~EOL
22 | This stack will create IAM resources. Please approve to run the command again with #{capabilities} capabilities.
23 |
24 | #{command_with_iam(capabilities)}
25 |
26 | You can also avoid this prompt by setting config.up.capabilities in config/app.rb
27 | See: https://lono.cloud/docs/config/reference/
28 |
29 | EOL
30 | logger.print "Please confirm (y/N) "
31 | $stdin.gets
32 | end
33 |
34 | def command_with_iam(capabilities)
35 | "lono #{Lono.argv.join(' ')} --capabilities #{capabilities}"
36 | end
37 |
38 | def capabilities
39 | return @retry_capabilities if @retry_capabilities
40 | return @options[:capabilities] if @options[:capabilities]
41 | if @options[:iam]
42 | ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM", "CAPABILITY_AUTO_EXPAND"]
43 | else
44 | Lono.config.up.capabilities
45 | end
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/lib/lono/cfn/deploy/notification.rb:
--------------------------------------------------------------------------------
1 | class Lono::Cfn::Deploy
2 | class Notification < Base
3 | def arns
4 | arns = @options["notification_arns"] || Lono.config.up.notification_arns
5 | arns == [''] ? [] : arns # allow removing the notification_arns setting
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/lib/lono/cfn/deploy/operable.rb:
--------------------------------------------------------------------------------
1 | class Lono::Cfn::Deploy
2 | class Operable < Base
3 | def check!
4 | status = stack_status
5 | unless status =~ /_COMPLETE$/ || status == "UPDATE_ROLLBACK_FAILED"
6 | logger.info "Cannot run operation on stack #{@stack} is not in an updatable state. Stack status: #{status}".color(:red)
7 | quit 1
8 | end
9 | end
10 |
11 | # All CloudFormation states listed here:
12 | # http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-describing-stacks.html
13 | def stack_status
14 | resp = cfn.describe_stacks(stack_name: @stack)
15 | resp.stacks[0].stack_status
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/lono/cfn/deploy/rollback.rb:
--------------------------------------------------------------------------------
1 | class Lono::Cfn::Deploy
2 | class Rollback < Base
3 | def delete_stack
4 | return unless complete?
5 | logger.info "Existing stack in ROLLBACK_COMPLETE state. Deleting stack before continuing."
6 | cfn.delete_stack(stack_name: @stack)
7 | status.wait
8 | status.reset
9 | true
10 | end
11 |
12 | def continue_update
13 | continue_update?
14 | begin
15 | cfn.continue_update_rollback(stack_name: @stack)
16 | rescue Aws::CloudFormation::Errors::ValidationError => e
17 | logger.info "ERROR: Continue update: #{e.message}".color(:red)
18 | quit 1
19 | end
20 | end
21 |
22 | def continue_update?
23 | logger.info <<~EOL
24 | The stack is in the UPDATE_ROLLBACK_FAILED state. More info here: https://amzn.to/2IiEjc5
25 | Would you like to try to continue the update rollback? (y/N)
26 | EOL
27 |
28 | yes = @options[:yes] ? "y" : $stdin.gets
29 | unless yes =~ /^y/
30 | logger.info "Exiting without continuing the update rollback."
31 | quit 0
32 | end
33 | end
34 |
35 | def complete?
36 | stack&.stack_status == 'ROLLBACK_COMPLETE'
37 | end
38 |
39 | def stack
40 | find_stack(@stack)
41 | end
42 | memoize :stack
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/lib/lono/cfn/deploy/tags.rb:
--------------------------------------------------------------------------------
1 | class Lono::Cfn::Deploy
2 | class Tags < Base
3 | # Maps to CloudFormation format. Example:
4 | #
5 | # {"a"=>"1", "b"=>"2"}
6 | # To
7 | # [{key: "a", value: "1"}, {key: "b", value: "2"}]
8 | #
9 | def values
10 | tags = Lono.config.up.tags
11 | return if tags.nil? # nil = keep current tags. [] = remove all tags
12 |
13 | tags.map do |k,v|
14 | { key: k.to_s, value: v }
15 | end
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/lono/cfn/download.rb:
--------------------------------------------------------------------------------
1 | require 'json'
2 | require 'open-uri'
3 |
4 | module Lono::Cfn
5 | class Download < Base
6 | def run
7 | pretty_path = download_path.sub("#{Lono.root}/", '')
8 | logger.info "Downloading template to: #{pretty_path}"
9 | download_template
10 | end
11 |
12 | def download_template
13 | body = download_stack
14 | body = convert_to_yaml(body)
15 | FileUtils.mkdir_p(File.dirname(download_path))
16 | IO.write(download_path, body)
17 | end
18 |
19 | def download_stack
20 | source = @options[:source]
21 | if source
22 | open(source).read # url or file
23 | else
24 | resp = cfn.get_template(
25 | stack_name: @stack,
26 | template_stage: "Original"
27 | )
28 | resp.template_body
29 | end
30 | end
31 |
32 | def convert_to_yaml(body)
33 | json?(body) ? YAML.dump(JSON.parse(body)) : body
34 | end
35 |
36 | def json?(body)
37 | !!JSON.parse(body) rescue false
38 | end
39 |
40 | def download_path
41 | "#{Lono.root}/output/#{@blueprint.name}/templates/#{@blueprint.name}.yml"
42 | end
43 |
44 | def name
45 | @options[:name] || @stack
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/lib/lono/cfn/output.rb:
--------------------------------------------------------------------------------
1 | module Lono::Cfn
2 | class Output < Base
3 | def run
4 | stack = find_stack(@stack)
5 | unless stack
6 | logger.info "ERROR: stack #{@stack} not found".color(:red)
7 | quit 1
8 | end
9 |
10 | outputs = stack.outputs.map(&:to_h)
11 | logger.info <<~EOL
12 | #{"Outputs:".color(:green)}
13 |
14 | EOL
15 | if outputs.empty?
16 | logger.stdout outputs.inspect
17 | else
18 | logger.stdout pretty(outputs)
19 | end
20 | end
21 |
22 | private
23 | # Input:
24 | #
25 | # [{
26 | # output_key: "SecurityGroup",
27 | # output_value: "demo-dev-SecurityGroup-142DZFIEG3G9L"
28 | # }]
29 | #
30 | # Output:
31 | #
32 | # { "SecurityGroup" => "demo-dev-SecurityGroup-142DZFIEG3G9L" }
33 | #
34 | def pretty(outputs)
35 | outs = outputs.inject({}) do |result,h|
36 | result.merge!(h[:output_key] => h[:output_value])
37 | end
38 | default = {format: "equal"}
39 | options = default.merge(@options.symbolize_keys)
40 | presenter = CliFormat::Presenter.new(options)
41 | presenter.header = ["Key", "Value"] unless presenter.format == "equal"
42 | outs.keys.sort.each do |k|
43 | row = [k,outs[k]]
44 | presenter.rows << row
45 | end
46 | presenter.show
47 | end
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/lib/lono/cfn/plan/base.rb:
--------------------------------------------------------------------------------
1 | class Lono::Cfn::Plan
2 | class Base < Lono::Cfn::Base
3 | def initialize(options={})
4 | super
5 | # Allow build and iam to be passed from Cfn::Deploy. Reasons:
6 | #
7 | # 1. build.all only needs to be called once.
8 | # 2. iam.capabilities can be adjusted with retry
9 | # 3. objects are passed in a clear single direction.
10 | #
11 | # However, for CLI commands like `lono plan`, no objects are passed.
12 | # In this case, new instances are created. In both cases the same options
13 | # to the build and iam instances are used.
14 | #
15 | @build = options[:build] || build
16 | @iam = options[:iam] || iam
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/lono/cfn/plan/changeset/base.rb:
--------------------------------------------------------------------------------
1 | class Lono::Cfn::Plan::Changeset
2 | class Base
3 | extend Memoist
4 | include Lono::AwsServices
5 | include Lono::Utils::Logging
6 | include Lono::Cfn::Concerns::TemplateOutput
7 |
8 | def initialize(options={})
9 | @blueprint = options[:blueprint]
10 | @change_set = options[:change_set]
11 | @stack = options[:stack]
12 | end
13 |
14 | def stack
15 | find_stack(@stack)
16 | end
17 | memoize :stack
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/lono/cfn/plan/changeset/notifications.rb:
--------------------------------------------------------------------------------
1 | class Lono::Cfn::Plan::Changeset
2 | class Notifications < Base
3 | def changes
4 | old = stack.notification_arns
5 | new = @change_set.notification_arns
6 | added = new - old
7 | removed = old - new
8 | return if added.empty? && removed.empty?
9 |
10 | logger.info "Changes to notifications"
11 | log = Proc.new do |k|
12 | logger.info " #{k}"
13 | end
14 | unless added.empty?
15 | logger.info "Added:"
16 | added.each(&log)
17 | end
18 | unless removed.empty?
19 | logger.info "Removed:"
20 | removed.each(&log)
21 | end
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/lib/lono/cfn/plan/changeset/outputs.rb:
--------------------------------------------------------------------------------
1 | class Lono::Cfn::Plan::Changeset
2 | class Outputs < Base
3 | # Not enough info to show value changes. Show whats possible: Added and Removed keys
4 | def changes
5 | old_keys = stack.outputs.map { |output| output[:output_key] }
6 | new_keys = template_output.outputs.map { |k,_| k }
7 | added_keys = new_keys - old_keys
8 | removed_keys = old_keys - new_keys
9 | return if added_keys.empty? && removed_keys.empty?
10 |
11 | logger.info "Changes to outputs"
12 | log = Proc.new do |k|
13 | logger.info " #{k}"
14 | end
15 | unless added_keys.empty?
16 | logger.info "Added:"
17 | added_keys.each(&log)
18 | end
19 | unless removed_keys.empty?
20 | logger.info "Removed:"
21 | removed_keys.each(&log)
22 | end
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/lib/lono/cfn/plan/changeset/resources.rb:
--------------------------------------------------------------------------------
1 | class Lono::Cfn::Plan::Changeset
2 | class Resources < Base
3 | def changes
4 | return if @change_set.changes.empty?
5 | logger.info "Resource Changes"
6 | changes = @change_set.changes.sort_by do |change|
7 | change["resource_change"]["action"]
8 | end
9 | changes.each do |change|
10 | display_change(change)
11 | end
12 | end
13 |
14 | # Private: formats a Aws::CloudFormation::Types::Change in pretty human readable form
15 | #
16 | # change - Aws::CloudFormation::Types::Change
17 | #
18 | # Examples
19 | #
20 | # display_change(change)
21 | # => Remove AWS::Route53::RecordSet: DnsRecord testsubdomain.sub.tongueroo.com
22 | #
23 | # Returns nil
24 | #
25 | # change.to_h
26 | # {:type=>"Resource",
27 | # :resource_change=>
28 | # {:action=>"Remove",
29 | # :logical_resource_id=>"DnsRecord",
30 | # :physical_resource_id=>"testsubdomain.sub.tongueroo.com",
31 | # :resource_type=>"AWS::Route53::RecordSet",
32 | # :scope=>[],
33 | # :details=>[]}}
34 | def display_change(change)
35 | message = if change.type == "Resource"
36 | c = change.resource_change
37 | " #{c.action} #{c.resource_type}: #{c.logical_resource_id} #{c.physical_resource_id}"
38 | else
39 | change.to_h
40 | end
41 |
42 | colors = { Remove: :red, Add: :green, Modify: :yellow }
43 | action = change.resource_change.action.to_sym
44 | message = message.color(colors[action]) if colors.has_key?(action)
45 | logger.info message
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/lib/lono/cfn/plan/changeset/tags.rb:
--------------------------------------------------------------------------------
1 | class Lono::Cfn::Plan::Changeset
2 | class Tags < Base
3 | def changes
4 | old = simplify(stack.tags)
5 | new = simplify(@change_set.tags)
6 | diff = Lono::Cfn::Plan::Diff::Data.new(old, new)
7 | diff.show("Tag Changes:")
8 | end
9 |
10 | def simplify(tags)
11 | tags.inject({}) do |result, i|
12 | result.merge(i.key => i.value)
13 | end
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/lono/cfn/plan/concerns.rb:
--------------------------------------------------------------------------------
1 | class Lono::Cfn::Plan
2 | module Concerns
3 | extend Memoist
4 |
5 | def summary
6 | Summary.new
7 | end
8 | memoize :summary
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/lib/lono/cfn/plan/delete.rb:
--------------------------------------------------------------------------------
1 | class Lono::Cfn::Plan
2 | class Delete < Lono::Cfn::Base
3 | include Concerns
4 |
5 | def run
6 | stack = find_stack(@stack)
7 | unless stack
8 | logger.info "ERROR: stack #{@stack} not found".color(:red)
9 | quit 1
10 | end
11 |
12 | logger.info "Will delete".color(:green)
13 | Lono::Cfn::Show.new(@options).print
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/lono/cfn/plan/diff/base.rb:
--------------------------------------------------------------------------------
1 | module Lono::Cfn::Plan::Diff
2 | class Base
3 | include Lono::Utils::Logging
4 | include Lono::Utils::Pretty
5 |
6 | def initialize(options={})
7 | @options = options
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/lib/lono/cfn/plan/diff/data.rb:
--------------------------------------------------------------------------------
1 | module Lono::Cfn::Plan::Diff
2 | class Data < Base
3 | extend Memoist
4 |
5 | def initialize(old, new)
6 | @old = old
7 | @new = new
8 | end
9 |
10 | def show(header_message=nil)
11 | changes = calculate
12 | if !changes && header_message
13 | return
14 | end
15 | logger.info header_message if header_message
16 |
17 | unless @added_keys.empty?
18 | logger.info "Added:"
19 | @added_keys.each do |k|
20 | logger.info " #{k}: #{@new[k]}"
21 | end
22 | end
23 | unless @removed_keys.empty?
24 | logger.info "Removed:"
25 | @removed_keys.each do |k|
26 | logger.info " #{k}: #{@old[k]}"
27 | end
28 | end
29 | unless @modified_keys.empty?
30 | logger.info "Modified:"
31 | @modified_keys.each do |k|
32 | logger.info " #{k}: #{show_old(@old[k])} -> #{@new[k]}"
33 | end
34 | end
35 |
36 | unless changes
37 | logger.info "No changes"
38 | end
39 | end
40 |
41 | def show_old(v)
42 | if v.nil?
43 | '(unset)'
44 | elsif v == ''
45 | "''"
46 | else
47 | v
48 | end
49 | end
50 |
51 | def calculate
52 | @added_keys = @new.keys - @old.keys
53 | @removed_keys = @old.keys - @new.keys
54 | all_keys = (@old.keys + @new.keys).uniq
55 | kept_keys = all_keys - @added_keys - @removed_keys
56 | @modified_keys = kept_keys.select { |k| @old[k] != @new[k] }
57 | no_changes = @added_keys.empty? && @removed_keys.empty? && @modified_keys.empty?
58 | !no_changes
59 | end
60 | memoize :calculate
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/lib/lono/cfn/plan/new.rb:
--------------------------------------------------------------------------------
1 | class Lono::Cfn::Plan
2 | class New < Lono::Cfn::Base
3 | include Concerns
4 |
5 | def run
6 | if resource_types.empty?
7 | logger.info "ERROR: At least one Resource member must be defined in the template".color(:red)
8 | quit 1
9 | end
10 |
11 | logger.info "Will create".color(:green)
12 | summary.print(resource_types)
13 | end
14 |
15 | def resource_types
16 | resources = template_output.resources
17 | return {} unless resources
18 |
19 | types = Hash.new(0)
20 | resources.each do |logical_id, resource|
21 | types[resource["Type"]] += 1
22 | end
23 | types
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/lib/lono/cfn/plan/summary.rb:
--------------------------------------------------------------------------------
1 | class Lono::Cfn::Plan
2 | class Summary
3 | include Lono::Utils::Logging
4 |
5 | def print(resource_types)
6 | types = resource_types.sort_by {|r| r[0]} # Hash -> 2D Array
7 | types.each do |a|
8 | type, count = a
9 | logger.info "%3s %s" % [count, type]
10 | end
11 | total = types.inject(0) { |sum,(type,count)| sum += count }
12 | logger.info "%3s %s" % [total, "Total"]
13 | logger.info "" # newline
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/lono/cfn/plan/template.rb:
--------------------------------------------------------------------------------
1 | class Lono::Cfn::Plan
2 | class Template < Base
3 | def run
4 | return unless Lono.config.plan.template
5 | @build.all
6 | logger.info "Template Changes:".color(:green)
7 | download_existing_cfn_template
8 | diff = Diff::File.new(mode: Lono.config.plan.template)
9 | diff.show(existing_template_path, new_cfn_template)
10 | logger.info "" # newline
11 | end
12 |
13 | def download_existing_cfn_template
14 | resp = cfn.get_template(stack_name: @stack, template_stage: "Original")
15 | FileUtils.mkdir_p(File.dirname(existing_template_path))
16 | IO.write(existing_template_path, resp.template_body)
17 | end
18 |
19 | # for clarity
20 | def new_cfn_template
21 | @blueprint.output_path
22 | end
23 |
24 | def existing_template_path
25 | "/tmp/lono/diff/template/existing.yml"
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/lono/cfn/show.rb:
--------------------------------------------------------------------------------
1 | module Lono::Cfn
2 | class Show < Base
3 | include Plan::Concerns
4 |
5 | def run
6 | logger.info "Info for stack: #{@stack.color(:green)}"
7 | logger.info "Resources:".color(:green)
8 | print
9 | Output.new(@options).run
10 | end
11 |
12 | def print
13 | summary.print(resource_types)
14 | end
15 |
16 | def resource_types
17 | resources = find_stack_resources(@stack)
18 | unless resources
19 | logger.info "ERROR: Stack #{@stack} not found".color(:red)
20 | exit 1
21 | end
22 |
23 | types = Hash.new(0)
24 | resources.each do |resource|
25 | types[resource.resource_type] += 1
26 | end
27 | types
28 | end
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/lib/lono/cfn/status.rb:
--------------------------------------------------------------------------------
1 | module Lono::Cfn
2 | class Status < CfnStatus
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/lib/lono/cli/abstract.rb:
--------------------------------------------------------------------------------
1 | class Lono::CLI
2 | class Abstract
3 | extend Memoist
4 | include Lono::Utils::Logging
5 | include Lono::Utils::Pretty
6 |
7 | def initialize(options={})
8 | @options = options
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/lib/lono/cli/base.rb:
--------------------------------------------------------------------------------
1 | class Lono::CLI
2 | class Base < Abstract
3 | include Lono::Concerns::Names
4 | include Lono::Utils::Sh
5 |
6 | def initialize(options={})
7 | super
8 | @blueprint = Lono::Blueprint.new(options.merge(name: options[:blueprint]))
9 | @stack = names.stack
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/lib/lono/cli/bundle.rb:
--------------------------------------------------------------------------------
1 | class Lono::CLI
2 | class Bundle < Abstract
3 | def run
4 | Lono::Bundler::CLI.start(args)
5 | end
6 |
7 | # Allows bundle to be called without install. So both work:
8 | #
9 | # lono bundle
10 | # lono bundle install
11 | #
12 | def args
13 | args = @options[:args]
14 | if args.empty? or args.first.include?('--')
15 | args.unshift("install")
16 | end
17 | args = ["bundle"] + args
18 | args
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/lib/lono/cli/cfn.rb:
--------------------------------------------------------------------------------
1 | class Lono::CLI
2 | class Cfn < Lono::Command
3 | opts = Lono::CLI::Cfn::Opts.new(self)
4 |
5 | desc "cancel BLUEPRINT", "Cancel a CloudFormation blueprint."
6 | long_desc Help.text("cfn/cancel")
7 | opts.cancel
8 | def cancel(blueprint)
9 | Lono::Cfn::Cancel.new(options.merge(blueprint: blueprint)).run
10 | end
11 |
12 | desc "download BLUEPRINT", "Download CloudFormation template from existing blueprint."
13 | long_desc Help.text("cfn/download")
14 | opts.download
15 | def download(blueprint)
16 | Lono::Cfn::Download.new(options.merge(blueprint: blueprint)).run
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/lono/cli/cfn/opts.rb:
--------------------------------------------------------------------------------
1 | class Lono::CLI::Cfn
2 | class Opts < Lono::CLI::Opts
3 | def create
4 | base_options
5 | wait_options
6 | end
7 |
8 | def update
9 | base_options
10 | wait_options
11 | end
12 |
13 | def deploy
14 | update
15 | end
16 |
17 | def delete
18 | wait_options
19 | end
20 |
21 | def cancel
22 | wait_options
23 | end
24 |
25 | def preview
26 | base_options
27 | with_cli_scope do
28 | option :keep, type: :boolean, desc: "keep the changeset instead of deleting it afterwards"
29 | end
30 | end
31 |
32 | def download
33 | base_options
34 | with_cli_scope do
35 | option :name, desc: "Name you want to save the template as. Default: existing stack name."
36 | option :source, desc: "url or path to file with template"
37 | end
38 | end
39 |
40 | # Lono::Cfn and Lono::Sets
41 | def base_options(rollback: true)
42 | with_cli_scope do
43 | if rollback
44 | option :rollback, type: :boolean, desc: "rollback" # default: nil means CloudFormation default which is true
45 | end
46 | # common to Lono::Cfn and Lono::Sets
47 | option :capabilities, type: :array, desc: "iam capabilities. Ex: CAPABILITY_IAM, CAPABILITY_NAMED_IAM"
48 | option :iam, type: :boolean, desc: "Shortcut for common IAM capabilities: CAPABILITY_IAM, CAPABILITY_NAMED_IAM"
49 | end
50 | end
51 |
52 | def wait_options
53 | with_cli_scope do
54 | option :wait, type: :boolean, desc: "Wait for stack operation to complete.", default: true
55 | option :yes, aliases: :y, type: :boolean, desc: "Skip are you sure prompt" # moved to base but used by commands like `lono down` also. Just keep here.
56 | end
57 | end
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/lib/lono/cli/clean.rb:
--------------------------------------------------------------------------------
1 | require "fileutils"
2 |
3 | class Lono::CLI
4 | class Clean
5 | include Lono::Utils::Logging
6 | include Lono::Utils::Sure
7 |
8 | attr_reader :options
9 | def initialize(options={})
10 | @options = options
11 | @mute = options[:mute] # used by CLI::Build at beginning to clear out the output folder
12 | end
13 |
14 | def run
15 | folders = %w[output tmp]
16 | @mute || sure?("Will remove folders: #{folders.join(' ')}")
17 | folders.each do |folder|
18 | FileUtils.rm_rf("#{Lono.root}/#{folder}")
19 | end
20 | logger.info "Removed folders: #{folders.join(' ')}" unless @mute
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/lono/cli/completion.rb:
--------------------------------------------------------------------------------
1 | class Lono::CLI
2 | class Completion < Base
3 | desc "script", "generates script that can be eval to setup auto-completion"
4 | long_desc Help.text("completion:script")
5 | def script
6 | Completer::Script.generate
7 | end
8 |
9 | desc "completions *PARAMS", "prints words for auto-completion"
10 | long_desc Help.text("completion:list")
11 | def list(*params)
12 | Completer.new(*params).run
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/lono/cli/help.rb:
--------------------------------------------------------------------------------
1 | class Lono::CLI
2 | module Help
3 | extend ActiveSupport::Concern
4 |
5 | class_methods do
6 | def main_commands
7 | %w[
8 | build
9 | down
10 | list
11 | new
12 | plan
13 | seed
14 | show
15 | up
16 | ]
17 | end
18 |
19 | def help(shell, subcommand)
20 | list = printable_commands(true, subcommand)
21 | list.sort! { |a, b| a[0] <=> b[0] }
22 | filter = Proc.new do |command, desc|
23 | main_commands.detect { |name| command =~ Regexp.new("^lono #{name}") }
24 | end
25 | main = list.select(&filter)
26 | other = list.reject(&filter)
27 |
28 | shell.say <<~EOL
29 | Usage: lono COMMAND [args]
30 | The available commands are listed below.
31 | The primary workflow commands are given first, followed by
32 | less common or more advanced commands.
33 | EOL
34 | shell.say "\nMain Commands:\n\n"
35 | shell.print_table(main, :indent => 2, :truncate => true)
36 | shell.say "\nOther Commands:\n\n"
37 | shell.print_table(other, :indent => 2, :truncate => true)
38 | shell.say <<~EOL
39 |
40 | For more help on each command, you can use the -h option. Example:
41 |
42 | lono up -h
43 |
44 | CLI Reference also available at: https://lono.cloud/reference/
45 | EOL
46 | end
47 | end
48 |
49 | def text(namespaced_command)
50 | path = namespaced_command.to_s.gsub(':','/')
51 | path = File.expand_path("../help/#{path}.md", __FILE__)
52 | IO.read(path) if File.exist?(path)
53 | end
54 | extend self
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/lib/lono/cli/help/build.md:
--------------------------------------------------------------------------------
1 | Generates CloudFormation template, parameter files, and scripts in lono project and writes them to the `output` folder.
2 |
3 | ## Examples
4 |
5 | lono build BLUEPRINT
6 | lono build BLUEPRINT --clean
7 | lono g BLUEPRINT --clean # shortcut
8 |
9 | ## Example Output
10 |
11 | $ lono build ec2
12 | Building template, parameters, and scripts
13 | Building template:
14 | output/templates/ec2.yml
15 | Building parameters:
16 | output/params/ec2.json
17 | $
18 |
--------------------------------------------------------------------------------
/lib/lono/cli/help/cfn.md:
--------------------------------------------------------------------------------
1 | ## Examples
2 |
3 | lono up demo
4 | lono down demo
5 |
--------------------------------------------------------------------------------
/lib/lono/cli/help/cfn/cancel.md:
--------------------------------------------------------------------------------
1 | ## Examples
2 |
3 | $ lono cfn cancel demo
4 | Canceling updates to demo.
5 | Current stack status: UPDATE_IN_PROGRESS
6 | Canceling stack update.
7 | Waiting for stack to complete
8 | 01:20:32PM UPDATE_ROLLBACK_IN_PROGRESS AWS::CloudFormation::Stack demo User Initiated
9 | 01:20:44PM UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS AWS::CloudFormation::Stack demo
10 | 01:20:44PM UPDATE_ROLLBACK_COMPLETE AWS::CloudFormation::Stack demo
11 | Stack rolled back: UPDATE_ROLLBACK_COMPLETE
12 | Time took for stack deployment: 15s.
13 | $
--------------------------------------------------------------------------------
/lib/lono/cli/help/cfn/download.md:
--------------------------------------------------------------------------------
1 | ## Examples
2 |
3 | lono cfn download demo
4 |
--------------------------------------------------------------------------------
/lib/lono/cli/help/cfn/preview.md:
--------------------------------------------------------------------------------
1 | ## Examples
2 |
3 | We'll make a change to the default InstanceType of a CloudFromation stack.
4 |
5 | lono cfn plan demo
6 |
7 | The output should look similiar to this:
8 |
9 | Building CloudFormation source code diff...
10 | => colordiff /tmp/existing_cfn_template.yml output/templates/ec2.yml
11 | 14c14
12 | < Default: t2.small
13 | ---
14 | > Default: t2.micro
15 | Building CloudFormation Change Set for plan.....
16 | CloudFormation plan for 'ec2' stack update. Changes:
17 | Modify AWS::EC2::Instance: EC2Instance i-07e939db65120fb75
18 |
19 | The plan includes colors:
20 |
21 |
22 |
23 | There are several types of "diffs" in the plan.
24 |
25 | 1. Source code diff of the templates. This is generated by downloading the current CloudFormation template and comparing it with the locally generated one using `colordiff`.
26 | 2. Param diff.
27 | 3. CloudFormation Change Set list of changes. This is generated using [AWS CloudFormation Change Set](https://medium.com/boltops/a-simple-introduction-to-cloudformation-part-4-change-sets-dry-run-mode-c14e41dfeab7) feature.
28 |
--------------------------------------------------------------------------------
/lib/lono/cli/help/cfn/status.md:
--------------------------------------------------------------------------------
1 | Shows the status of the stack. If the stack is in progress then tail the status and provide live updates.
2 |
3 | ## Examples
4 |
5 | $ lono cfn status ecs-asg
6 | The current status for the stack ecs-asg is UPDATE_COMPLETE
7 | Stack events:
8 | 11:52:28PM UPDATE_IN_PROGRESS AWS::CloudFormation::Stack ecs-asg User Initiated
9 | 11:52:32PM UPDATE_IN_PROGRESS AWS::CloudWatch::Alarm HighMemory
10 | 11:52:32PM UPDATE_IN_PROGRESS AWS::CloudWatch::Alarm HighCpu
11 | 11:52:32PM UPDATE_COMPLETE AWS::CloudWatch::Alarm HighMemory
12 | 11:52:33PM UPDATE_COMPLETE AWS::CloudWatch::Alarm HighCpu
13 | 11:52:35PM UPDATE_COMPLETE_CLEANUP_IN_PROGRESS AWS::CloudFormation::Stack ecs-asg
14 | 11:52:36PM UPDATE_COMPLETE AWS::CloudFormation::Stack ecs-asg
15 |
--------------------------------------------------------------------------------
/lib/lono/cli/help/code/import.md:
--------------------------------------------------------------------------------
1 | ## Examples
2 |
3 | lono code import path/to/file
4 | lono code import http://example.com/url/to/template.yml
5 | lono code import http://example.com/url/to/template.json
6 | lono code import http://example.com/url/to/template.json --blueprint myblueprint
7 |
8 | ## Example with Output
9 |
10 | $ URL=https://s3.amazonaws.com/cloudformation-templates-us-east-1/EC2InstanceWithSecurityGroupSample.template
11 | $ lono code import $URL --blueprint ec2
12 | => Creating new blueprint called ec2.
13 | create blueprints/ec2
14 | create blueprints/ec2/ec2.gemspec
15 | create blueprints/ec2/.gitignore
16 | create blueprints/ec2/.meta/config.yml
17 | create blueprints/ec2/CHANGELOG.md
18 | create blueprints/ec2/Gemfile
19 | create blueprints/ec2/README.md
20 | create blueprints/ec2/Rakefile
21 | create blueprints/ec2/seed/configs.rb
22 | create blueprints/ec2/app/templates
23 | create blueprints/ec2/app/templates/ec2.rb
24 | create config/ec2/params/development.txt
25 | create config/ec2/params/production.txt
26 | ================================================================
27 | Congrats You have successfully imported a lono blueprint.
28 |
29 | More info: https://lono.cloud/docs/core/blueprints
30 | $
31 |
--------------------------------------------------------------------------------
/lib/lono/cli/help/completion.md:
--------------------------------------------------------------------------------
1 | ## Example
2 |
3 | lono completion
4 |
5 | Prints words for TAB auto-completion.
6 |
7 | ## Examples
8 |
9 | lono completion
10 | lono completion cfn
11 | lono completion up
12 |
13 | To enable, TAB auto-completion add the following to your profile:
14 |
15 | eval $(lono completion_script)
16 |
17 | Auto-completion example usage:
18 |
19 | lono [TAB]
20 | lono cfn [TAB]
21 | lono up [TAB]
22 | lono up --[TAB]
23 |
--------------------------------------------------------------------------------
/lib/lono/cli/help/completion_script.md:
--------------------------------------------------------------------------------
1 | To use, add the following to your `~/.bashrc` or `~/.profile`:
2 |
3 | eval $(lono completion_script)
4 |
--------------------------------------------------------------------------------
/lib/lono/cli/help/configsets.md:
--------------------------------------------------------------------------------
1 | ## Examples
2 |
3 | When the `lono configsets` command is passed the blueprint will list the configsets for the blueprint.
4 |
5 | $ lono configsets ec2
6 | Configsets used by ec2 blueprint:
7 | +-------+----------------------+---------+---------+
8 | | Name | Path | Type | From |
9 | +-------+----------------------+---------+---------+
10 | | httpd | app/configsets/httpd | project | project |
11 | +-------+----------------------+---------+---------+
12 | $
13 |
14 | When there are no arguments passed to the `lono configsets` command it will list the project configsets.
15 |
16 | $ lono configsets
17 | Project configsets:
18 | +-------+------------------------+---------+
19 | | Name | Path | Type |
20 | +-------+------------------------+---------+
21 | | httpd | app/configsets/httpd | project |
22 | | ruby | vendor/configsets/ruby | vendor |
23 | +-------+------------------------+---------+
24 | $
--------------------------------------------------------------------------------
/lib/lono/cli/help/down.md:
--------------------------------------------------------------------------------
1 | ## Examples
2 |
3 | $ lono down demo
4 | Are you sure you want to delete the demo-dev stack? (y/N)
5 | y
6 | Deleted demo-dev stack.
7 | $
8 |
9 | Lono prompts you with an "Are you sure?" message before the stack gets deleted. If you would like to bypass the prompt, you can use the `-y` flag.
10 |
11 | $ lono down demo -y
12 | Deleted demo-dev stack.
13 | $
14 |
--------------------------------------------------------------------------------
/lib/lono/cli/help/new/helper.md:
--------------------------------------------------------------------------------
1 | ## Blueprint Helpers
2 |
3 | When the `--blueprint` option is used a blueprint helper is generated.
4 |
5 | The default name is custom
6 |
7 | $ lono new helper --blueprint demo
8 | create app/blueprints/demo/helpers/custom_helper.rb
9 |
10 | Here's an example with a helper named vars.
11 |
12 | $ lono new helper vars --blueprint demo
13 | create app/blueprints/demo/helpers/vars_helper.rb
14 |
15 | ## Project Helpers
16 |
17 | When no `--blueprint` option is used a project helper is generated.
18 |
19 | The default name is custom.
20 |
21 | $ lono new helper
22 | create app/helpers/custom/custom_helper.rb
23 |
24 | Here's an example with a helper named common.
25 |
26 | $ lono new helper common
27 | create app/helpers/common/common_helper.rb
28 |
--------------------------------------------------------------------------------
/lib/lono/cli/help/new/hook.md:
--------------------------------------------------------------------------------
1 | ## Blueprint Hooks
2 |
3 | When the `--blueprint` option is used a blueprint hook is generated.
4 |
5 | $ lono new helper --blueprint demo
6 | create app/blueprints/demo/config/hooks.rb
7 |
8 | ## Project Helpers
9 |
10 | When no `--blueprint` option is used a project hook is generated.
11 |
12 | $ lono new helper
13 | create app/config/hooks.rb
14 |
15 |
--------------------------------------------------------------------------------
/lib/lono/cli/help/new/project.md:
--------------------------------------------------------------------------------
1 | ## Example
2 |
3 | $ lono new project infra
4 | Other infra
5 | => Creating new project called infra.
6 | create infra
7 | create infra/.gitignore
8 | create infra/Gemfile
9 | create infra/README.md
10 | create infra/config/app.rb
11 | => Installing dependencies with: bundle install
12 | Bundle complete! 2 Gemfile dependencies, 47 gems now installed.
13 | ================================================================
14 | Congrats 🎉 You have successfully created a lono project.
15 |
16 | cd infra
17 |
18 | To generate a new blueprint:
19 |
20 | lono new blueprint demo --examples
21 |
22 | To deploy:
23 |
24 | lono up demo
25 |
26 | More info: https://lono.cloud/
27 | $
--------------------------------------------------------------------------------
/lib/lono/cli/help/param.md:
--------------------------------------------------------------------------------
1 | ## Examples
2 |
3 | $ lono param generate ecs-asg
4 | Building parameters
5 | output/ecs-asg/params/development.json
6 | $
--------------------------------------------------------------------------------
/lib/lono/cli/help/param/generate.md:
--------------------------------------------------------------------------------
1 | ## Example
2 |
3 | To generate CloudFormation json parameter files in the `output` folder:
4 |
5 | lono param generate
6 |
--------------------------------------------------------------------------------
/lib/lono/cli/help/pro/blueprints.md:
--------------------------------------------------------------------------------
1 | ## Example
2 |
3 | $ lono pro blueprints
4 | +-------------+------------------------------------------------+-----------------------------------------+
5 | | Name | Docs | Description |
6 | +-------------+------------------------------------------------+-----------------------------------------+
7 | | elasticache | https://github.com/boltopspro-docs/elasticache | Amazon Elasticache Memcached and Red... |
8 | | elb | https://github.com/boltopspro-docs/elb | ELB Blueprint: Application or Networ... |
9 | | rds | https://github.com/boltopspro-docs/rds | RDS Database Blueprint |
10 | +-------------+------------------------------------------------+-----------------------------------------+
11 | $
--------------------------------------------------------------------------------
/lib/lono/cli/help/pro/configsets.md:
--------------------------------------------------------------------------------
1 | ## Example
2 |
3 | $ lono pro configsets
4 | +---------+--------------------------------------------+-----------------------------------------+
5 | | Name | Docs | Description |
6 | +---------+--------------------------------------------+-----------------------------------------+
7 | | jenkins | https://github.com/boltopspro-docs/jenkins | Installs and Runs Jenkins |
8 | | ruby | https://github.com/boltopspro-docs/ruby | ruby configset: install ruby on amaz... |
9 | | ssm | https://github.com/boltopspro-docs/ssm | SSM Configset: Install, Configure an... |
10 | +---------+--------------------------------------------+-----------------------------------------+
11 | $
--------------------------------------------------------------------------------
/lib/lono/cli/help/script/build.md:
--------------------------------------------------------------------------------
1 | ## Examples
2 |
3 | lono script build
4 |
--------------------------------------------------------------------------------
/lib/lono/cli/help/script/upload.md:
--------------------------------------------------------------------------------
1 | This command must be ran after `lono script build` since it relies the artifacts of that command. Namely:
2 |
3 | * output/scripts/scripts-md5sum.tgz
4 | * output/data/scripts_info.txt
5 |
6 | ## Examples
7 |
8 | lono script upload
9 |
--------------------------------------------------------------------------------
/lib/lono/cli/help/seed.md:
--------------------------------------------------------------------------------
1 | ## Example
2 |
3 | $ lono seed demo
4 | Creating starter config files for ec2
5 | create config/blueprints/ec2/params/dev.txt
6 | create config/blueprints/ec2/vars/dev.rb
7 |
8 | To create the files in the top-level app folder
9 |
10 | $ lono seed ec2 --where app
11 | Creating starter config files for ec2
12 | create app/blueprints/ec2/config/params/dev.txt
13 | create app/blueprints/ec2/config/vars/dev.rb
14 | $
15 |
16 | You can also set the default where option with
17 |
18 | config/app.rb
19 |
20 | ```ruby
21 | Lono.configure do |config|
22 | config.seed.where = "app"
23 | end
24 | ```
25 |
--------------------------------------------------------------------------------
/lib/lono/cli/help/summary.md:
--------------------------------------------------------------------------------
1 | The `lono summary` command helps you quickly understand a CloudFormation template.
2 |
3 | ## Examples
4 |
5 | $ bundle exec lono summary ec2
6 | Building template
7 | output/ec2/templates/ec2-old.yml
8 | output/ec2/templates/ec2-new.yml
9 | => CloudFormation Template Summary for template ec2-new:
10 | Required Parameters (0):
11 | There are no required parameters.
12 | Optional Parameters (3):
13 | InstanceType (String) Default: t3.micro
14 | Subnet (String) Default:
15 | Vpc (String) Default:
16 | Resources:
17 | 1 AWS::EC2::Instance
18 | 1 AWS::EC2::SecurityGroup
19 | 2 Total
20 | => CloudFormation Template Summary for template ec2-old:
21 | Required Parameters (0):
22 | There are no required parameters.
23 | Optional Parameters (3):
24 | InstanceType (String) Default: t3.micro
25 | Subnet (String) Default:
26 | Vpc (String) Default:
27 | Resources:
28 | 1 AWS::EC2::Instance
29 | 1 AWS::EC2::SecurityGroup
30 | 2 Total
31 | $
32 |
33 | Blog Post also covers this: [lono summary Tutorial Introduction](https://blog.boltops.com/2017/09/18/lono-inspect-summary-tutorial-introduction)
34 |
--------------------------------------------------------------------------------
/lib/lono/cli/help/template.md:
--------------------------------------------------------------------------------
1 | ## Examples
2 |
3 | lono template generate --help
4 | lono template bashify --help
5 |
--------------------------------------------------------------------------------
/lib/lono/cli/help/template/bashify.md:
--------------------------------------------------------------------------------
1 | ## Examples
2 |
3 | lono template bashify /path/to/cloudformation-template.json
4 | lono template bashify https://s3.amazonaws.com/cloudformation-templates-us-east-1/EC2WebSiteSample.template
5 |
--------------------------------------------------------------------------------
/lib/lono/cli/help/template/generate.md:
--------------------------------------------------------------------------------
1 | ## Examples
2 |
3 | lono template generate
4 | lono template generate --clean
5 | lono template g --clean
6 |
7 | Builds the CloudFormation template files based on lono project and writes them to the output folder on the filesystem.
8 |
--------------------------------------------------------------------------------
/lib/lono/cli/help/up.md:
--------------------------------------------------------------------------------
1 | ## Examples
2 |
3 | $ lono up demo
4 | Deploying demo-dev stack
5 | Building template
6 | output/demo/template.yml
7 | Uploading template
8 | Not modified: output/demo/template.yml to s3://lono-bucket-12di8xz5sy72z/dev/output/demo/template.yml
9 | Uploaded to s3.
10 | Building parameters
11 | output/demo/params.json
12 | Going to create stack demo-dev with blueprint demo. (y/N) y
13 | Parameters passed to create_stack:
14 | ---
15 | disable_rollback: false
16 | parameters:
17 | - parameter_key: AccessControl
18 | parameter_value: PublicRead
19 | stack_name: demo-dev
20 | template_url: https://lono-bucket-12di8xz5sy72z.s3.us-west-2.amazonaws.com/dev/output/demo/template.yml
21 | Waiting for stack to complete
22 | 05:07:26AM CREATE_IN_PROGRESS AWS::CloudFormation::Stack demo-dev User Initiated
23 | 05:07:30AM CREATE_IN_PROGRESS AWS::S3::Bucket Bucket
24 | 05:07:31AM CREATE_IN_PROGRESS AWS::S3::Bucket Bucket Resource creation Initiated
25 | 05:07:52AM CREATE_COMPLETE AWS::S3::Bucket Bucket
26 | 05:07:53AM CREATE_COMPLETE AWS::CloudFormation::Stack demo-dev
27 | Stack success status: CREATE_COMPLETE
28 | Time took: 30s.
29 | Create demo-dev stack.
30 | $
31 |
--------------------------------------------------------------------------------
/lib/lono/cli/help/user_data.md:
--------------------------------------------------------------------------------
1 | Generates user_data scripts in `app/user_data` so you can see it for debugging. Let's say you have a script in `app/user_data/bootstrap.sh`. To generate it:
2 |
3 | lono user_data bootstrap
4 |
5 | ## Example Output
6 |
7 | Script:
8 |
9 | #!/bin/bash -exu
10 |
11 | <%= extract_scripts(to: "/opt") %>
12 |
13 | SCRIPTS=/opt/scripts
14 | $SCRIPTS/install_stuff.sh
15 |
16 | Running `lono user_data bootstrap` produces:
17 |
18 | $ lono user_data bootstrap
19 | Detected scripts
20 | Tarballing scripts folder to scripts.tgz
21 | => cd app && dot_clean .
22 | => cd app && tar -c scripts | gzip -n > scripts.tgz
23 | Tarball created at output/scripts/scripts-93b8b29b.tgz
24 | Building user_data for 'bootstrap' at ./app/user_data/bootstrap.sh
25 | #!/bin/bash -exu
26 |
27 | # Generated from the lono extract_scripts helper.
28 | # Downloads scripts from s3, extract them, and setup.
29 | mkdir -p /opt
30 | aws s3 cp s3://mybucket/path/to/folder/development/scripts/scripts-93b8b29b.tgz /opt/
31 | cd /opt
32 | tar zxf /opt/scripts-93b8b29b.tgz
33 | chown -R ec2-user:ec2-user /opt/scripts
34 |
35 | SCRIPTS=/opt/scripts
36 | $SCRIPTS/install_stuff.sh
37 | $
38 |
--------------------------------------------------------------------------------
/lib/lono/cli/iam.rb:
--------------------------------------------------------------------------------
1 | class Lono::CLI
2 | class Iam < Base
3 | include Lono::Cfn::Concerns::Build
4 | include Lono::Cfn::Concerns::TemplateOutput
5 |
6 | def run
7 | build.template_builder.run
8 | resources = template_output.data['Resources']
9 | types = resources.map { |logical_id, attrs| attrs['Type'] }
10 | actions = types.map do |t|
11 | service = t.split('::')[1]
12 | "#{service.downcase}:*"
13 | end.uniq
14 | text =<<~EOL
15 | Version: 2012-10-17
16 | Statement:
17 | - Sid: #{@blueprint.name}
18 | Effect: Allow
19 | Resource: "*"
20 | EOL
21 | policy = YAML.load(text)
22 | policy['Statement'][0]['Action'] = actions
23 | puts "IAM Policy Example for blueprint #{@blueprint.name}".color(:green)
24 | puts JSON.pretty_generate(policy)
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/lib/lono/cli/list.rb:
--------------------------------------------------------------------------------
1 | class Lono::CLI
2 | class List < Base
3 | attr_reader :options
4 | def initialize(options={})
5 | @options = options
6 | end
7 |
8 | def run
9 | list_type("blueprints") if show?("blueprint")
10 | list_type("configsets") if show?("configset")
11 | list_type("extensions") if show?("extension")
12 | end
13 |
14 | private
15 | def list_type(type)
16 | Dir.glob("#{Lono.root}/{app,vendor}/#{type}/*").each do |path|
17 | logger.info pretty_path(path)
18 | end
19 | end
20 |
21 | def show?(type)
22 | @options[:type] == type || @options[:type].nil? || @options[:type] == "all"
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/lib/lono/cli/new.rb:
--------------------------------------------------------------------------------
1 | class Lono::CLI
2 | class New < Lono::Command
3 | long_desc Help.text("new/blueprint")
4 | Blueprint.cli_options.each { |args| option(*args) }
5 | register(Blueprint, "blueprint", "blueprint NAME", "Generate new blueprint")
6 |
7 | long_desc Help.text("new/configset")
8 | Configset.cli_options.each { |args| option(*args) }
9 | register(Configset, "configset", "configset NAME", "Generate new configset")
10 |
11 | long_desc Help.text("new/helper")
12 | Helper.cli_options.each { |args| option(*args) }
13 | register(Helper, "helper", "helper NAME", "Generate new helper")
14 |
15 | long_desc Help.text("new/hook")
16 | Hook.cli_options.each { |args| option(*args) }
17 | register(Hook, "hook", "hook NAME", "Generate new hook")
18 |
19 | long_desc Help.text("new/project")
20 | Project.cli_options.each { |args| option(*args) }
21 | register(Project, "project", "project NAME", "Generate new project")
22 |
23 | long_desc Help.text("new/shim")
24 | Shim.cli_options.each { |args| option(*args) }
25 | register(Shim, "shim", "shim NAME", "Generate new shim")
26 |
27 | desc "test SUBCOMMAND", "test subcommands"
28 | long_desc Help.text(:test)
29 | subcommand "test", Test
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/lib/lono/cli/new/blueprint.rb:
--------------------------------------------------------------------------------
1 | class Lono::CLI::New
2 | class Blueprint < Sequence
3 | argument :name
4 | def self.cli_options
5 | [
6 | [:examples, type: :boolean, desc: "Whether not to generate examples"],
7 | [:force, type: :boolean, desc: "Bypass overwrite are you sure prompt for existing files"],
8 | ]
9 | end
10 | cli_options.each do |args|
11 | class_option(*args)
12 | end
13 |
14 | def set_source
15 | if @options[:examples]
16 | set_template_source "examples/blueprint"
17 | else
18 | set_template_source "blueprint"
19 | end
20 | end
21 |
22 | def create_blueprint
23 | logger.info "=> Creating new blueprint: #{name}"
24 | directory ".", "app/blueprints/#{name}"
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/lib/lono/cli/new/concerns.rb:
--------------------------------------------------------------------------------
1 | class Lono::CLI::New
2 | module Concerns
3 | extend ActiveSupport::Concern
4 |
5 | private
6 | def class_name
7 | name.underscore.camelize
8 | end
9 |
10 | # Files should be named with underscores instead of dashes even though project name can contain a dash.
11 | # This is because autoloading works with underscores in the filenames only.
12 | def underscore_name
13 | name.underscore
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/lono/cli/new/configset.rb:
--------------------------------------------------------------------------------
1 | class Lono::CLI::New
2 | class Configset < Sequence
3 | argument :name
4 | def self.cli_options
5 | [
6 | [:examples, type: :boolean, desc: "Whether not to generate examples"],
7 | [:force, type: :boolean, desc: "Bypass overwrite are you sure prompt for existing files"],
8 | ]
9 | end
10 | cli_options.each do |args|
11 | class_option(*args)
12 | end
13 |
14 | def set_source
15 | if @options[:examples]
16 | set_template_source "examples/configset"
17 | else
18 | set_template_source "configset"
19 | end
20 | end
21 |
22 | def create_configset
23 | dest = "#{Lono.root}/app/configsets"
24 | directory ".", "#{dest}/#{name}"
25 | end
26 |
27 | def welcome_message
28 | puts <<~EOL
29 | #{"="*64}
30 | Congrats 🎉 You have successfully created a lono configset.
31 |
32 | More info: https://lono.cloud/docs/configsets
33 |
34 | EOL
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/lib/lono/cli/new/helper.rb:
--------------------------------------------------------------------------------
1 | class Lono::CLI::New
2 | class Helper < Lono::CLI::New::Sequence
3 | def self.cli_options
4 | # required for name => underscore_name => app/blueprints/demo/helpers/%underscore_name%_helper.rb.tt
5 | argument :name, default: "custom", desc: "Helper name"
6 |
7 | [
8 | [:force, type: :boolean, desc: "Bypass overwrite are you sure prompt for existing files"],
9 | [:blueprint, aliases: :b, desc: "Blueprint name. Only use you want a blueprint helper. Otherwise a project helper is generated"],
10 | ]
11 | end
12 | cli_options.each do |args|
13 | class_option(*args)
14 | end
15 |
16 | def set_source
17 | set_template_source "helper"
18 | end
19 |
20 | def create_helper
21 | if @options[:blueprint]
22 | create_blueprint_helper
23 | else
24 | create_project_helper
25 | end
26 | end
27 |
28 | private
29 | def create_blueprint_helper
30 | @blueprint = @options[:blueprint] # allows %underscore_name%_helper.rb.tt to access @blueprint
31 | logger.info "=> Generating #{underscore_name}_helper.rb"
32 | directory ".", "app/blueprints/#{@blueprint}/helpers"
33 | end
34 |
35 | def create_project_helper
36 | logger.info "=> Generating #{underscore_name}_helper.rb"
37 | directory ".", "app/helpers/#{underscore_name}"
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/lono/cli/new/hook.rb:
--------------------------------------------------------------------------------
1 | class Lono::CLI::New
2 | class Hook < Lono::CLI::New::Sequence
3 | def self.cli_options
4 | [
5 | [:force, type: :boolean, desc: "Bypass overwrite are you sure prompt for existing files"],
6 | [:blueprint, aliases: :b, desc: "Blueprint name. Only use you want a blueprint helper. Otherwise a project helper is generated"],
7 | ]
8 | end
9 | cli_options.each do |args|
10 | class_option(*args)
11 | end
12 |
13 | def set_source
14 | set_template_source "hook"
15 | end
16 |
17 | def create_hook
18 | logger.info "=> Generating hook"
19 | if blueprint
20 | directory ".", "app/blueprints/#{blueprint}"
21 | else
22 | directory ".", "."
23 | end
24 | end
25 |
26 | private
27 | # So templates/hooks/config/hooks.rb.tt has access to blueprint
28 | def blueprint
29 | @options[:blueprint]
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/lib/lono/cli/new/sequence.rb:
--------------------------------------------------------------------------------
1 | require 'fileutils'
2 | require 'active_support'
3 | require 'active_support/core_ext/string'
4 | require 'thor'
5 | require 'bundler'
6 |
7 | class Lono::CLI::New
8 | class Sequence < Thor::Group
9 | include Concerns
10 | include Lono::Utils::Logging
11 | include Thor::Actions
12 |
13 | private
14 | def self.set_template_source(folder)
15 | path = File.expand_path("../../../templates/#{folder}", __dir__)
16 | source_root path
17 | end
18 |
19 | def set_template_source(*paths)
20 | paths = paths.flatten.map do |path|
21 | File.expand_path("../../../templates/#{path}", __dir__)
22 | end
23 | set_template_paths(paths)
24 | end
25 |
26 | def set_template_paths(*paths)
27 | paths.flatten!
28 | # https://github.com/erikhuda/thor/blob/34df888d721ecaa8cf0cea97d51dc6c388002742/lib/thor/actions.rb#L128
29 | instance_variable_set(:@source_paths, nil) # unset instance variable cache
30 | # Using string with instance_eval because block doesnt have access to path at runtime.
31 | instance_eval %{
32 | def self.source_paths
33 | #{paths.flatten.inspect}
34 | end
35 | }
36 | end
37 |
38 | def git_installed?
39 | system("type git > /dev/null 2>&1")
40 | end
41 |
42 | def run_git?
43 | options[:git] && git_installed?
44 | end
45 |
46 | def run_git_init
47 | return unless run_git?
48 | puts "=> Initialize git repo"
49 | run("git init")
50 | end
51 |
52 | def env
53 | Lono.env # allows for seed/vars/%env%.rb.tt
54 | end
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/lib/lono/cli/new/shim.rb:
--------------------------------------------------------------------------------
1 | class Lono::CLI::New
2 | class Shim < Thor::Group
3 | include Thor::Actions
4 |
5 | def self.cli_options
6 | [
7 | [:path, aliases: %w[p], default: "/usr/local/bin/lono", desc: "path to save the shim script"],
8 | ]
9 | end
10 | cli_options.each { |args| class_option(*args) }
11 |
12 | def self.source_root
13 | File.expand_path("../../../templates/shim", __dir__)
14 | end
15 |
16 | def set_vars
17 | @path = @options[:path]
18 | end
19 |
20 | def create
21 | dest = @path
22 | template "lono", dest
23 | chmod dest, 0755
24 | end
25 |
26 | def message
27 | dir = File.dirname(@path)
28 | puts <<~EOL
29 | A lono shim as been generated at #{@path}
30 | Please make sure that it is found in the $PATH.
31 |
32 | You can double check with:
33 |
34 | which lono
35 |
36 | You should see
37 |
38 | $ which lono
39 | #{@path}
40 |
41 | If you do not, please add #{dir} to your PATH.
42 | You can usually do so by adding this line to ~/.bash_profile and opening a new terminal to check.
43 |
44 | export PATH=#{dir}:/$PATH
45 |
46 | Also note, the shim wrapper contains starter code. Though it should generally work for most systems,
47 | it might require adjustments depending on your system.
48 | EOL
49 | end
50 |
51 | private
52 | def switch_ruby_version_line
53 | rbenv_installed = system("type rbenv > /dev/null 2>&1")
54 | if rbenv_installed
55 | 'eval "$(rbenv init -)"'
56 | end
57 | end
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/lib/lono/cli/new/test.rb:
--------------------------------------------------------------------------------
1 | class Lono::CLI::New
2 | class Test < Lono::Command
3 | Help = Lono::CLI::Help
4 | long_desc Help.text("new/test/blueprint")
5 | Blueprint.cli_options.each { |args| option(*args) }
6 | register(Blueprint, "blueprint", "blueprint NAME", "Generate new blueprint test.")
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/lib/lono/cli/new/test/blueprint.rb:
--------------------------------------------------------------------------------
1 | class Lono::CLI::New::Test
2 | class Blueprint < Sequence
3 | argument :name
4 |
5 | def self.cli_options
6 | [
7 | [:force, type: :boolean, desc: "Bypass overwrite are you sure prompt for existing files"],
8 | ]
9 | end
10 | cli_options.each { |args| class_option(*args) }
11 |
12 | def set_source
13 | name = Lono.config.test.framework
14 | framework = Lono::Plugin.find(name: name, type: "test_framework")
15 | set_template_paths("#{framework.root}/lib/templates/blueprint")
16 | end
17 |
18 | def generate
19 | dest = "app/blueprints/#{name}"
20 | directory ".", dest
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/lono/cli/new/test/sequence.rb:
--------------------------------------------------------------------------------
1 | require 'fileutils'
2 | require 'active_support'
3 | require 'active_support/core_ext/string'
4 | require 'thor'
5 | require 'bundler'
6 |
7 | class Lono::CLI::New::Test
8 | class Sequence < Lono::CLI::New::Sequence
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/lib/lono/cli/opts.rb:
--------------------------------------------------------------------------------
1 | # Note moving this under Lono::CLI::Opts messes with the zeitwerk autoloader.
2 | # A weird workaround is calling Lono::CLI::Opts right after the autoloader and then it seems to fix itself.
3 | # It may be because there's a custom infleciton cli => CLI. Unsure.
4 | # Unsure if there are other side-effects with the workaround so named this:
5 | # Lono::Opts instead of Lono::CLI::Opts
6 | #
7 | # Also, there's Thor Options class, so this is named Opts to avoid having to fully qualify it.
8 | class Lono::CLI
9 | class Opts
10 | def initialize(cli)
11 | @cli = cli
12 | end
13 |
14 | def clean
15 | with_cli_scope do
16 | option :clean, type: :boolean, default: true, desc: "remove all output files before generating"
17 | end
18 | end
19 |
20 | def yes
21 | with_cli_scope do
22 | option :yes, aliases: :y, type: :boolean, desc: "Bypass are you sure prompt"
23 | end
24 | end
25 |
26 | # Based on https://github.com/rails/thor/blob/ab3b5be455791f4efb79f0efb4f88cc6b59c8ccf/lib/thor/actions.rb#L48
27 | def runtime_options
28 | with_cli_scope do
29 | option :force, :type => :boolean, :aliases => "-f", :group => :runtime,
30 | :desc => "Overwrite files that already exist"
31 |
32 | option :skip, :type => :boolean, :aliases => "-s", :group => :runtime,
33 | :desc => "Skip files that already exist"
34 | end
35 | end
36 |
37 | private
38 | def with_cli_scope(&block)
39 | @cli.instance_eval(&block)
40 | end
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/lib/lono/cli/s3.rb:
--------------------------------------------------------------------------------
1 | class Lono::CLI
2 | class S3 < Lono::Command
3 | opts = Opts.new(self)
4 |
5 | desc "deploy", "deploys lono managed s3 bucket"
6 | long_desc Help.text("s3/deploy")
7 | def deploy
8 | Lono::S3::Bucket.new(options).deploy
9 | end
10 |
11 | desc "show", "shows lono managed s3 bucket"
12 | long_desc Help.text("s3/show")
13 | opts.yes
14 | def show
15 | Lono::S3::Bucket.new(options).show
16 | end
17 |
18 | desc "delete", "deletes lono managed s3 bucket"
19 | long_desc Help.text("s3/delete")
20 | opts.yes
21 | def delete
22 | Lono::S3::Bucket.new(options).delete
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/lib/lono/cli/script.rb:
--------------------------------------------------------------------------------
1 | class Lono::CLI
2 | class Script < Lono::Command
3 | desc "build", "Builds `output/scripts/scripts-md5sum.tgz` from `app/script` folder"
4 | long_desc Help.text("script/build")
5 | def build(blueprint)
6 | Build.new(blueprint, options).run
7 | end
8 |
9 | desc "upload", "Uploads `output/scripts/scripts-md5sum.tgz` to s3"
10 | long_desc Help.text("script/upload")
11 | def upload(blueprint)
12 | Upload.new(blueprint, options).run
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/lono/cli/script/base.rb:
--------------------------------------------------------------------------------
1 | class Lono::CLI::Script
2 | class Base < Lono::CLI::Base
3 | SCRIPTS_INFO_PATH = "#{Lono.root}/output/data/scripts_info.txt"
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/lib/lono/cli/script/upload.rb:
--------------------------------------------------------------------------------
1 | require "filesize"
2 |
3 | class Lono::CLI::Script
4 | class Upload < Base
5 | include Lono::AwsServices
6 |
7 | def run
8 | return unless scripts_built?
9 |
10 | upload(tarball_path)
11 | puts "Uploaded #{File.basename(s3_dest)} to s3."
12 | end
13 |
14 | def upload(tarball_path)
15 | puts "Uploading scripts.tgz (#{filesize}) to #{s3_dest}"
16 | obj = s3_resource.bucket(bucket_name).object(key)
17 | start_time = Time.now
18 | obj.upload_file(tarball_path)
19 | time_took = pretty_time(Time.now-start_time).color(:green)
20 | puts "Time took to upload code to s3: #{time_took}"
21 | end
22 |
23 | def filesize
24 | Filesize.from(File.size(tarball_path).to_s + " B").pretty
25 | end
26 |
27 | def s3_dest
28 | "s3://#{bucket_name}/#{key}"
29 | end
30 |
31 | def key
32 | # Example key: cloudformation/development/scripts/scripts-md5
33 | "#{Lono.env}/output/#{@blueprint.name}/scripts/#{File.basename(tarball_path)}"
34 | end
35 |
36 | def bucket_name
37 | Lono::S3::Bucket.name
38 | end
39 |
40 | # Scripts are only built if the app/scripts folder is non empty
41 | def scripts_built?
42 | File.exist?(SCRIPTS_INFO_PATH) && !tarball_path.empty?
43 | end
44 |
45 | def tarball_path
46 | IO.read(SCRIPTS_INFO_PATH).strip
47 | end
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/lib/lono/cli/seed.rb:
--------------------------------------------------------------------------------
1 | class Lono::CLI
2 | class Seed < Lono::CLI::Base
3 | def create
4 | logger.info "Creating starter config files for #{@blueprint.name}"
5 | seeder = Lono::Seeder.new(@options)
6 | seeder.run
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/lono/cli/status.rb:
--------------------------------------------------------------------------------
1 | class Lono::CLI
2 | class Status < Lono::CLI::Base
3 | include Lono::AwsServices
4 |
5 | def run
6 | names = Lono::Names.new(@options)
7 | stack = find_stack(@stack)
8 | if stack
9 | status = Lono::Cfn::Status.new(@stack, @options)
10 | success = status.run
11 | exit 1 unless success
12 | else
13 | logger.error "ERROR: stack #{@stack} not found".color(:red)
14 | exit 1
15 | end
16 | end
17 | end
18 | end
--------------------------------------------------------------------------------
/lib/lono/cli/test.rb:
--------------------------------------------------------------------------------
1 | class Lono::CLI
2 | class Test
3 | def initialize(options={})
4 | @options = options
5 | end
6 |
7 | def run
8 | config = Lono.config
9 | test_command = config.test.framework
10 | execute(test_command)
11 | end
12 |
13 | def execute(command)
14 | command = adjust_command(command)
15 | puts "=> #{command}"
16 | Kernel.exec(command)
17 | end
18 |
19 | def adjust_command(command)
20 | if cd_into_test?
21 | command = "bundle exec #{command}" unless command.include?("bundle exec")
22 | command = "cd test && #{command}"
23 | else
24 | command
25 | end
26 | end
27 |
28 | # Automatically cd into the test folder in case running within the root of a module.
29 | # Detect/guess that we're in a module folder vs the lono project
30 | def cd_into_test?
31 | !File.exist?("app") && File.exist?("test") &&
32 | (File.exist?("template.rb") || File.exist?("template"))
33 | end
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/lib/lono/completer/script.rb:
--------------------------------------------------------------------------------
1 | class Lono::Completer::Script
2 | def self.generate
3 | bash_script = File.expand_path("script.sh", File.dirname(__FILE__))
4 | puts "source #{bash_script}"
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/lib/lono/completer/script.sh:
--------------------------------------------------------------------------------
1 | _lono() {
2 | COMPREPLY=()
3 | local word="${COMP_WORDS[COMP_CWORD]}"
4 | local words=("${COMP_WORDS[@]}")
5 | unset words[0]
6 | local completion=$(lono completion ${words[@]})
7 | COMPREPLY=( $(compgen -W "$completion" -- "$word") )
8 | }
9 |
10 | complete -F _lono lono
11 |
--------------------------------------------------------------------------------
/lib/lono/component.rb:
--------------------------------------------------------------------------------
1 | module Lono
2 | class Component
3 | attr_reader :name
4 | def initialize(options={})
5 | @options = options
6 | @name = options[:name]
7 | end
8 |
9 | def type
10 | self.class.name.to_s.split('::').last.underscore # IE: blueprint
11 | end
12 |
13 | def exist?
14 | !root.nil?
15 | end
16 |
17 | def type_dir
18 | type.pluralize # IE: blueprints
19 | end
20 |
21 | def root
22 | paths = Dir.glob("#{Lono.root}/{app,vendor}/#{type_dir}/*")
23 | paths.find do |path|
24 | found = path.sub(%r{.*/(app|vendor)/}, '')
25 | found == "#{type_dir}/#{@name}" # exact match
26 | end
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/lib/lono/concerns/aws_info.rb:
--------------------------------------------------------------------------------
1 | # Dont name AwsData. Think prefer AwsInfo vs AwsConcern
2 | module Lono::Concerns
3 | module AwsInfo
4 | extend Memoist
5 | delegate :region, to: :aws_data
6 | alias_method :aws_region, :region
7 | alias_method :current_region, :region
8 |
9 | def aws_data
10 | AwsData.new
11 | end
12 | memoize :aws_data
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/lib/lono/concerns/names.rb:
--------------------------------------------------------------------------------
1 | module Lono::Concerns
2 | module Names
3 | extend Memoist
4 | def names
5 | Lono::Names.new(@options)
6 | end
7 | memoize :names
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/lono/configset.rb:
--------------------------------------------------------------------------------
1 | module Lono
2 | class Configset < Component
3 | attr_reader :resource
4 | attr_accessor :metadata
5 | def initialize(options={})
6 | super
7 | @resource = options[:resource]
8 | end
9 |
10 | def path
11 | return unless root
12 | exts = %w[rb yml json] # rb highest precedence
13 | paths = exts.map { |ext| "#{root}/configset.#{ext}" }
14 | paths.find { |p| File.exist?(p) }
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/lono/core.rb:
--------------------------------------------------------------------------------
1 | require 'pathname'
2 |
3 | module Lono
4 | module Core
5 | extend Memoist
6 |
7 | cattr_accessor :argv
8 | cattr_accessor :check_project, default: true
9 |
10 | # allow testing frameworks to switch root
11 | cattr_writer :root
12 | def root
13 | path = @@root || ENV['LONO_ROOT'] || Dir.pwd
14 | Pathname.new(path)
15 | end
16 |
17 | def app
18 | ENV['LONO_APP']
19 | end
20 | memoize :app
21 |
22 | def role
23 | ENV['LONO_ROLE']
24 | end
25 | memoize :role
26 |
27 | def env
28 | ENV['LONO_ENV'] || 'dev'
29 | end
30 | memoize :env
31 |
32 | def extra
33 | ENV['LONO_EXTRA']
34 | end
35 | memoize :extra
36 |
37 | def tmp_root
38 | ENV['LONO_TMP_ROOT'] || "/tmp/lono"
39 | end
40 | memoize :tmp_root
41 |
42 | def log_root
43 | "#{root}/log"
44 | end
45 |
46 | def configure(&block)
47 | App.instance.configure(&block)
48 | end
49 |
50 | # Generally, use the Lono.config instead of App.instance.config since it guarantees the load_project_config call
51 | def config
52 | App.instance.load_project_config
53 | App.instance.config
54 | end
55 | memoize :config
56 |
57 | # allow different logger when running up all or rspec-lono
58 | cattr_writer :logger
59 | def logger
60 | @@logger ||= config.logger
61 | end
62 | end
63 | end
64 |
--------------------------------------------------------------------------------
/lib/lono/ext.rb:
--------------------------------------------------------------------------------
1 | require_relative "ext/bundler"
2 | require_relative "ext/core/module"
3 | require_relative "ext/core/object"
4 | require_relative "ext/core/string"
5 |
--------------------------------------------------------------------------------
/lib/lono/ext/bundler.rb:
--------------------------------------------------------------------------------
1 | # Bundler 2.0 does yet not have with_unbundled_env
2 | # Bundler 2.1 deprecates with_clean_env for with_unbundled_env
3 |
4 | require "bundler"
5 | def Bundler.with_unbundled_env(&block)
6 | with_clean_env(&block)
7 | end unless Bundler.respond_to?(:with_unbundled_env)
8 |
--------------------------------------------------------------------------------
/lib/lono/ext/core/module.rb:
--------------------------------------------------------------------------------
1 | class Module
2 | # Include all modules within the relative folder. IE: for dsl/syntax/mod/*
3 | #
4 | # include Common
5 | # include Provider
6 | # # etc
7 | #
8 | # Caller lines are different for OSes:
9 | #
10 | # windows: "C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/lono-1.1.1/lib/lono/builder.rb:34:in `build'"
11 | # linux: "/home/ec2-user/.rvm/gems/ruby-3.0.3/gems/lono-1.1.1/lib/lono/compiler/dsl/syntax/mod.rb:4:in `'"
12 | #
13 | def include_modules(dir)
14 | caller_line = caller[0]
15 | parts = caller_line.split(':')
16 | calling_file = caller_line.match(/^[a-zA-Z]:/) ? parts[1] : parts[0]
17 | parent_dir = File.dirname(calling_file)
18 |
19 | full_dir = "#{parent_dir}/#{dir}"
20 | paths = Dir.glob("#{full_dir}/**/*.rb")
21 | if paths.empty?
22 | raise "Empty include_modules full_dir: #{full_dir}"
23 | end
24 | paths.each do |path|
25 | regexp = Regexp.new(".*/lib/")
26 | mod = path.sub(regexp, '').sub('.rb','').camelize
27 | c = mod.constantize
28 | include c if c.class == Module
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/lib/lono/ext/core/object.rb:
--------------------------------------------------------------------------------
1 | class Object
2 | # Load custom helper methods from project
3 | def load_helper_files(dir, type: :blueprint)
4 | paths = Dir.glob("#{dir}/**/*.rb")
5 | paths.sort_by! { |p| p.size } # so namespaces are loaded first
6 | paths.each do |path|
7 | next unless File.file?(path)
8 |
9 | filename = if type == :project
10 | path.sub(%r{.*/helpers/[a-zA-Z\-_]+/},'').sub('.rb','')
11 | else # blueprint, configset
12 | path.sub(%r{.*/helpers/},'').sub('.rb','')
13 | end
14 | module_name = filename.camelize
15 |
16 | # Prepend a period so require works when LONO_ROOT is set to a relative path without a period.
17 | # Example: LONO_ROOT=tmp/lono_project
18 | first_char = path[0..0]
19 | path = "./#{path}" unless %w[. /].include?(first_char)
20 |
21 | # Examples:
22 | # project:
23 | # path: app/helpers/custom/custom_helper.rb
24 | # module_name: CustomHelper
25 | # blueprint:
26 | # path: app/blueprints/demo/helpers/outputs.rb
27 | # module_name: Outputs
28 | require path
29 | if path.include?("_helper.rb")
30 | self.class.send :include, module_name.constantize
31 | end
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/lib/lono/ext/core/string.rb:
--------------------------------------------------------------------------------
1 | module Lono
2 | refine String do
3 | def camelcase
4 | self.underscore.camelize
5 | end
6 | end
7 | end
8 |
9 | using Lono
10 |
--------------------------------------------------------------------------------
/lib/lono/files.rb:
--------------------------------------------------------------------------------
1 | # Class name is Lono::Files because can represent a single file or a directory.
2 | # Also, it avoids conflicting with the stdlib File class name.
3 | # Files is like a "FileList". Using shorter Files name.
4 | module Lono
5 | class Files < Lono::CLI::Base
6 | include Concerns::Registration
7 | include Concerns::PostProcessing
8 |
9 | attr_reader :path, :layer, :type
10 | def initialize(options={})
11 | super
12 | @path = options[:path]
13 | @layer = options[:layer]
14 | @type = @layer ? "layer" : "normal"
15 | @caller = options[:caller] # original caller stack at registration time
16 | end
17 |
18 | def full_path
19 | "#{@blueprint.root}/#{@path}"
20 | end
21 |
22 | class << self
23 | def register(options={})
24 | path = options[:path]
25 | layer = options[:layer]
26 | # Registration uniquess based on both path and layer option
27 | file = files.find { |f| f.path == path && f.layer == layer }
28 | unless file
29 | file = Files.new(options.merge(caller: caller))
30 | files << file
31 | end
32 | file.marker
33 | end
34 |
35 | delegate :empty?, :files, to: :files
36 | def files
37 | Registry.files
38 | end
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/lib/lono/files/base.rb:
--------------------------------------------------------------------------------
1 | class Lono::Files
2 | class Base < Lono::CLI::Base
3 | delegate :full_path, :output_path, :zip_name, :zip_path, to: :files
4 | attr_reader :files
5 | def initialize(options={})
6 | super
7 | # @files is reference to Lono::Files self instance
8 | # IE: Lono::Files instance is the caller
9 | @files = options[:files]
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/lib/lono/files/builder.rb:
--------------------------------------------------------------------------------
1 | class Lono::Files
2 | class Builder < Base
3 | extend Memoist
4 | include Lono::Builder::Context
5 | include Lono::Builder::Dsl::Syntax
6 | # Tricky must define files again to avoid conflict with files from loading the Dsl::Syntax
7 | attr_reader :files
8 |
9 | def run
10 | load_context
11 | if File.directory?(full_path)
12 | directory(@files)
13 | else
14 | template(@files)
15 | end
16 | end
17 |
18 | def template(files)
19 | sequence.send(:set_template_paths, @blueprint.root)
20 | src = files.path
21 | dest = files.output_path
22 | sequence.template(src, dest)
23 | end
24 |
25 | def directory(files)
26 | src = files.full_path
27 | sequence.send(:set_template_paths, src)
28 | sequence.destination_root = @files.output_path
29 | sequence.directory(".", verbose: false, force: true, context: binding) # Thor::Action
30 | end
31 |
32 | def sequence
33 | Lono::CLI::New::Sequence.new(@options)
34 | end
35 | memoize :sequence
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/lib/lono/files/builder/lambda_layer.rb:
--------------------------------------------------------------------------------
1 | class Lono::Files::Builder
2 | class LambdaLayer < Lono::Files::Base
3 | def run
4 | layer = @options[:layer]
5 | return unless layer # triggers Lambda Layer building
6 | unless layer =~ /ruby/
7 | logger.warn "WARN: Currently only support Ruby lambda layers automatically".color(:yellow)
8 | return
9 | end
10 |
11 | klass_name = "Lono::Files::Builder::LambdaLayer::#{layer.camelize}Packager"
12 | klass = klass_name.constantize
13 | packager = klass.new(@options)
14 | packager.build
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/lono/files/builder/lambda_layer/rsync.rb:
--------------------------------------------------------------------------------
1 | require 'shellwords'
2 |
3 | class Lono::Files::Builder::LambdaLayer
4 | module Rsync
5 | def sh(command)
6 | logger.debug "=> #{command}"
7 | out = `#{command}`
8 | logger.debug out
9 | success = $?.success?
10 | raise("ERROR: running command #{command}").color(:red) unless success
11 | success
12 | end
13 |
14 | def rsync(src, dest)
15 | # Using FileUtils.cp_r doesnt work if there are special files like socket files in the src dir.
16 | # Instead of using this hack https://bugs.ruby-lang.org/issues/10104
17 | # Using rsync to perform the copy.
18 | src.chop! if src.ends_with?('/')
19 | dest.chop! if dest.ends_with?('/')
20 | check_rsync_installed!
21 | # Ensures required trailing slashes
22 | FileUtils.mkdir_p(File.dirname(dest))
23 | sh "rsync -a --links --no-specials --no-devices #{Shellwords.escape(src)}/ #{Shellwords.escape(dest)}/"
24 | end
25 |
26 | @@rsync_installed = false
27 | def check_rsync_installed!
28 | return if @@rsync_installed # only check once
29 | if system "type rsync > /dev/null 2>&1"
30 | @@rsync_installed = true
31 | else
32 | raise "ERROR: Rsync is required. Rsync does not seem to be installed.".color(:red)
33 | end
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/lib/lono/files/builder/lambda_layer/ruby_version.rb:
--------------------------------------------------------------------------------
1 | class Lono::Files::Builder::LambdaLayer
2 | class RubyVersion < Lono::Files::Base
3 | def initialize(options={})
4 | # cfn passed from Finalizer::Files::Build#build_files to
5 | # Lono::Files#build_lambda_layer(@cfn)
6 | # This because Lono::Files at initialization only has registration info
7 | # The build info is passed to the build_lambda_layer method
8 | @cfn = options[:cfn]
9 | super
10 | end
11 |
12 | def check!
13 | # IE: logical_id = LayerVersion
14 | # attrs = attrs with attrs['Properties']
15 | logical_id, attrs = @cfn['Resources'].find do |logical_id, attrs|
16 | attrs.dig('Properties', 'Content', 'S3Key') == @files.marker
17 | end
18 | props = attrs['Properties']
19 | major, minor, _ = RUBY_VERSION.split('.')
20 | current_ruby = "ruby#{major}.#{minor}"
21 | ok = props['CompatibleRuntimes'].include?(current_ruby)
22 | return if ok
23 |
24 | runtimes = props['CompatibleRuntimes'].map { |s| s.sub('ruby','') }.join(', ')
25 | logger.error "ERROR: Current Ruby Version does not match the Lambda Layer Ruby Version".color(:red)
26 | logger.error <<~EOL
27 | Current Ruby: #{RUBY_VERSION}
28 | Lambda Layer Ruby Version: #{runtimes}
29 |
30 | Lono is unable to package up a Lambda Layer
31 | unless you use the same Ruby version on this machine.
32 | EOL
33 | logger.error "Resource: #{logical_id}"
34 | logger.error YAML.dump(attrs)
35 | exit 1
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/lib/lono/files/compressor.rb:
--------------------------------------------------------------------------------
1 | require 'fileutils'
2 |
3 | class Lono::Files
4 | class Compressor < Base
5 | def run
6 | if File.directory?(output_path)
7 | zip_directory
8 | else
9 | zip_file
10 | end
11 | end
12 |
13 | def zip_file
14 | logger.debug "Zipping file to #{pretty_path(zip_path)}"
15 | # create zipfile at same level of file
16 | command = "cd #{File.dirname(output_path)} && zip -q #{zip_name} #{File.basename(output_path)}"
17 | execute_zip(command)
18 | end
19 |
20 | def zip_directory
21 | logger.debug "Zipping folder to #{zip_path}"
22 | command = "cd #{output_path} && zip --symlinks -rq #{zip_name} ." # create zipfile witih directory
23 | execute_zip(command)
24 | FileUtils.mv("#{output_path}/#{zip_name}", "#{File.dirname(output_path)}/#{zip_name}") # move zip back to the parent directory
25 | end
26 |
27 | private
28 | def execute_zip(command)
29 | check_zip_installed!
30 | logger.debug "=> #{command}".color(:green)
31 | `#{command}`
32 | return if $?.success?
33 | logger.info "ERROR: Fail trying to zip files".color(:red)
34 | logger.info "Command: #{command}".color(:red)
35 | exit 1
36 | end
37 |
38 | @@zip_installed = false
39 | def check_zip_installed!
40 | @@zip_installed = system("type zip > /dev/null 2>&1")
41 | if @@zip_installed
42 | @@zip_installed = true
43 | return
44 | end
45 | logger.error "ERROR: The command 'zip' is not installed.".color(:red)
46 | logger.error <<~EOL
47 | The file helper requires the zip command.
48 | Please install the zip command on this system.
49 | EOL
50 | exit 1
51 | end
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/lib/lono/files/concerns/post_processing.rb:
--------------------------------------------------------------------------------
1 | module Lono::Files::Concerns
2 | module PostProcessing
3 | extend Memoist
4 |
5 | def build
6 | Lono::Files::Builder.new(@options.merge(files: self)).run
7 | end
8 |
9 | def build_lambda_layer(cfn)
10 | Lono::Files::Builder::LambdaLayer.new(@options.merge(files: self, cfn: cfn)).run
11 | end
12 |
13 | def compress
14 | Lono::Files::Compressor.new(@options.merge(files: self)).run
15 | end
16 |
17 | def upload
18 | uploader.upload # Lono::S3::Uploader#upload
19 | end
20 |
21 | delegate :s3_path, :s3_key, to: :uploader
22 | def uploader
23 | Lono::S3::Uploader.new(zip_path)
24 | end
25 | memoize :uploader
26 |
27 | def zip_name
28 | name = File.basename(full_path)
29 | name << "-layer" if layer
30 | "#{name}-#{Lono::Md5.sum(full_path)}.zip"
31 | end
32 |
33 | def zip_path
34 | "#{File.dirname(output_path)}/#{zip_name}"
35 | end
36 |
37 | def output_path
38 | if layer
39 | # For Lambda Layer the final output path is the opt folder, where final artifact is zipped
40 | "#{Lono.root}/output/#{@blueprint.name}/layer/#{path}/opt" # Note: layer and opt
41 | else
42 | # For normal files the final output path in the compiled path
43 | compiled_path
44 | end
45 | end
46 |
47 | # Also used by LambdaLayer::RubyPackager#compiled_area
48 | def compiled_path
49 | "#{Lono.root}/output/#{@blueprint.name}/normal/#{path}"
50 | end
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/lib/lono/files/concerns/registration.rb:
--------------------------------------------------------------------------------
1 | module Lono::Files::Concerns
2 | module Registration
3 | def call_line
4 | @caller.find do |line|
5 | line.include?(Lono.root.to_s)
6 | end
7 | end
8 |
9 | # IE:
10 | # LONO:://files/function-normal
11 | # LONO:://files/function-layer
12 | def marker
13 | "LONO://#{@path}-#{@type}"
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/lono/files/registry.rb:
--------------------------------------------------------------------------------
1 | class Lono::Files
2 | class Registry
3 | class_attribute :files
4 | self.files = []
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/lib/lono/hooks/builder.rb:
--------------------------------------------------------------------------------
1 | module Lono::Hooks
2 | class Builder
3 | extend Memoist
4 | include Dsl
5 | include DslEvaluator
6 | include Lono::Utils
7 |
8 | # IE: dsl_file: config/hooks.rb
9 | attr_accessor :name
10 | def initialize(blueprint, name)
11 | @blueprint, @name = blueprint, name
12 | @hooks = {before: {}, after: {}}
13 | end
14 |
15 | def build
16 | evaluate_file("#{Lono.root}/config/hooks.rb")
17 | evaluate_file("#{@blueprint.root}/config/hooks.rb")
18 | @hooks.deep_stringify_keys!
19 | end
20 | memoize :build
21 |
22 | def run_hooks
23 | build
24 | run_each_hook("before")
25 | out = yield if block_given?
26 | run_each_hook("after")
27 | out
28 | end
29 |
30 | def run_each_hook(type)
31 | hooks = @hooks.dig(type, @name) || []
32 | hooks.each do |hook|
33 | run_hook(type, hook)
34 | end
35 | end
36 |
37 | def run_hook(type, hook)
38 | return unless run?(hook)
39 |
40 | id = "#{type} #{@name}"
41 | label = " label: #{hook["label"]}" if hook["label"]
42 | logger.info "Hook: Running #{id} hook#{label}".color(:cyan) if Lono.config.hooks.show
43 | Runner.new(@blueprint, hook).run
44 | end
45 |
46 | def run?(hook)
47 | !!hook["execute"]
48 | end
49 | end
50 | end
51 |
--------------------------------------------------------------------------------
/lib/lono/hooks/concern.rb:
--------------------------------------------------------------------------------
1 | module Lono::Hooks
2 | module Concern
3 | def run_hooks(name, &block)
4 | hooks = Builder.new(@blueprint, name)
5 | hooks.build # build hooks
6 | hooks.run_hooks(&block)
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/lono/hooks/dsl.rb:
--------------------------------------------------------------------------------
1 | module Lono::Hooks
2 | module Dsl
3 | def before(*commands, **props)
4 | commands.each do |name|
5 | each_hook(:before, name, props)
6 | end
7 | end
8 |
9 | def after(*commands, **props)
10 | commands.each do |name|
11 | each_hook(:after, name, props)
12 | end
13 | end
14 |
15 | def each_hook(type, name, props={})
16 | @hooks[type][name] ||= []
17 | @hooks[type][name] << props
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/lono/hooks/runner.rb:
--------------------------------------------------------------------------------
1 | module Lono::Hooks
2 | class Runner
3 | include Lono::Utils
4 |
5 | # exposing blueprint and hook so lono hooks have access to them via runner context. IE:
6 | #
7 | # class EnvExporter
8 | # def call(runner)
9 | # puts "runner.hook #{runner.hook}"
10 | # end
11 | # end
12 | #
13 | # Docs: http://lono.cloud/docs/config/hooks/ruby/#method-argument
14 | #
15 | attr_reader :blueprint, :hook
16 | def initialize(blueprint, hook)
17 | @blueprint, @hook = blueprint, hook
18 | @execute = @hook["execute"]
19 | end
20 |
21 | def run
22 | case @execute
23 | when String
24 | sh(@execute, exit_on_fail: @hook["exit_on_fail"])
25 | when -> (e) { e.respond_to?(:public_instance_methods) && e.public_instance_methods.include?(:call) }
26 | executor = @execute.new
27 | when -> (e) { e.respond_to?(:call) }
28 | executor = @execute
29 | else
30 | logger.warn "WARN: execute option not set for hook: #{@hook.inspect}"
31 | end
32 |
33 | return unless executor
34 |
35 | meth = executor.method(:call)
36 | case meth.arity
37 | when 0
38 | executor.call # backwards compatibility
39 | when 1
40 | executor.call(self)
41 | else
42 | raise "The #{executor} call method definition has been more than 1 arguments and is not supported"
43 | end
44 | end
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/lib/lono/importer.rb:
--------------------------------------------------------------------------------
1 | module Lono
2 | class Importer < Lono::CLI::Base
3 | def run
4 | # Examples:
5 | # Lono::Importer::Erb.new(source, options.clone).run
6 | # Lono::Importer::Dsl.new(source, options.clone).run
7 | type = @options[:type] || 'dsl'
8 | importer_class = "Lono::Importer::#{type.classify}"
9 | importer_class = Object.const_get(importer_class)
10 | importer_class.new(@options).run
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/lono/layering.rb:
--------------------------------------------------------------------------------
1 | require "active_support/lazy_load_hooks"
2 |
3 | module Lono
4 | module Layering
5 | def layers
6 | pre_layers + main_layers + post_layers
7 | end
8 |
9 | def main_layers
10 | super
11 | end
12 |
13 | def pre_layers
14 | []
15 | end
16 |
17 | def post_layers
18 | []
19 | end
20 | end
21 | end
22 |
23 | ActiveSupport.run_load_hooks(:lono_layering, Lono::Layering)
24 |
--------------------------------------------------------------------------------
/lib/lono/logger.rb:
--------------------------------------------------------------------------------
1 | require 'logger'
2 |
3 | module Lono
4 | class Logger < ::Logger
5 | def initialize(*args)
6 | super
7 | self.formatter = Formatter.new
8 | self.level = ENV['LONO_LOG_LEVEL'] || :info # note: only respected when config.logger not set in config/app.rb
9 | end
10 |
11 | def format_message(severity, datetime, progname, msg)
12 | line = if @logdev.dev == $stdout || @logdev.dev == $stderr || @logdev.dev.is_a?(StringIO)
13 | msg # super simple format if stdout
14 | else
15 | super # use the configured formatter
16 | end
17 | line =~ /\n$/ ? line : "#{line}\n"
18 | end
19 |
20 | # Used to allow output to always go to stdout
21 | def stdout(msg, newline: true)
22 | if newline
23 | puts msg
24 | else
25 | print msg
26 | end
27 | end
28 |
29 | public :print
30 | public :printf
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/lib/lono/logger/formatter.rb:
--------------------------------------------------------------------------------
1 | class Lono::Logger
2 | class Formatter < ::Logger::Formatter
3 | def call(severity, time, progname, msg)
4 | # careful changing the format. All::Summary uses a regexp on this format to remove the timestamp
5 | "[#{format_datetime(time)} ##{Process.pid} #{progname}]: #{msg}"
6 | end
7 |
8 | private
9 | def format_datetime(time)
10 | time.strftime("%Y-%m-%dT%H:%M:%S")
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/lono/names.rb:
--------------------------------------------------------------------------------
1 | module Lono
2 | class Names
3 | extend Memoist
4 |
5 | attr_reader :blueprint
6 | def initialize(options={})
7 | @options = options
8 | @blueprint = options[:blueprint]
9 | end
10 |
11 | def stack
12 | expansion(Lono.config.names.stack) # IE: :APP-:BLUEPRINT-:ENV
13 | end
14 | memoize :stack
15 |
16 | def expansion(string)
17 | return string unless string.is_a?(String) # in case of nil
18 |
19 | string = string.dup
20 | vars = string.scan(/:\w+/) # => [":APP", ":BLUEPRINT", :ENV"]
21 | vars.each do |var|
22 | string.gsub!(var, var_value(var))
23 | end
24 | cleanse(string)
25 | end
26 |
27 | def var_value(unexpanded)
28 | name = unexpanded.sub(':','').downcase
29 | if respond_to?(name)
30 | send(name).to_s # value
31 | else
32 | unexpanded
33 | end
34 | end
35 |
36 | def cleanse(string)
37 | string.sub(/^-+/,'').sub(/-+$/,'') # remove leading and trailing -
38 | .gsub(%r{-+},'-') # remove double dashes are more. IE: -- => -
39 | .gsub('_','-') # underscores are not allowed in CloudFormation stack names
40 | end
41 |
42 | delegate :app, :role, :env, :extra, to: :lono
43 | def lono
44 | Lono
45 | end
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/lib/lono/plugin.rb:
--------------------------------------------------------------------------------
1 | module Lono
2 | class Plugin
3 | include Meta
4 | delegate_to_meta :name, :root
5 |
6 | def initialize(options={})
7 | @options = options
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/lib/lono/plugin/meta.rb:
--------------------------------------------------------------------------------
1 | class Lono::Plugin
2 | module Meta
3 | extend ActiveSupport::Concern
4 |
5 | included do
6 | cattr_accessor :meta, default: []
7 | end
8 |
9 | class_methods do
10 | def register(o={})
11 | found = meta.find { |m| m[:name] == o[:name] && m[:type] == o[:type] }
12 | meta << o unless found
13 | end
14 |
15 | def find(o={})
16 | found = meta.find { |m| m[:name] == o[:name] && m[:type] == o[:type] }
17 | Lono::Plugin.new(found) if found
18 | end
19 |
20 | def delegate_to_meta(*attrs)
21 | attrs.each do |attr|
22 | attr = attr.to_sym
23 | define_method attr do
24 | @options[attr]
25 | end
26 | end
27 | end
28 | end
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/lib/lono/plugin/tester.rb:
--------------------------------------------------------------------------------
1 | class Lono::Plugin
2 | class Tester
3 | class << self
4 | def register(name, options={})
5 | Lono::Plugin.register(
6 | name: name,
7 | type: "test_framework",
8 | root: options[:root],
9 | )
10 | end
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/lono/registration.rb:
--------------------------------------------------------------------------------
1 | module Lono
2 | class Registration < Command
3 | desc "check", "Check .lono/registration.yml file"
4 | long_desc Help.text(:check)
5 | option :debug, type: :boolean, desc: "Enable debug mode"
6 | def check
7 | User.new(options.merge(cli: true)).check
8 | end
9 |
10 | desc "temp_check", "Check .lono/temp.yml file", hide: true
11 | def temp_check
12 | Temp.new.check
13 | end
14 |
15 | def self.check
16 | Check.new.check
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/lono/s3/aws_setup.rb:
--------------------------------------------------------------------------------
1 | module Lono::S3
2 | class AwsSetup
3 | include Lono::AwsServices
4 | include Lono::Utils::Logging
5 |
6 | def check!
7 | s3.config.region
8 | rescue Aws::Errors::MissingRegionError => e
9 | logger.info "ERROR: #{e.class}: #{e.message}".color(:red)
10 | logger.info <<~EOL
11 | Unable to detect the AWS_REGION to make AWS API calls. This is might be because the AWS access
12 | has not been set up yet. Please either your ~/.aws files.
13 | EOL
14 | exit 1
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/lono/s3/rollback.rb:
--------------------------------------------------------------------------------
1 | module Lono::S3
2 | class Rollback < Lono::Cfn::Deploy::Rollback
3 | # override initialize
4 | def initialize(stack)
5 | @stack = stack
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/lib/lono/script/base.rb:
--------------------------------------------------------------------------------
1 | module Lono::Script
2 | class Base < Lono::CLI::Base
3 | SCRIPTS_INFO_PATH = "#{Lono.root}/output/data/scripts_info.txt"
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/lib/lono/script/upload.rb:
--------------------------------------------------------------------------------
1 | require "filesize"
2 |
3 | module Lono::Script
4 | class Upload < Base
5 | include Lono::AwsServices
6 | include Lono::Utils::Pretty
7 | include Lono::Utils::Logging
8 |
9 | def run
10 | return unless scripts_built?
11 |
12 | upload(tarball_path)
13 | logger.info "Uploaded #{File.basename(s3_dest)} to s3."
14 | end
15 |
16 | def upload(tarball_path)
17 | logger.info "Uploading scripts.tgz (#{filesize}) to #{s3_dest}"
18 | obj = s3_resource.bucket(bucket_name).object(key)
19 | start_time = Time.now
20 | obj.upload_file(tarball_path)
21 | time_took = pretty_time(Time.now-start_time).color(:green)
22 | logger.info "Time took to upload code to s3: #{time_took}"
23 | end
24 |
25 | def filesize
26 | Filesize.from(File.size(tarball_path).to_s + " B").pretty
27 | end
28 |
29 | def s3_dest
30 | "s3://#{bucket_name}/#{key}"
31 | end
32 |
33 | def key
34 | # Example key: cloudformation/development/scripts/scripts-md5
35 | "#{dest_folder}/#{File.basename(tarball_path)}"
36 | end
37 |
38 | def bucket_name
39 | Lono::S3::Bucket.name
40 | end
41 |
42 | def dest_folder
43 | "#{Lono.env}/scripts"
44 | end
45 |
46 | # Scripts are only built if the scripts folder is non empty
47 | def scripts_built?
48 | File.exist?(SCRIPTS_INFO_PATH) && !tarball_path.empty?
49 | end
50 |
51 | def tarball_path
52 | IO.read(SCRIPTS_INFO_PATH).strip
53 | end
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/lib/lono/user_data.rb:
--------------------------------------------------------------------------------
1 | module Lono
2 | # This class is not use by lono internally. It is really only meant to be
3 | # exposed to the lono user_data command so users can debug their generated
4 | # user_data scripts. It is useful for debugging.
5 | #
6 | # Normally, the Lono::Erb#run method generates the CloudFormation template
7 | # and embeds user-data script into the template.
8 | class UserData < Lono::CLI::Base
9 | include Lono::Builder::Dsl::Syntax
10 |
11 | def initialize(options={})
12 | super
13 | @name = options[:name]
14 | @path = "#{Lono.root}/app/user_data/#{@name}.sh"
15 | end
16 |
17 | def generate
18 | pretty_path = pretty_path(@path)
19 | logger.info "Building user_data for '#{@name}' at #{pretty_path}"
20 | if File.exist?(@path)
21 | logger.info RenderMePretty.result(@path, context: self)
22 | else
23 | logger.info "ERROR: #{pretty_path} does not exist".color(:red)
24 | exit 1
25 | end
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/lono/utils.rb:
--------------------------------------------------------------------------------
1 | module Lono
2 | module Utils
3 | include_modules "utils"
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/lib/lono/utils/call_line.rb:
--------------------------------------------------------------------------------
1 | module Lono::Utils
2 | module CallLine
3 | include Pretty
4 |
5 | def lono_call_line
6 | caller.find { |l| l.include?(Lono.root.to_s) }
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/lono/utils/logging.rb:
--------------------------------------------------------------------------------
1 | module Lono::Utils
2 | module Logging
3 | def logger
4 | Lono.logger
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/lib/lono/utils/pretty.rb:
--------------------------------------------------------------------------------
1 | module Lono::Utils
2 | module Pretty
3 | def pretty_path(path)
4 | path.sub("#{Lono.root}/",'')
5 | end
6 |
7 | # http://stackoverflow.com/questions/4175733/convert-duration-to-hoursminutesseconds-or-similar-in-rails-3-or-ruby
8 | def pretty_time(total_seconds)
9 | minutes = (total_seconds / 60) % 60
10 | seconds = total_seconds % 60
11 | if total_seconds < 60
12 | "#{seconds.to_i}s"
13 | else
14 | "#{minutes.to_i}m #{seconds.to_i}s"
15 | end
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/lono/utils/quit.rb:
--------------------------------------------------------------------------------
1 | module Lono::Utils
2 | module Quit
3 | def quit(code)
4 | ENV['LONO_TEST'] ? raise : exit(code)
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/lib/lono/utils/sh.rb:
--------------------------------------------------------------------------------
1 | module Lono::Utils
2 | module Sh
3 | def sh(command, options={})
4 | on_fail = options[:on_fail].nil? ? "raise" : options[:on_fail]
5 | strategy = options[:strategy].nil? ? "system" : options[:strategy] # system or backticks
6 |
7 | logger.info "=> #{command}"
8 |
9 | if strategy == "backticks"
10 | out = `#{command}`
11 | logger.debug out
12 | success = $?.success?
13 | else
14 | success = system(command)
15 | end
16 |
17 | result = strategy == "backticks" ? out : success
18 | return result if success
19 |
20 | logger.error "ERROR: Running #{command}"
21 | case on_fail.to_sym
22 | when :raise
23 | raise
24 | when :exit
25 | status = $?.exitstatus
26 | exit status
27 | end
28 |
29 | result
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/lib/lono/utils/sure.rb:
--------------------------------------------------------------------------------
1 | module Lono::Utils
2 | module Sure
3 | def sure?(message, desc=nil)
4 | if @options[:yes]
5 | yes = 'y'
6 | else
7 | out = message
8 | if desc
9 | out += "\n#{desc}\nAre you sure? (y/N) "
10 | else
11 | out += " (y/N) "
12 | end
13 | print out
14 | yes = $stdin.gets
15 | end
16 |
17 | unless yes =~ /^y/
18 | puts "Whew! Exiting."
19 | exit 0
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/lono/version.rb:
--------------------------------------------------------------------------------
1 | module Lono
2 | VERSION = "8.0.0-rc6"
3 | end
4 |
--------------------------------------------------------------------------------
/lib/lono/yamler/loader.rb:
--------------------------------------------------------------------------------
1 | require "yaml"
2 |
3 | module Lono::Yamler
4 | class Loader
5 | def self.load(text)
6 | new(text).load
7 | end
8 |
9 | def initialize(text)
10 | @text = text
11 | end
12 |
13 | def load
14 | add_domain_types!
15 | YAML.load(@text)
16 | end
17 |
18 | private
19 | def add_domain_types!
20 | intrinsic_functions.each do |name|
21 | YAML.add_domain_type('', name) do |type,val|
22 | key = type.split('::').last
23 | key = "Fn::" + key unless name == 'Ref'
24 | { key => val }
25 | end
26 | end
27 | end
28 |
29 | def intrinsic_functions
30 | %w[
31 | And
32 | Base64
33 | Cidr
34 | Equals
35 | FindInMap
36 | GetAtt
37 | GetAZs
38 | If
39 | If
40 | ImportValue
41 | Join
42 | Not
43 | Or
44 | Ref
45 | Select
46 | Split
47 | Sub
48 | Transform
49 | ]
50 | end
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/lib/lono/yamler/validator.rb:
--------------------------------------------------------------------------------
1 | module Lono::Yamler
2 | class Validator
3 | include Lono::Utils::Logging
4 | include Lono::Utils::Pretty
5 |
6 | def initialize(path)
7 | @path = path
8 | end
9 |
10 | def validate!
11 | validate_yaml(@path)
12 | end
13 |
14 | def validate_yaml(path)
15 | text = IO.read(path)
16 | begin
17 | YAML.load(text)
18 | rescue Psych::SyntaxError => e
19 | handle_yaml_syntax_error(e, path)
20 | end
21 | end
22 |
23 | def handle_yaml_syntax_error(e, path)
24 | logger.error "ERROR: Invalid YAML. #{e.message}".color(:red)
25 | logger.error "Template for debugging: #{pretty_path(path)}"
26 |
27 | # Grab line info. Example error:
28 | # ERROR: (): could not find expected ':' while scanning a simple key at line 2 column 1
29 | md = e.message.match(/at line (\d+) column (\d+)/)
30 | line = md[1].to_i
31 |
32 | DslEvaluator.print_code(path, line)
33 | exit 1
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/lib/templates/blueprint/template.rb:
--------------------------------------------------------------------------------
1 | # This is where you put your infrastructure code
2 | # Docs: https://lono.cloud/docs/dsl/
3 |
--------------------------------------------------------------------------------
/lib/templates/configset/configset.rb:
--------------------------------------------------------------------------------
1 | # This is where you write your configset code
2 | # Docs: https://lono.cloud/docs/configsets/
3 |
--------------------------------------------------------------------------------
/lib/templates/examples/blueprint/template.rb:
--------------------------------------------------------------------------------
1 | parameter("BucketName", Conditional: true)
2 | parameter("AccessControl", Default: "Private")
3 |
4 | resource("Bucket", "AWS::S3::Bucket",
5 | BucketName: ref("BucketName", Conditional: true),
6 | AccessControl: ref("AccessControl"),
7 | )
8 |
9 | output("BucketName", ref("Bucket"))
10 |
--------------------------------------------------------------------------------
/lib/templates/examples/configset/configset.rb:
--------------------------------------------------------------------------------
1 | configset("config1") do
2 | command("test",
3 | command: 'echo "$CFNTEST" > test1.txt',
4 | env: {
5 | CFNTEST: "I come from config1"
6 | }
7 | )
8 | end
9 | configset("config2") do
10 | command("test",
11 | command: 'echo "$CFNTEST" > test2.txt',
12 | env: {
13 | CFNTEST: "I come from config2"
14 | }
15 | )
16 | end
17 |
--------------------------------------------------------------------------------
/lib/templates/helper/%underscore_name%_helper.rb.tt:
--------------------------------------------------------------------------------
1 | module <%= class_name %>Helper
2 | # Your code goes here
3 | # def custom_method
4 | # ...
5 | # end
6 | end
7 |
--------------------------------------------------------------------------------
/lib/templates/hook/config/hooks.rb.tt:
--------------------------------------------------------------------------------
1 | <%
2 | message = if blueprint
3 | "app/blueprints/#{blueprint}/config/hooks.rb: test blueprint hook for build"
4 | else
5 | "config/hooks.rb: test project hook for build"
6 | end
7 | -%>
8 | before("build",
9 | execute: "echo '<%= message %> before hook'",
10 | )
11 |
12 | after("build",
13 | execute: "echo '<%= message %> after hook'"
14 | )
15 |
--------------------------------------------------------------------------------
/lib/templates/project/.gitignore:
--------------------------------------------------------------------------------
1 | .lono
2 | output
3 | tmp
4 |
--------------------------------------------------------------------------------
/lib/templates/project/Gemfile.tt:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | # gem "lono", "~> <%= Lono::VERSION %>"
4 |
5 | gem "lono", git: "https://github.com/boltops-tools/lono", branch: "master"
6 | gem "rspec-lono", git: "https://github.com/boltops-tools/rspec-lono", branch: "master"
7 |
--------------------------------------------------------------------------------
/lib/templates/project/README.md:
--------------------------------------------------------------------------------
1 | # Lono Project
2 |
3 | This is a Lono project. It contains code to provision Cloud infrastructure built with [CloudFormation](https://aws.amazon.com/cloudformation/) and the [Lono Framework](https://lono.cloud/).
4 |
5 | ## Deploy
6 |
7 | To deploy
8 |
9 | lono up demo # where demo app/blueprints/demo
10 |
--------------------------------------------------------------------------------
/lib/templates/project/config/app.rb:
--------------------------------------------------------------------------------
1 | Lono.configure do |config|
2 | config.logger.level = "info"
3 | end
4 |
--------------------------------------------------------------------------------
/lib/templates/shim/lono:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | <%= switch_ruby_version_line %>
3 | if [ -f config/app.rb ]; then
4 | exec bundle exec lono "$@"
5 | else
6 | exec lono "$@"
7 | fi
8 |
--------------------------------------------------------------------------------
/spec/fixtures/configsets/dsl/httpd/configset.rb:
--------------------------------------------------------------------------------
1 | package("yum",
2 | httpd: []
3 | )
4 | file("/var/www/html/index.html",
5 | content: "headline
"
6 | )
7 | service("sysvinit",
8 | httpd: {
9 | enabled: true,
10 | ensureRunning: true,
11 | }
12 | )
13 |
--------------------------------------------------------------------------------
/spec/fixtures/configsets/erb/httpd/configset.yml:
--------------------------------------------------------------------------------
1 | AWS::CloudFormation::Init:
2 | configSets:
3 | default:
4 | - httpd
5 | httpd:
6 | packages:
7 | yum:
8 | httpd: []
9 | files:
10 | "/var/www/html/index.html":
11 | content: |
12 | <%= indent(@html, 10) %>
13 | services:
14 | sysvinit:
15 | httpd:
16 | enabled: true
17 | ensureRunning: true
--------------------------------------------------------------------------------
/spec/fixtures/configsets/erb/httpd/vars.rb:
--------------------------------------------------------------------------------
1 | @html = "test html"
2 |
--------------------------------------------------------------------------------
/spec/fixtures/configsets/snippets/config1.json:
--------------------------------------------------------------------------------
1 | {
2 | "AWS::CloudFormation::Init": {
3 | "configSets": {
4 | "default": ["aaa1", "aaa2"]
5 | },
6 | "aaa1": {
7 | "commands": {
8 | "test": {
9 | "command": "echo from-aaa1 > test1.txt"
10 | }
11 | }
12 | },
13 | "aaa2": {
14 | "commands": {
15 | "test": {
16 | "command": "echo from-aaa2 > test1.txt"
17 | }
18 | }
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/spec/fixtures/configsets/snippets/config2.json:
--------------------------------------------------------------------------------
1 | {
2 | "AWS::CloudFormation::Init": {
3 | "configSets": {
4 | "default": ["bbb1", "bbb2"]
5 | },
6 | "bbb1": {
7 | "commands": {
8 | "test": {
9 | "command": "echo from-bbb1 > test2.txt"
10 | }
11 | }
12 | },
13 | "bbb2": {
14 | "commands": {
15 | "test": {
16 | "command": "echo from-bbb2 > test2.txt"
17 | }
18 | }
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/spec/fixtures/configsets/snippets/single.yml:
--------------------------------------------------------------------------------
1 | ---
2 | AWS::CloudFormation::Init:
3 | config:
4 | commands:
5 | c1:
6 | command: echo c1 >> test.txt
7 | c2:
8 | command: echo c2 >> test.txt
9 |
--------------------------------------------------------------------------------
/spec/fixtures/configsets/templates/ec2-and-sg-metadata.yml:
--------------------------------------------------------------------------------
1 | Resources:
2 | Instance:
3 | Type: AWS::EC2::Instance
4 | Properties:
5 | ImageId: ami-111
6 | InstanceType: t3.micro
7 | Metadata:
8 | AWS::CloudFormation::Init:
9 | configSets:
10 | default:
11 | - "existing"
12 | existing:
13 | commands:
14 | test:
15 | command: "echo existing1 >> /tmp/test.txt"
16 | SecurityGroup:
17 | Type: AWS::EC2::SecurityGroup
18 | Properties:
19 | GroupName: test
20 | Metadata:
21 | AWS::CloudFormation::Init:
22 | configSets:
23 | default:
24 | - "existing"
25 | existing:
26 | commands:
27 | test:
28 | command: "echo existing2 >> /tmp/test.txt"
29 |
--------------------------------------------------------------------------------
/spec/fixtures/configsets/templates/ec2-and-sg.yml:
--------------------------------------------------------------------------------
1 | Resources:
2 | Instance:
3 | Type: AWS::EC2::Instance
4 | Properties:
5 | ImageId: ami-111
6 | InstanceType: t3.micro
7 | SecurityGroup:
8 | Type: AWS::EC2::SecurityGroup
9 | Properties:
10 | GroupName: test
11 |
--------------------------------------------------------------------------------
/spec/fixtures/configsets/templates/ec2-multiple.yml:
--------------------------------------------------------------------------------
1 | Resources:
2 | Instance:
3 | Metadata:
4 | AWS::CloudFormation::Init:
5 | configSets:
6 | default:
7 | - "existing"
8 | existing:
9 | commands:
10 | test:
11 | command: "echo existing >> /tmp/test.txt"
12 | Type: AWS::EC2::Instance
13 | Properties:
14 | ImageId: ami-111
15 | InstanceType: t3.micro
16 |
--------------------------------------------------------------------------------
/spec/fixtures/configsets/templates/ec2-no-metadata.yml:
--------------------------------------------------------------------------------
1 | Resources:
2 | Instance:
3 | Type: AWS::EC2::Instance
4 | Properties:
5 | ImageId: ami-111
6 | InstanceType: t3.micro
7 |
--------------------------------------------------------------------------------
/spec/fixtures/configsets/templates/ec2-single.yml:
--------------------------------------------------------------------------------
1 | Resources:
2 | Instance:
3 | Metadata:
4 | AWS::CloudFormation::Init:
5 | config:
6 | commands:
7 | existing:
8 | command: existing >> test.txt
9 | Type: AWS::EC2::Instance
10 | Properties:
11 | ImageId: ami-111
12 | InstanceType: t3.micro
13 |
--------------------------------------------------------------------------------
/spec/fixtures/md5/a.rb:
--------------------------------------------------------------------------------
1 | a
2 |
--------------------------------------------------------------------------------
/spec/fixtures/md5/folder/a.rb:
--------------------------------------------------------------------------------
1 | a
2 |
--------------------------------------------------------------------------------
/spec/fixtures/md5/folder1/folder2/a.rb:
--------------------------------------------------------------------------------
1 | a
2 |
--------------------------------------------------------------------------------
/spec/fixtures/validator/bad.yml:
--------------------------------------------------------------------------------
1 | test: 1
2 | foo
3 | test
4 |
--------------------------------------------------------------------------------
/spec/lono/builder/configset/definition/dsl_spec.rb:
--------------------------------------------------------------------------------
1 | describe Lono::Builder::Configset::Definition::Dsl do
2 | let(:dsl) do
3 | path = "spec/fixtures/configsets/dsl/httpd/configset.rb"
4 | meta = {path: path, resource: "LaunchTemplate", name: "main"}
5 | dsl = Lono::Builder::Configset::Definition::Dsl.new(meta: meta)
6 | configset = Lono::Configset.new(meta)
7 | allow(configset).to receive(:path).and_return path
8 | dsl.instance_variable_set(:@configset, configset)
9 | dsl
10 | end
11 |
12 | context("example") do
13 | it "evaluate" do
14 | metadata = dsl.evaluate
15 | expect(metadata).to be_a(Hash)
16 | data = metadata["Metadata"]
17 | init_key = data.key?("AWS::CloudFormation::Init")
18 | expect(init_key).to be true
19 | template =<<~EOL
20 | ---
21 | AWS::CloudFormation::Init:
22 | configSets:
23 | default:
24 | - main
25 | main:
26 | packages:
27 | yum:
28 | httpd: []
29 | files:
30 | "/var/www/html/index.html":
31 | content: "headline
"
32 | services:
33 | sysvinit:
34 | httpd:
35 | enabled: true
36 | ensureRunning: true
37 | EOL
38 | expect(YAML.dump(data)).to eq template
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/spec/lono/builder/configset/definition/erb_spec.rb:
--------------------------------------------------------------------------------
1 | describe Lono::Builder::Configset::Definition::Erb do
2 | let(:erb) do
3 | path = "spec/fixtures/configsets/erb/httpd/configset.yml"
4 | meta = {path: path, resource: "LaunchTemplate", name: "main"}
5 | erb = Lono::Builder::Configset::Definition::Erb.new(blueprint: "demo", meta: meta)
6 | configset = Lono::Configset.new(meta)
7 | allow(configset).to receive(:path).and_return path
8 | allow(configset).to receive(:root).and_return "spec/fixtures/configsets/erb/httpd"
9 | erb.instance_variable_set(:@configset, configset)
10 | erb
11 | end
12 |
13 | context("example") do
14 | it "evaluate" do
15 | metadata = erb.evaluate
16 | expect(metadata).to be_a(Hash)
17 | data = metadata["Metadata"]
18 | init_key = data.key?("AWS::CloudFormation::Init")
19 | expect(init_key).to be true
20 | template =<<~EOL
21 | ---
22 | AWS::CloudFormation::Init:
23 | configSets:
24 | default:
25 | - httpd
26 | httpd:
27 | packages:
28 | yum:
29 | httpd: []
30 | files:
31 | "/var/www/html/index.html":
32 | content: 'test html
33 |
34 | '
35 | services:
36 | sysvinit:
37 | httpd:
38 | enabled: true
39 | ensureRunning: true
40 | EOL
41 | expect(YAML.dump(data)).to eq template
42 | end
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/spec/lono/cfn/plan/diff/data_spec.rb:
--------------------------------------------------------------------------------
1 | describe Lono::Cfn::Plan::Diff::Data do
2 | it "diff" do
3 | old = {k1: "v1", k2: "v2", k4: "v4"}
4 | new = {k2: "v2changed", k3: "v3", k4: "v4"}
5 | data = described_class.new(old, new)
6 | data.show
7 | expect(logs).to include("Added")
8 | expect(logs).to include("Removed")
9 | expect(logs).to include("Modified")
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/spec/lono/cli_spec.rb:
--------------------------------------------------------------------------------
1 | describe Lono::CLI do
2 | it "version" do
3 | out = sh "exe/lono version"
4 | expect(out).to match /\d+/
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/spec/lono/completion_spec.rb:
--------------------------------------------------------------------------------
1 | describe Lono::CLI do
2 | describe "lono completion" do
3 | commands = {
4 | # "new" => "name", # options is the completion because it's a Thor::Group
5 | "up" => "blueprint",
6 | }
7 | commands.each do |command, expected_word|
8 | it "#{command}" do
9 | out = execute("exe/lono completion #{command}")
10 | expect(out).to include(expected_word) # only checking for one word for simplicity
11 | end
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/spec/lono/md5_spec.rb:
--------------------------------------------------------------------------------
1 | describe Lono::Md5 do
2 | let(:md5) { Lono::Md5 }
3 | context "file" do
4 | it "md5" do
5 | sum = md5.sum("spec/fixtures/md5/a.rb")
6 | expect(sum).to match "a03f0901"
7 | end
8 |
9 | it "name" do
10 | name = md5.name("spec/fixtures/md5/a.rb")
11 | expect(name).to eq "spec/fixtures/md5/a-a03f0901.rb"
12 | end
13 | end
14 |
15 | context "folder" do
16 | it "md5" do
17 | sum = md5.sum("spec/fixtures/md5/folder")
18 | expect(sum).to eq "a03f0901"
19 | end
20 |
21 | it "name" do
22 | name = md5.name("spec/fixtures/md5/folder")
23 | expect(name).to eq "spec/fixtures/md5/folder-a03f0901.zip"
24 | end
25 | end
26 |
27 | context "nested folder" do
28 | it "md5" do
29 | sum = md5.sum("spec/fixtures/md5/folder1/folder2")
30 | expect(sum).to eq "a03f0901"
31 | end
32 |
33 | it "name" do
34 | name = md5.name("spec/fixtures/md5/folder1/folder2")
35 | expect(name).to eq "spec/fixtures/md5/folder1/folder2-a03f0901.zip"
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/spec/lono/yamler/validator_spec.rb:
--------------------------------------------------------------------------------
1 | describe Lono::Yamler::Validator do
2 | subject { Lono::Yamler::Validator.new(path) }
3 | let(:path) { "spec/fixtures/validator/bad.yml" }
4 | it "validate shows exact line of error code where yaml is invalid" do
5 | out = subject.validate!
6 | expect(out).to include("2 foo")
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # Ensures aws api never called. Fixture home folder does not contain ~/.aws/credentials
2 | ENV['HOME'] = "#{Dir.pwd}/spec/fixtures/home"
3 | ENV['AWS_REGION'] ||= "us-west-2"
4 | ENV['LONO_TEST'] = '1'
5 | ENV['LONO_ROOT'] = "#{Dir.pwd}/spec/fixtures/project"
6 |
7 | require "pp"
8 | require "byebug"
9 |
10 | root = File.expand_path("../", File.dirname(__FILE__))
11 | require "#{root}/lib/lono"
12 |
13 | Dir.glob("./spec/spec_helper/**/*.rb").each do |file|
14 | require file
15 | end
16 | module Helper
17 | include SpecHelper::Logging
18 | include SpecHelper::Execute
19 | end
20 |
21 | RSpec.configure do |c|
22 | c.include Helper
23 | c.before(:each) do
24 | override_logger
25 | end
26 | c.after(:each) do
27 | flush_overriden_logger
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/spec/spec_helper/execute.rb:
--------------------------------------------------------------------------------
1 | module SpecHelper
2 | module Execute
3 | def execute(cmd)
4 | show_command = ENV['LONO_DEBUG_SPECS'] || ENV['SHOW_COMMAND']
5 | puts "Running: #{cmd}" if show_command
6 | out = `#{cmd}`
7 | puts out if show_command
8 | out
9 | end
10 | alias_method :sh, :execute
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/spec/spec_helper/logging.rb:
--------------------------------------------------------------------------------
1 | module SpecHelper
2 | module Logging
3 | # Should be called by before(:each)
4 | def override_logger
5 | @logs ||= StringIO.new
6 | logger = Lono::Logger.new(@logs)
7 | Lono.logger = logger
8 | end
9 |
10 | # Should be called by after(:each)
11 | def flush_overriden_logger
12 | @logs = StringIO.new
13 | end
14 |
15 | # stored logs
16 | def logs
17 | @logs.string
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------