├── .ruby-version
├── lib
├── default
│ ├── Moonfile.rb
│ ├── moonshot
│ │ └── template.yml
│ ├── appspec.yml
│ └── bin
│ │ └── build.sh
├── moonshot
│ ├── config.rb
│ ├── task.rb
│ ├── ssh_config.rb
│ ├── stack_config.rb
│ ├── commands
│ │ ├── status.rb
│ │ ├── version.rb
│ │ ├── push.rb
│ │ ├── interactive_command.rb
│ │ ├── doctor.rb
│ │ ├── ssh.rb
│ │ ├── show_all_events_option.rb
│ │ ├── list.rb
│ │ ├── deploy.rb
│ │ ├── build.rb
│ │ ├── tag_arguments.rb
│ │ ├── delete.rb
│ │ ├── console.rb
│ │ ├── parent_stack_option.rb
│ │ ├── parameter_arguments.rb
│ │ ├── update.rb
│ │ ├── generate_template.rb
│ │ ├── create.rb
│ │ └── new.rb
│ ├── yaml_stack_template.rb
│ ├── resources.rb
│ ├── json_stack_template.rb
│ ├── account_context.rb
│ ├── tools
│ │ ├── asg_rollout
│ │ │ ├── asg_instance.rb
│ │ │ ├── instance_health.rb
│ │ │ └── hook_exec_environment.rb
│ │ └── asg_rollout_config.rb
│ ├── stack_output_printer.rb
│ ├── stack_list_printer.rb
│ ├── ssh_fork_executor.rb
│ ├── resources_helper.rb
│ ├── creds_helper.rb
│ ├── stack_template.rb
│ ├── stack_parameter_printer.rb
│ ├── always_use_default_source.rb
│ ├── ask_user_source.rb
│ ├── ssh_command_builder.rb
│ ├── interactive_logger_proxy.rb
│ ├── ssh_command.rb
│ ├── ssh_target_selector.rb
│ ├── doctor_helper.rb
│ ├── stack_lister.rb
│ ├── stack_parameter.rb
│ ├── parameter_collection.rb
│ ├── stack_events_poller.rb
│ ├── command.rb
│ ├── unicode_table.rb
│ ├── build_mechanism
│ │ ├── version_proxy.rb
│ │ └── script.rb
│ ├── parent_stack_parameter_loader.rb
│ ├── dynamic_template.rb
│ ├── controller_config.rb
│ ├── command_line_dispatcher.rb
│ ├── changelog_parser.rb
│ ├── shell.rb
│ ├── artifact_repository
│ │ └── s3_bucket.rb
│ └── change_set.rb
├── moonshot.rb
└── plugins
│ ├── encrypted_parameters
│ ├── parameter_encrypter.rb
│ └── kms_key.rb
│ ├── dynamic_template.rb
│ └── code_deploy_setup.rb
├── sample
├── build
│ └── .placeholder
├── docroot
│ └── index.php
├── bin
│ ├── start.sh
│ ├── stop.sh
│ ├── clean.sh
│ ├── aws-codedeploy-samples
│ │ ├── conf-mgmt
│ │ │ ├── puppet
│ │ │ │ ├── aws-codedeploy
│ │ │ │ │ ├── .rspec
│ │ │ │ │ ├── spec
│ │ │ │ │ │ ├── classes
│ │ │ │ │ │ │ ├── coverage_spec.rb
│ │ │ │ │ │ │ └── example_spec.rb
│ │ │ │ │ │ └── spec_helper.rb
│ │ │ │ │ ├── .gitignore
│ │ │ │ │ ├── Guardfile
│ │ │ │ │ ├── .fixtures.yml
│ │ │ │ │ ├── manifests
│ │ │ │ │ │ ├── install.pp
│ │ │ │ │ │ ├── service.pp
│ │ │ │ │ │ ├── params.pp
│ │ │ │ │ │ └── init.pp
│ │ │ │ │ ├── Modulefile
│ │ │ │ │ ├── metadata.json
│ │ │ │ │ ├── tests
│ │ │ │ │ │ └── init.pp
│ │ │ │ │ ├── Rakefile
│ │ │ │ │ └── README.md
│ │ │ │ ├── masterless
│ │ │ │ │ ├── deploy_hooks
│ │ │ │ │ │ ├── puppet-apply.sh
│ │ │ │ │ │ ├── verify_service.sh
│ │ │ │ │ │ └── install-puppet.sh
│ │ │ │ │ ├── application_vars.sh
│ │ │ │ │ ├── puppet
│ │ │ │ │ │ └── manifests
│ │ │ │ │ │ │ └── hello_world.pp
│ │ │ │ │ ├── appspec.yml
│ │ │ │ │ ├── src
│ │ │ │ │ │ └── main
│ │ │ │ │ │ │ ├── java
│ │ │ │ │ │ │ └── com
│ │ │ │ │ │ │ │ └── amazonaws
│ │ │ │ │ │ │ │ └── sample
│ │ │ │ │ │ │ │ └── HelloServlet.java
│ │ │ │ │ │ │ └── webapp
│ │ │ │ │ │ │ └── WEB-INF
│ │ │ │ │ │ │ └── web.xml
│ │ │ │ │ └── pom.xml
│ │ │ │ └── README.md
│ │ │ ├── salt
│ │ │ │ ├── local-only
│ │ │ │ │ ├── salt
│ │ │ │ │ │ ├── top.sls
│ │ │ │ │ │ └── webserver.sls
│ │ │ │ │ ├── TODO.txt
│ │ │ │ │ ├── index.html
│ │ │ │ │ ├── deploy_hooks
│ │ │ │ │ │ ├── before_install.sh
│ │ │ │ │ │ ├── application_start.sh
│ │ │ │ │ │ └── verify_service.sh
│ │ │ │ │ ├── application_vars.sh
│ │ │ │ │ └── appspec.yml
│ │ │ │ └── README.md
│ │ │ ├── chef
│ │ │ │ ├── aws-codedeploy-agent
│ │ │ │ │ ├── node.json
│ │ │ │ │ ├── solo.rb
│ │ │ │ │ ├── cookbooks
│ │ │ │ │ │ └── codedeploy-agent
│ │ │ │ │ │ │ ├── metadata.rb
│ │ │ │ │ │ │ └── recipes
│ │ │ │ │ │ │ └── default.rb
│ │ │ │ │ └── README.md
│ │ │ │ ├── solo
│ │ │ │ │ ├── chef
│ │ │ │ │ │ ├── node.json
│ │ │ │ │ │ ├── Cheffile
│ │ │ │ │ │ ├── site-cookbooks
│ │ │ │ │ │ │ └── homesite
│ │ │ │ │ │ │ │ └── recipes
│ │ │ │ │ │ │ │ └── default.rb
│ │ │ │ │ │ └── solo.rb
│ │ │ │ │ ├── deploy_hooks
│ │ │ │ │ │ ├── verify_service.sh
│ │ │ │ │ │ ├── chef-solo.sh
│ │ │ │ │ │ └── install-chef.sh
│ │ │ │ │ ├── appspec.yml
│ │ │ │ │ ├── src
│ │ │ │ │ │ └── main
│ │ │ │ │ │ │ ├── java
│ │ │ │ │ │ │ └── com
│ │ │ │ │ │ │ │ └── amazonaws
│ │ │ │ │ │ │ │ └── sample
│ │ │ │ │ │ │ │ └── HelloServlet.java
│ │ │ │ │ │ │ └── webapp
│ │ │ │ │ │ │ └── WEB-INF
│ │ │ │ │ │ │ └── web.xml
│ │ │ │ │ └── pom.xml
│ │ │ │ └── README.md
│ │ │ └── ansible
│ │ │ │ ├── local-only
│ │ │ │ ├── lamp_simple
│ │ │ │ │ ├── hosts
│ │ │ │ │ ├── roles
│ │ │ │ │ │ ├── web
│ │ │ │ │ │ │ ├── tasks
│ │ │ │ │ │ │ │ ├── main.yml
│ │ │ │ │ │ │ │ ├── copy_code.yml
│ │ │ │ │ │ │ │ └── install_httpd.yml
│ │ │ │ │ │ │ ├── handlers
│ │ │ │ │ │ │ │ └── main.yml
│ │ │ │ │ │ │ └── templates
│ │ │ │ │ │ │ │ └── index.php.j2
│ │ │ │ │ │ ├── db
│ │ │ │ │ │ │ ├── handlers
│ │ │ │ │ │ │ │ └── main.yml
│ │ │ │ │ │ │ ├── templates
│ │ │ │ │ │ │ │ └── my.cnf.j2
│ │ │ │ │ │ │ └── tasks
│ │ │ │ │ │ │ │ └── main.yml
│ │ │ │ │ │ └── common
│ │ │ │ │ │ │ ├── templates
│ │ │ │ │ │ │ ├── ntp.conf.j2
│ │ │ │ │ │ │ └── iptables.j2
│ │ │ │ │ │ │ ├── handlers
│ │ │ │ │ │ │ └── main.yml
│ │ │ │ │ │ │ └── tasks
│ │ │ │ │ │ │ └── main.yml
│ │ │ │ │ ├── group_vars
│ │ │ │ │ │ ├── all
│ │ │ │ │ │ └── dbservers
│ │ │ │ │ ├── LICENSE.md
│ │ │ │ │ ├── site.yml
│ │ │ │ │ └── README.md
│ │ │ │ ├── verify_service.sh
│ │ │ │ ├── before_install.sh
│ │ │ │ ├── application_start.sh
│ │ │ │ ├── common_variables.sh
│ │ │ │ └── appspec.yml
│ │ │ │ ├── module
│ │ │ │ └── site.yml
│ │ │ │ └── README.md
│ │ ├── applications
│ │ │ ├── SampleApp_Linux
│ │ │ │ ├── scripts
│ │ │ │ │ ├── start_server
│ │ │ │ │ ├── install_dependencies
│ │ │ │ │ └── stop_server
│ │ │ │ ├── appspec.yml
│ │ │ │ └── index.html
│ │ │ └── SampleApp_Windows
│ │ │ │ ├── appspec.yml
│ │ │ │ ├── before-install.bat
│ │ │ │ └── index.html
│ │ ├── load-balancing
│ │ │ └── elb
│ │ │ │ ├── stop_httpd.sh
│ │ │ │ ├── start_httpd.sh
│ │ │ │ ├── appspec.yml
│ │ │ │ ├── README.md
│ │ │ │ ├── register_with_elb.sh
│ │ │ │ └── deregister_from_elb.sh
│ │ ├── README.md
│ │ └── version-control
│ │ │ └── git
│ │ │ ├── README.md
│ │ │ └── pre-push
│ └── build.sh
├── .bundle
│ └── config
├── Gemfile
├── cloud_formation
│ └── parameters
│ │ └── moonshot-sample-app.yml
├── Moonfile
├── appspec.yml
└── Gemfile.lock
├── spec
├── .rspec
├── fixtures
│ ├── empty1.yml
│ ├── answer1.yml
│ ├── answer2.yml
│ ├── update1.yml
│ ├── create1.yml
│ └── update2.yml
├── fs_fixtures
│ ├── moonshot
│ │ ├── template.yml
│ │ ├── custom.json
│ │ └── template.json
│ └── cloud_formation
│ │ ├── rspec-app.yml
│ │ └── rspec-app.json
├── moonshot
│ ├── command_spec.rb
│ ├── commands
│ │ ├── console_spec.rb
│ │ ├── build_spec.rb
│ │ ├── update_spec.rb
│ │ └── create_spec.rb
│ ├── command_optional_argument_spec.rb
│ ├── interactive_logger_proxy_spec.rb
│ ├── plugins_spec.rb
│ ├── ssh_spec.rb
│ ├── stub_rollout_asg.rb
│ ├── plugins
│ │ └── code_deploy_setup_spec.rb
│ ├── shell_spec.rb
│ ├── artifact_repository
│ │ └── s3_bucket_via_github_releases_spec.rb
│ └── command_restrictions.rb
└── spec_helper.rb
├── .gitignore
├── .rspec
├── docs
├── logo.png
├── moonshot.png
├── requirements.txt
├── about
│ ├── license.md
│ └── contribute.md
├── mechanisms
│ ├── artifact-repository.md
│ ├── build.md
│ └── deployment.md
├── user-guide
│ ├── parent_stacks.md
│ ├── custom-commands.md
│ └── include_in_your_project.md
├── plugins
│ ├── dynamic_template.md
│ ├── code_deploy_setup.md
│ └── backup.md
├── index.md
└── plugins.md
├── Gemfile
├── bin
└── moonshot
├── Rakefile
├── mkdocs.yml
├── .rubocop.yml
├── moonshot.gemspec
└── README.md
/.ruby-version:
--------------------------------------------------------------------------------
1 | 3.3.4
--------------------------------------------------------------------------------
/lib/default/Moonfile.rb:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/moonshot/config.rb:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/sample/build/.placeholder:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/.rspec:
--------------------------------------------------------------------------------
1 | --require spec_helper.rb --color
--------------------------------------------------------------------------------
/sample/docroot/index.php:
--------------------------------------------------------------------------------
1 | 0.7.0'
6 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/salt/local-only/salt/webserver.sls:
--------------------------------------------------------------------------------
1 | httpd:
2 | pkg:
3 | - installed
4 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/applications/SampleApp_Linux/scripts/start_server:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | service httpd start
3 |
4 |
--------------------------------------------------------------------------------
/lib/default/moonshot/template.yml:
--------------------------------------------------------------------------------
1 | # Place your CloudFormation template here!
2 | ---
3 | Resources:
4 | Parameters:
5 | Outputs:
6 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/applications/SampleApp_Linux/scripts/install_dependencies:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | yum install -y httpd
3 |
4 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/chef/aws-codedeploy-agent/node.json:
--------------------------------------------------------------------------------
1 | {
2 | "run_list": [ "recipe[codedeploy-agent]" ]
3 | }
4 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/chef/solo/chef/node.json:
--------------------------------------------------------------------------------
1 | {
2 | "run_list": [ "recipe[homesite]", "recipe[tomcat]" ]
3 | }
4 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/puppet/aws-codedeploy/spec/classes/coverage_spec.rb:
--------------------------------------------------------------------------------
1 | at_exit { RSpec::Puppet::Coverage.report! }
2 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/puppet/aws-codedeploy/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require 'puppetlabs_spec_helper/module_spec_helper'
2 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/ansible/local-only/lamp_simple/hosts:
--------------------------------------------------------------------------------
1 | [webservers]
2 | localhost
3 |
4 | [dbservers]
5 | localhost
6 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/chef/solo/chef/Cheffile:
--------------------------------------------------------------------------------
1 | site "https://supermarket.getchef.com/api/v1"
2 |
3 | cookbook "tomcat"
4 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/salt/local-only/TODO.txt:
--------------------------------------------------------------------------------
1 | * write scripts for running salt modules in masterless mode via CodeDeploy
2 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/puppet/aws-codedeploy/.gitignore:
--------------------------------------------------------------------------------
1 | .*.sw?
2 | pkg
3 | spec/fixtures
4 | .rspec_system
5 | .vagrant
6 | .bundle
7 | vendor
8 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/ansible/local-only/lamp_simple/roles/web/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - include: install_httpd.yml
3 | - include: copy_code.yml
4 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/chef/solo/chef/site-cookbooks/homesite/recipes/default.rb:
--------------------------------------------------------------------------------
1 | node.default["tomcat"]["user"] = "root"
2 | node.default["tomcat"]["port"] = 8888
3 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/puppet/aws-codedeploy/Guardfile:
--------------------------------------------------------------------------------
1 | notification :off
2 |
3 | guard 'rake', :task => 'test' do
4 | watch(%r{^manifests\/(.+)\.pp$})
5 | end
6 |
--------------------------------------------------------------------------------
/spec/fs_fixtures/moonshot/template.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - Resources:
3 | - Parameters:
4 | - Parent1:
5 | - Type: String
6 | - Description: This is imported from the parent stack on create.
--------------------------------------------------------------------------------
/spec/fs_fixtures/cloud_formation/rspec-app.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - Resources:
3 | - Parameters:
4 | - Parent1:
5 | - Type: String
6 | - Description: This is imported from the parent stack on create.
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/ansible/module/site.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - hosts: all
3 | tasks:
4 | - name: install codedeploy agent
5 | sudo: yes
6 | action: aws_codedeploy enabled=true
7 |
--------------------------------------------------------------------------------
/sample/cloud_formation/parameters/moonshot-sample-app.yml:
--------------------------------------------------------------------------------
1 | ---
2 | ArtifactBucket: "moonshot-sample-bucket2"
3 | AvailabilityZone1: "us-east-1a"
4 | AvailabilityZone2: "us-east-1b"
5 | DesiredCapacity: "2"
6 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/applications/SampleApp_Linux/scripts/stop_server:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | isExistApp=`pgrep httpd`
3 | if [[ -n $isExistApp ]]; then
4 | service httpd stop
5 | fi
6 |
7 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/chef/aws-codedeploy-agent/solo.rb:
--------------------------------------------------------------------------------
1 | # These are here just for testing only.
2 | file_cache_path "/etc/chef"
3 | cookbook_path "/etc/chef/cookbooks"
4 | json_attribs "/etc/chef/node.json"
5 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/puppet/aws-codedeploy/.fixtures.yml:
--------------------------------------------------------------------------------
1 | fixtures:
2 | repositories:
3 | stdlib: "git://github.com/puppetlabs/puppetlabs-stdlib.git"
4 | symlinks:
5 | codedeploy: "#{source_dir}"
6 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/chef/aws-codedeploy-agent/cookbooks/codedeploy-agent/metadata.rb:
--------------------------------------------------------------------------------
1 | name 'codedeploy-agent'
2 | recipe 'codedeploy-agent::default', 'Fetches, installs, and starts the AWS CodeDeploy host agent'
3 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/salt/local-only/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | AWS CodeDeploy with Salt Masterless
4 |
5 |
6 | Hello World
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source 'https://rubygems.org'
4 |
5 | gemspec
6 |
7 | gem 'rake', require: false
8 |
9 | group :test do
10 | gem 'codeclimate-test-reporter'
11 | gem 'pry'
12 | gem 'rubocop'
13 | end
14 |
--------------------------------------------------------------------------------
/lib/default/appspec.yml:
--------------------------------------------------------------------------------
1 | version:
2 | os:
3 | files:
4 | - source:
5 | destination:
6 | hooks:
7 | ApplicationStop:
8 | - location:
9 | BeforeInstall:
10 | - location:
11 | ApplicationStart:
12 | - location:
13 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/puppet/masterless/deploy_hooks/puppet-apply.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | BASE_DIR="/etc/puppet/"
4 |
5 | /usr/bin/puppet apply --modulepath=${BASE_DIR}/modules ${BASE_DIR}/codedeploy/manifests/hello_world.pp
6 |
--------------------------------------------------------------------------------
/docs/about/license.md:
--------------------------------------------------------------------------------
1 | # License
2 |
3 | The legal stuff.
4 |
5 | ---
6 |
7 | ## Moonshot License (Apache License)
8 |
9 | Please [read the full license](https://github.com/acquia/moonshot/blob/master/LICENSE.txt) and make sure you adhere.
10 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/chef/solo/deploy_hooks/verify_service.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | result=$(curl -s http://localhost:8888/hello/)
4 |
5 | if [[ "$result" =~ "Hello World" ]]; then
6 | exit 0
7 | else
8 | exit 1
9 | fi
10 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/ansible/local-only/lamp_simple/group_vars/all:
--------------------------------------------------------------------------------
1 | ---
2 | # Variables listed here are applicable to all host groups
3 |
4 | httpd_port: 80
5 | ntpserver: localhost
6 | repository: https://github.com/bennojoy/mywebapp.git
7 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/puppet/masterless/deploy_hooks/verify_service.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | result=$(curl -s http://localhost:8080/hello/)
4 |
5 | if [[ "$result" =~ "Hello World" ]]; then
6 | exit 0
7 | else
8 | exit 1
9 | fi
10 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/chef/solo/chef/solo.rb:
--------------------------------------------------------------------------------
1 | file_cache_path "/etc/chef/codedeploy/"
2 | cookbook_path [
3 | "/etc/chef/codedeploy/cookbooks",
4 | "/etc/chef/codedeploy/site-cookbooks"
5 | ]
6 | json_attribs "/etc/chef/codedeploy/node.json"
7 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/puppet/masterless/application_vars.sh:
--------------------------------------------------------------------------------
1 | APPLICATION_NAME="PuppetMasterless"
2 | DEPLOY_GROUP="PuppetMasterless"
3 | BUCKET=""
4 | BUNDLE_PATH="puppet-masterless.tar"
5 | EC2_TAG_KEY='Puppet'
6 | EC2_TAG_VALUE='Masterless'
7 |
--------------------------------------------------------------------------------
/sample/Moonfile:
--------------------------------------------------------------------------------
1 | Moonshot.config do |m|
2 | m.app_name = 'moonshot-sample-app'
3 | m.artifact_repository = S3Bucket.new('moonshot-sample-bucket2')
4 | m.build_mechanism = Script.new('bin/build.sh')
5 | m.deployment_mechanism = CodeDeploy.new(asg: 'AutoScalingGroup')
6 | end
7 |
8 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/chef/solo/deploy_hooks/chef-solo.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # First, make sure the tomcat cookbook is installed
4 | cd /etc/chef/codedeploy/
5 | /usr/local/bin/librarian-chef install
6 |
7 | /usr/local/bin/chef-solo -c /etc/chef/codedeploy/solo.rb
8 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/load-balancing/elb/stop_httpd.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Here is where you'd want to stop your http daemon. For example:
4 | #service httpd stop
5 | #exit $?
6 |
7 | # In this case, since it's just a placeholder, we don't need to do anything.
8 | exit 0
9 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/load-balancing/elb/start_httpd.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Here is where you'd want to start your http daemon. For example:
4 | #service httpd start
5 | #exit $?
6 |
7 | # In this case, since it's just a placeholder, we don't need to do anything.
8 | exit 0
9 |
--------------------------------------------------------------------------------
/spec/fs_fixtures/moonshot/custom.json:
--------------------------------------------------------------------------------
1 | {
2 | "Resources": {
3 |
4 | },
5 |
6 | "Parameters": {
7 | "Parent1": {
8 | "Type": "String",
9 | "Description": "This is imported from the parent stack on create."
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/spec/fs_fixtures/moonshot/template.json:
--------------------------------------------------------------------------------
1 | {
2 | "Resources": {
3 |
4 | },
5 |
6 | "Parameters": {
7 | "Parent1": {
8 | "Type": "String",
9 | "Description": "This is imported from the parent stack on create."
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/applications/SampleApp_Windows/appspec.yml:
--------------------------------------------------------------------------------
1 | version: 0.0
2 | os: windows
3 | files:
4 | - source: \index.html
5 | destination: C:\inetpub\wwwroot
6 | hooks:
7 | BeforeInstall:
8 | - location: \before-install.bat
9 | timeout: 900
10 |
--------------------------------------------------------------------------------
/lib/moonshot/task.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | class Task
5 | attr_reader :name, :desc, :block
6 |
7 | def initialize(name, desc, &block)
8 | @name = name.to_sym
9 | @desc = desc
10 | @block = block
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/ansible/local-only/lamp_simple/roles/db/handlers/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # Handler to handle DB tier notifications
3 |
4 | - name: restart mysql
5 | service: name=mysqld state=restarted
6 |
7 | - name: restart iptables
8 | service: name=iptables state=restarted
9 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/salt/local-only/deploy_hooks/before_install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Check for salt, and attempt to install if not
4 |
5 | yum list installed | grep salt-minion &> /dev/null
6 | if [ $? != 0 ]; then
7 | yum install -y --enablerepo=epel salt-minion
8 | fi
9 |
--------------------------------------------------------------------------------
/spec/fs_fixtures/cloud_formation/rspec-app.json:
--------------------------------------------------------------------------------
1 | {
2 | "Resources": {
3 |
4 | },
5 |
6 | "Parameters": {
7 | "Parent1": {
8 | "Type": "String",
9 | "Description": "This is imported from the parent stack on create."
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/salt/local-only/application_vars.sh:
--------------------------------------------------------------------------------
1 | APPLICATION_NAME="SaltMasterless"
2 | DEPLOY_GROUP="Salt"
3 | BUCKET=""
4 | BUNDLE_PATH="salt-masterless.tar"
5 | EC2_TAG_KEY='Salt'
6 | EC2_TAG_VALUE='Masterless'
7 |
8 | DESTINATION_PATH="/etc/salt/codedeploy"
9 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/ansible/local-only/lamp_simple/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (C) 2013 AnsibleWorks, Inc.
2 |
3 | This work is licensed under the Creative Commons Attribution 3.0 Unported License.
4 | To view a copy of this license, visit http://creativecommons.org/licenses/by/3.0/deed.en_US.
5 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/ansible/local-only/lamp_simple/roles/common/templates/ntp.conf.j2:
--------------------------------------------------------------------------------
1 |
2 | driftfile /var/lib/ntp/drift
3 |
4 | restrict 127.0.0.1
5 | restrict -6 ::1
6 |
7 | server {{ ntpserver }}
8 |
9 | includefile /etc/ntp/crypto/pw
10 |
11 | keys /etc/ntp/keys
12 |
13 |
--------------------------------------------------------------------------------
/spec/fixtures/update1.yml:
--------------------------------------------------------------------------------
1 | ---
2 | Resources:
3 | Parameters:
4 | InputParameter1:
5 | Type: String
6 | Default: 'Default1'
7 | InputParameter2:
8 | Type: String
9 | Default: 'Default2'
10 | InputParameter3:
11 | Type: String
12 | Outputs:
13 | Output1: !Ref "ResourceName"
14 |
--------------------------------------------------------------------------------
/spec/moonshot/command_spec.rb:
--------------------------------------------------------------------------------
1 | describe Moonshot::Command do
2 | describe '#parser' do
3 | subject { described_class.new.parser }
4 |
5 | it 'should parse the verbose option' do
6 | allow(described_class).to receive(:usage)
7 | subject.parse('-v')
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/puppet/aws-codedeploy/manifests/install.pp:
--------------------------------------------------------------------------------
1 | # == Class codedeploy::install
2 | #
3 | class codedeploy::install {
4 |
5 | package { 'codedeploy-agent':
6 | ensure => present,
7 | source => $codedeploy::package_source,
8 | provider => rpm,
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/lib/moonshot/ssh_config.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | class SSHConfig
5 | attr_accessor :ssh_identity_file, :ssh_user
6 |
7 | def initialize
8 | @ssh_identity_file = ENV['MOONSHOT_SSH_KEY_FILE']
9 | @ssh_user = ENV['MOONSHOT_SSH_USER']
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/ansible/local-only/verify_service.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Deploy hooks are run via absolute path, so taking dirname of this script will give us the path to
4 | # our deploy_hooks directory.
5 | . $(dirname $0)/common_variables.sh
6 |
7 | curl -s http://localhost:80/ansible/index.php
8 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/ansible/local-only/lamp_simple/roles/web/handlers/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # Handler for the webtier: handlers are called by other plays.
3 | # See http://ansible.cc/docs/playbooks.html for more information about handlers.
4 |
5 | - name: restart iptables
6 | service: name=iptables state=restarted
7 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/puppet/aws-codedeploy/Modulefile:
--------------------------------------------------------------------------------
1 | name 'aws-codedeploy'
2 | version '0.1.0'
3 | source ''
4 | author 'Amazon Web Services'
5 | license 'Apache 2.0'
6 | summary ''
7 | description ''
8 | project_page ''
9 |
10 | dependency 'puppetlabs/stdlib'
11 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/applications/SampleApp_Windows/before-install.bat:
--------------------------------------------------------------------------------
1 | REM Install Internet Information Server (IIS).
2 | c:\Windows\Sysnative\WindowsPowerShell\v1.0\powershell.exe -Command Import-Module -Name ServerManager
3 | c:\Windows\Sysnative\WindowsPowerShell\v1.0\powershell.exe -Command Install-WindowsFeature Web-Server
--------------------------------------------------------------------------------
/lib/moonshot/stack_config.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | # Configuration for the Moonshot::Stack class.
5 | class StackConfig
6 | attr_accessor :parent_stacks, :show_all_events
7 |
8 | def initialize
9 | @parent_stacks = []
10 | @show_all_events = false
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/spec/fixtures/create1.yml:
--------------------------------------------------------------------------------
1 | ---
2 | Resources:
3 | Parameters:
4 | InputParameter1:
5 | Type: String
6 | Default: 'Default1'
7 | InputParameter2:
8 | Type: String
9 | Default: 'Default2'
10 | InputParameter3:
11 | Type: String
12 | InputParameter4:
13 | Type: String
14 | Outputs:
15 | Output1: !Ref "ResourceName"
16 |
--------------------------------------------------------------------------------
/spec/fixtures/update2.yml:
--------------------------------------------------------------------------------
1 | ---
2 | Resources:
3 | Parameters:
4 | InputParameter1:
5 | Type: String
6 | Default: 'Default1'
7 | InputParameter2:
8 | Type: String
9 | Default: 'Default2'
10 | InputParameter3:
11 | Type: String
12 | InputParameter4:
13 | Type: String
14 | Outputs:
15 | Output1: !Ref "ResourceName"
16 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/salt/local-only/deploy_hooks/application_start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Deploy hooks are run via absolute path, so taking dirname of this script will give us the path to
4 | # our deploy_hooks directory.
5 | . $(dirname $0)/../application_vars.sh
6 |
7 | salt-call --local --file-root=$DESTINATION_PATH state.highstate
8 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/ansible/local-only/lamp_simple/group_vars/dbservers:
--------------------------------------------------------------------------------
1 | ---
2 | # The variables file used by the playbooks in the dbservers group.
3 | # These don't have to be explicitly imported by vars_files: they are autopopulated.
4 |
5 | mysqlservice: mysqld
6 | mysql_port: 3306
7 | dbuser: root
8 | dbname: foodb
9 | upassword: abc
10 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/puppet/aws-codedeploy/manifests/service.pp:
--------------------------------------------------------------------------------
1 | # == Class codedeploy::service
2 | #
3 | # This class is meant to be called from codedeploy
4 | # It ensure the service is running
5 | #
6 | class codedeploy::service {
7 |
8 | service { 'codedeploy-agent':
9 | ensure => running,
10 | enable => true,
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/bin/moonshot:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require 'moonshot'
5 |
6 | # This is the main entry point for the `moonshot` command-line tool.
7 | begin
8 | Moonshot::CommandLine.new.run!
9 | rescue StandardError => e
10 | warn "#{e} (at #{e.backtrace.first})"
11 | raise e if ENV['MOONSHOT_BACKTRACE']
12 |
13 | exit 1
14 | end
15 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/load-balancing/elb/appspec.yml:
--------------------------------------------------------------------------------
1 | version: 0.0
2 | os: linux
3 | files:
4 | - source: /
5 | destination: /tmp/elb-test
6 | hooks:
7 | ApplicationStop:
8 | - location: deregister_from_elb.sh
9 | - location: stop_httpd.sh
10 | ApplicationStart:
11 | - location: start_httpd.sh
12 | - location: register_with_elb.sh
13 |
--------------------------------------------------------------------------------
/lib/moonshot/commands/status.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | module Commands
5 | class Status < Moonshot::Command
6 | self.usage = 'status [options]'
7 | self.description = 'Show the status of an existing environment'
8 |
9 | def execute
10 | controller.status
11 | end
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/lib/moonshot/commands/version.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | module Commands
5 | class Version < Moonshot::Command
6 | self.usage = 'version'
7 | self.description = 'Display the version of Moonshot'
8 |
9 | def execute
10 | puts Gem.loaded_specs['moonshot'].version
11 | end
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/ansible/local-only/before_install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Check for ansible tools, and attempt to install if not
4 |
5 | yum list installed python-pip &> /dev/null
6 | if [ $? != 0 ]; then
7 | yum install -y python-pip
8 | fi
9 |
10 | pip list | grep -q ansible
11 | if [ $? != 0 ]; then
12 | pip install ansible
13 | fi
14 |
--------------------------------------------------------------------------------
/lib/moonshot/commands/push.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | module Commands
5 | class Push < Moonshot::Command
6 | self.usage = 'push [options]'
7 | self.description = 'Build and deploy a development artifact from the working directory'
8 |
9 | def execute
10 | controller.push
11 | end
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/ansible/local-only/application_start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Deploy hooks are run via absolute path, so taking dirname of this script will give us the path to
4 | # our deploy_hooks directory.
5 | . $(dirname $0)/common_variables.sh
6 |
7 | ansible-playbook $DESTINATION_PATH/lamp_simple/site.yml -i $DESTINATION_PATH/lamp_simple/hosts --connection=local
8 |
--------------------------------------------------------------------------------
/lib/moonshot/commands/interactive_command.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | module Commands
5 | module InteractiveCommand
6 | def parser
7 | parser = super
8 |
9 | parser.on('--[no-]interactive', TrueClass, 'Use interactive prompts.') do |v|
10 | Moonshot.config.interactive = v
11 | end
12 | end
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/moonshot/yaml_stack_template.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'yaml'
4 |
5 | module Moonshot
6 | # Handles YAML formatted AWS template files.
7 | class YamlStackTemplate < StackTemplate
8 | def body
9 | template_body.to_yaml
10 | end
11 |
12 | private
13 |
14 | def template_body
15 | @template_body ||= YAML.load_file(@filename)
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/ansible/local-only/lamp_simple/roles/db/templates/my.cnf.j2:
--------------------------------------------------------------------------------
1 | [mysqld]
2 | datadir=/var/lib/mysql
3 | socket=/var/lib/mysql/mysql.sock
4 | user=mysql
5 | # Disabling symbolic-links is recommended to prevent assorted security risks
6 | symbolic-links=0
7 | port={{ mysql_port }}
8 |
9 | [mysqld_safe]
10 | log-error=/var/log/mysqld.log
11 | pid-file=/var/run/mysqld/mysqld.pid
12 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'bundler/setup'
4 | require 'rspec/core/rake_task'
5 | require 'rubocop/rake_task'
6 |
7 | desc 'Run RuboCop against the source code.'
8 | RuboCop::RakeTask.new(:rubocop) do |task|
9 | task.options << '--display-cop-names'
10 | task.options << '--display-style-guide'
11 | end
12 |
13 | RSpec::Core::RakeTask.new(:spec)
14 |
15 | task default: %i[spec rubocop]
16 |
--------------------------------------------------------------------------------
/lib/moonshot/commands/doctor.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | module Commands
5 | class Doctor < Moonshot::Command
6 | self.usage = 'doctor [options]'
7 | self.description = 'Run configuration checks against the local environment'
8 |
9 | def execute
10 | controller.doctor || raise('One or more checks failed.')
11 | end
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/lib/moonshot/commands/ssh.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative '../ssh_command'
4 |
5 | module Moonshot
6 | module Commands
7 | class Ssh < Moonshot::SSHCommand
8 | self.usage = 'ssh [options]'
9 | self.description = 'SSH into the first (or specified) instance on the stack'
10 |
11 | def execute
12 | controller.ssh
13 | end
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/ansible/local-only/lamp_simple/roles/common/handlers/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # Handler to handle common notifications. Handlers are called by other plays.
3 | # See http://ansible.cc/docs/playbooks.html for more information about handlers.
4 |
5 | - name: restart ntp
6 | service: name=ntpd state=restarted
7 |
8 | - name: restart iptables
9 | service: name=iptables state=restarted
10 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/puppet/masterless/puppet/manifests/hello_world.pp:
--------------------------------------------------------------------------------
1 | class { 'tomcat': }
2 |
3 | class { 'epel': }->
4 | tomcat::instance{ 'default':
5 | install_from_source => false,
6 | package_name => 'tomcat6',
7 | package_ensure => 'present',
8 | }->
9 | tomcat::service { 'default':
10 | use_jsvc => false,
11 | use_init => true,
12 | service_name => 'tomcat6',
13 | }
14 |
--------------------------------------------------------------------------------
/lib/moonshot/commands/show_all_events_option.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | module Commands
5 | module ShowAllEventsOption
6 | def parser
7 | parser = super
8 |
9 | parser.on('--[no-]show-all-events', TrueClass, 'Show all stack events during update') do |v|
10 | Moonshot.config.show_all_stack_events = v
11 | end
12 | end
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/moonshot/commands/list.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | module Commands
5 | class List < Moonshot::Command
6 | self.usage = 'list [options]'
7 | self.description = 'List stacks for this application'
8 |
9 | def execute
10 | stacks = StackLister.new(controller.config.app_name).list
11 | StackListPrinter.new(stacks).print
12 | end
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/ansible/local-only/lamp_simple/roles/web/tasks/copy_code.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # These tasks are responsible for copying the latest dev/production code from
3 | # the version control system.
4 |
5 | - name: Copy the code from repository
6 | git: repo={{ repository }} dest=/var/www/html/ansible
7 |
8 | - name: Creates the index.php file
9 | template: src=index.php.j2 dest=/var/www/html/ansible/index.php
10 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/puppet/aws-codedeploy/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aws-codedeploy",
3 | "version": "0.1.0",
4 | "author": "Amazon Web Services",
5 | "summary": "",
6 | "license": "Apache 2.0",
7 | "source": "",
8 | "project_page": "",
9 | "issues_url": "",
10 | "dependencies": [
11 | {
12 | "name": "puppetlabs-stdlib",
13 | "version_requirement": ">= 1.0.0"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/salt/local-only/deploy_hooks/verify_service.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Deploy hooks are run via absolute path, so taking dirname of this script will give us the path to
4 | # our deploy_hooks directory.
5 | . $(dirname $0)/../application_vars.sh
6 |
7 | result=$(curl -s http://localhost/salt/index.html)
8 |
9 | if [[ "$result" =~ "Hello World" ]]; then
10 | exit 0
11 | else
12 | exit 1
13 | fi
14 |
--------------------------------------------------------------------------------
/lib/moonshot/commands/deploy.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | module Commands
5 | class Deploy < Moonshot::Command
6 | include InteractiveCommand
7 |
8 | self.usage = 'deploy VERSION'
9 | self.description = 'Deploy a versioned release to the environment'
10 |
11 | def execute(version_name)
12 | controller.deploy_version(version_name)
13 | end
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/moonshot/resources.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | # Resources is a dependency container that holds references to instances
5 | # provided to a Mechanism (build, deploy, etc.).
6 | class Resources
7 | attr_reader :stack, :ilog, :controller
8 |
9 | def initialize(stack:, ilog:, controller:)
10 | @stack = stack
11 | @ilog = ilog
12 | @controller = controller
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/moonshot/json_stack_template.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'json'
4 | require_relative 'stack_template'
5 |
6 | module Moonshot
7 | # Handles JSON formatted AWS template files.
8 | class JsonStackTemplate < StackTemplate
9 | def body
10 | template_body.to_json
11 | end
12 |
13 | private
14 |
15 | def template_body
16 | @template_body ||= JSON.parse(File.read(@filename))
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/sample/appspec.yml:
--------------------------------------------------------------------------------
1 | version: 0.0
2 | os: linux
3 | files:
4 | - source: docroot/
5 | destination: /var/www/html
6 | hooks:
7 | ApplicationStop:
8 | - location: bin/aws-codedeploy-samples/load-balancing/elb/deregister_from_elb.sh
9 | - location: bin/stop.sh
10 | BeforeInstall:
11 | - location: bin/clean.sh
12 | ApplicationStart:
13 | - location: bin/start.sh
14 | - location: bin/aws-codedeploy-samples/load-balancing/elb/register_with_elb.sh
15 |
--------------------------------------------------------------------------------
/sample/bin/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | CWD=$(pwd)
4 | PROJECT_DIR=$(dirname $0)/..
5 | BUILD_DIR=$PROJECT_DIR/build/$(date +"%s")
6 |
7 | mkdir $BUILD_DIR
8 | cp $PROJECT_DIR/appspec.yml $BUILD_DIR
9 | cp -r $PROJECT_DIR/bin $BUILD_DIR
10 | cp -r $PROJECT_DIR/docroot $BUILD_DIR
11 | rm -f $BUILD_DIR/docroot/index.php.dist
12 |
13 | cd $BUILD_DIR
14 | tar -zcvf output.tar.gz ./
15 | cd $CWD
16 |
17 | mv $BUILD_DIR/output.tar.gz $PROJECT_DIR
18 | rm -rf $BUILD_DIR
19 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/ansible/local-only/common_variables.sh:
--------------------------------------------------------------------------------
1 | #!/bin/true
2 | #
3 | # This is not an executable script, just a set of names and variable declarations.
4 | #
5 | # Use it with:
6 | # source common_variables.sh
7 | # Or:
8 | # . common_variables.sh
9 |
10 | APPLICATION_NAME="AnsibleApp"
11 | DEPLOY_GROUP="ansible"
12 | BUCKET=""
13 | BUNDLE_PATH="ansible-playbooks.zip"
14 |
15 | DESTINATION_PATH="/etc/ansible/codedeploy"
16 |
--------------------------------------------------------------------------------
/lib/moonshot/commands/build.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative 'interactive_command'
4 |
5 | module Moonshot
6 | module Commands
7 | class Build < Moonshot::Command
8 | include InteractiveCommand
9 |
10 | self.usage = 'build VERSION'
11 | self.description = 'Build a release artifact, ready for deployment'
12 |
13 | def execute(version_name)
14 | controller.build_version(version_name)
15 | end
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/moonshot/account_context.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | module AccountContext
5 | def self.get
6 | @account ||= determine_account_name
7 | end
8 |
9 | def self.set(account_name)
10 | @account = account_name
11 | end
12 |
13 | def self.reset
14 | @account = nil
15 | end
16 |
17 | def self.determine_account_name
18 | Aws::IAM::Client.new.list_account_aliases.account_aliases.first
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/lib/default/bin/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo "Building version '$VERSION_NAME', using target file '$OUTPUT_FILE'."
4 |
5 | # Add code here to assemble your project into a compressed tarball (.tar.gz)
6 | # located at $OUTPUT_FILE. Be sure to include your appspec.yml in the tarball's
7 | # root!
8 | #
9 | # For example:
10 | #
11 | # mkdir build_dir
12 | # rsync -avP path-to-source build_dir/src
13 | # cp moonshot/appspec.yml build_dir/
14 | # tar czf $OUTPUT_FILE -C build_dir .
15 | # rm -rf build_dir
16 |
--------------------------------------------------------------------------------
/lib/moonshot/tools/asg_rollout/asg_instance.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | module Tools
5 | class ASGRollout
6 | # Provides an abstraction around an Auto Scaling Group's
7 | # relationship with an instance.
8 | class ASGInstance
9 | attr_reader :asg_name, :id
10 |
11 | def initialize(asg_name, id, _config)
12 | @asg_name = asg_name
13 | @instance_id = id
14 | end
15 | end
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/applications/SampleApp_Linux/appspec.yml:
--------------------------------------------------------------------------------
1 | version: 0.0
2 | os: linux
3 | files:
4 | - source: /index.html
5 | destination: /var/www/html/
6 | hooks:
7 | BeforeInstall:
8 | - location: scripts/install_dependencies
9 | timeout: 300
10 | runas: root
11 | - location: scripts/start_server
12 | timeout: 300
13 | runas: root
14 | ApplicationStop:
15 | - location: scripts/stop_server
16 | timeout: 300
17 | runas: root
18 |
19 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/ansible/local-only/appspec.yml:
--------------------------------------------------------------------------------
1 | version: 0.0
2 | os: linux
3 | files:
4 | - source: /
5 | destination: /etc/ansible/codedeploy
6 | hooks:
7 | BeforeInstall:
8 | - location: before_install.sh
9 | timeout: 300
10 | runas: root
11 | ApplicationStart:
12 | - location: application_start.sh
13 | timeout: 300
14 | runas: root
15 | ValidateService:
16 | - location: verify_service.sh
17 | timeout: 300
18 | runas: root
19 |
--------------------------------------------------------------------------------
/lib/moonshot/stack_output_printer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | # Display the stack outputs to the user.
5 | class StackOutputPrinter
6 | def initialize(stack, table)
7 | @stack = stack
8 | @table = table
9 | end
10 |
11 | def print
12 | o_table = @table.add_leaf('Stack Outputs')
13 | rows = @stack.outputs.sort.map do |key, value|
14 | ["#{key}:", value]
15 | end
16 | o_table.add_table(rows)
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/chef/aws-codedeploy-agent/cookbooks/codedeploy-agent/recipes/default.rb:
--------------------------------------------------------------------------------
1 | remote_file "#{Chef::Config[:file_cache_path]}/codedeploy-agent-install" do
2 | source "https://s3.amazonaws.com/aws-codedeploy-us-east-1/latest/install"
3 | mode 0755
4 | end
5 |
6 | bash "install-codedeploy-agent" do
7 | code <<-EOH
8 | #{Chef::Config[:file_cache_path]}/codedeploy-agent-install auto
9 | EOH
10 | end
11 |
12 | service "codedeploy-agent" do
13 | action [:enable, :start]
14 | end
15 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/puppet/aws-codedeploy/manifests/params.pp:
--------------------------------------------------------------------------------
1 | # == Class codedeploy::params
2 | #
3 | # This class is meant to be called from codedeploy
4 | # It sets variables according to platform
5 | #
6 | class codedeploy::params {
7 | case $::osfamily {
8 | 'RedHat', 'Linux': {
9 | $package_source = 'https://s3.amazonaws.com/aws-codedeploy-us-east-1/latest/codedeploy-agent.noarch.rpm'
10 | }
11 | default: {
12 | fail("${::operatingsystem} not supported")
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/salt/local-only/appspec.yml:
--------------------------------------------------------------------------------
1 | version: 0.0
2 | os: linux
3 | files:
4 | - source: salt/
5 | destination: /etc/salt/codedeploy/
6 | - source: index.html
7 | destination: /var/www/html/salt/
8 | hooks:
9 | BeforeInstall:
10 | - location: deploy_hooks/before_install.sh
11 | runas: root
12 | ApplicationStart:
13 | - location: deploy_hooks/application_start.sh
14 | runas: root
15 | ValidateService:
16 | - location: deploy_hooks/verify_service.sh
17 | runas: root
18 |
--------------------------------------------------------------------------------
/lib/moonshot/stack_list_printer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | class StackListPrinter
5 | attr_accessor :stacks
6 |
7 | def initialize(stacks)
8 | @stacks = stacks
9 | @table = UnicodeTable.new('Environment List')
10 | end
11 |
12 | def print
13 | rows = @stacks.map do |stack|
14 | [stack.name, stack.creation_time, stack.status]
15 | end
16 |
17 | @table.add_table(rows)
18 |
19 | @table.draw
20 | @table.draw_children
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/puppet/masterless/appspec.yml:
--------------------------------------------------------------------------------
1 | version: 0.0
2 | os: linux
3 | files:
4 | - source: puppet/
5 | destination: /etc/puppet/codedeploy
6 | - source: target/hello.war
7 | destination: /var/lib/tomcat6/webapps
8 | hooks:
9 | BeforeInstall:
10 | - location: deploy_hooks/install-puppet.sh
11 | runas: root
12 | ApplicationStart:
13 | - location: deploy_hooks/puppet-apply.sh
14 | runas: root
15 | ValidateService:
16 | - location: deploy_hooks/verify_service.sh
17 | runas: root
18 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/chef/solo/appspec.yml:
--------------------------------------------------------------------------------
1 | version: 0.0
2 | os: linux
3 | files:
4 | - source: chef/
5 | destination: /etc/chef/codedeploy
6 | - source: target/hello.war
7 | destination: /var/lib/tomcat6/webapps
8 | hooks:
9 | BeforeInstall:
10 | - location: deploy_hooks/install-chef.sh
11 | timeout: 1800
12 | runas: root
13 | ApplicationStart:
14 | - location: deploy_hooks/chef-solo.sh
15 | runas: root
16 | ValidateService:
17 | - location: deploy_hooks/verify_service.sh
18 | runas: root
19 |
--------------------------------------------------------------------------------
/spec/moonshot/commands/console_spec.rb:
--------------------------------------------------------------------------------
1 | describe Moonshot::Commands::Console do
2 | describe '#execute' do
3 | it 'should start a pry session' do
4 | expect_any_instance_of(Moonshot::ControllerConfig).to receive(:update_for_account!)
5 | expect(Aws::EC2::Client).to receive(:new)
6 | expect(Aws::IAM::Client).to receive(:new)
7 | expect(Aws::AutoScaling::Client).to receive(:new)
8 | expect(Aws::CloudFormation::Client).to receive(:new)
9 | expect(Pry).to receive(:start)
10 | subject.execute
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/moonshot/ssh_fork_executor.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'open3'
4 |
5 | module Moonshot
6 | # Run an SSH command via fork/exec.
7 | class SSHForkExecutor
8 | Result = Struct.new(:output, :exitstatus)
9 |
10 | def run(cmd)
11 | output = StringIO.new
12 |
13 | exit_status = nil
14 | Open3.popen3(cmd) do |_, stdout, _, wt|
15 | output << stdout.read until stdout.eof?
16 | exit_status = wt.value.exitstatus
17 | end
18 |
19 | Result.new(output.string.chomp, exit_status)
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/README.md:
--------------------------------------------------------------------------------
1 | AWS CodeDeploy Samples
2 | ======================
3 |
4 | The samples in the repository each demonstrate one of a few different scenarios.
5 | These fall into one of a few different categories, including:
6 |
7 | - Sample Applications
8 | - Integrations and templates for configuration management systems
9 | - Integrations with load-balancers like Elastic Load Balancing
10 | - Sample hooks for version control systems like Git.
11 |
12 | License
13 | =======
14 |
15 | These samples and templates are all licensed under Apache 2.0.
16 |
17 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/puppet/aws-codedeploy/manifests/init.pp:
--------------------------------------------------------------------------------
1 | # == Class: codedeploy
2 | #
3 | # Install and manage the AWS CodeDeploy agent
4 | #
5 | # === Parameters
6 | #
7 | # [*package_source*]
8 | # URL or filepath passed to package provider to install agent
9 | #
10 | class codedeploy (
11 | $package_source = $codedeploy::params::package_source,
12 | ) inherits codedeploy::params {
13 |
14 | validate_string($package_source)
15 |
16 | class { 'codedeploy::install': } ~>
17 | class { 'codedeploy::service': } ->
18 | Class['codedeploy']
19 | }
20 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/ansible/local-only/lamp_simple/site.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # This playbook deploys the whole application stack in this site.
3 |
4 | - name: apply common configuration to all nodes
5 | hosts: all
6 | remote_user: root
7 |
8 | roles:
9 | - common
10 |
11 | - name: configure and deploy the webservers and application code
12 | hosts: webservers
13 | remote_user: root
14 |
15 | roles:
16 | - web
17 |
18 | - name: deploy MySQL and configure the databases
19 | hosts: dbservers
20 | remote_user: root
21 |
22 | roles:
23 | - db
24 |
--------------------------------------------------------------------------------
/lib/moonshot/commands/tag_arguments.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | module Commands
5 | module TagArguments
6 | def parser
7 | parser = super
8 |
9 | parser.on('--tag KEY=VALUE', '-TKEY=VALUE', 'Specify Stack Tag on the command line') do |v|
10 | data = v.split('=', 2)
11 | unless data.size == 2
12 | raise "Invalid tag format '#{v}', expected KEY=VALUE (e.g. MyStackTag=12)"
13 | end
14 |
15 | Moonshot.config.extra_tags << { key: data[0], value: data[1] }
16 | end
17 | end
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/puppet/aws-codedeploy/tests/init.pp:
--------------------------------------------------------------------------------
1 | # The baseline for module testing used by Puppet Labs is that each manifest
2 | # should have a corresponding test manifest that declares that class or defined
3 | # type.
4 | #
5 | # Tests are then run by using puppet apply --noop (to check for compilation
6 | # errors and view a log of events) or by fully applying the test in a virtual
7 | # environment (to compare the resulting system state to the desired state).
8 | #
9 | # Learn more about module testing here:
10 | # http://docs.puppetlabs.com/guides/tests_smoke.html
11 | #
12 | include codedeploy
13 |
--------------------------------------------------------------------------------
/lib/moonshot/commands/delete.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | module Commands
5 | class Delete < Moonshot::Command
6 | include ShowAllEventsOption
7 |
8 | self.usage = 'delete [options]'
9 | self.description = 'Delete an existing environment'
10 |
11 | def parser
12 | parser = super
13 | parser.on('--template-file=FILE', 'Override the path to the CloudFormation template.') do |v|
14 | Moonshot.config.template_file = v
15 | end
16 | end
17 |
18 | def execute
19 | controller.delete
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/moonshot/commands/console.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'pry'
4 |
5 | module Moonshot
6 | module Commands
7 | class Console < Moonshot::Command
8 | self.usage = 'console [options]'
9 | self.description = 'Launch a interactive Ruby console with configured access to AWS'
10 |
11 | def execute
12 | controller
13 |
14 | ec2 = Aws::EC2::Client.new
15 | iam = Aws::IAM::Client.new
16 | autoscaling = Aws::AutoScaling::Client.new
17 | cf = Aws::CloudFormation::Client.new
18 |
19 | Pry.start binding, backtrace: nil
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/moonshot/commands/parent_stack_option.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | module Commands
5 | module ParentStackOption
6 | def parser
7 | parser = super
8 |
9 | parser.on('-pPARENT_STACK', '--parent=PARENT_STACK',
10 | 'Parent stack to import parameters from') do |v|
11 | Moonshot.config.parent_stacks = [v]
12 | end
13 |
14 | parser.on('--parents a,b,c', Array,
15 | 'List of parent stacks to import parameters from') do |v|
16 | Moonshot.config.parent_stacks = v
17 | end
18 | end
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/lib/moonshot/resources_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | # Provides shorthand methods for accessing resources provided by the Resources
5 | # container.
6 | module ResourcesHelper
7 | attr_writer :resources
8 |
9 | private
10 |
11 | # TODO: Deprecate this interface.
12 | def log
13 | @log ||= Logger.new($stdout)
14 | end
15 |
16 | def stack
17 | raise 'Resources not provided to Mechanism!' unless @resources
18 |
19 | @resources.stack
20 | end
21 |
22 | def ilog
23 | raise 'Resources not provided to Mechanism!' unless @resources
24 |
25 | @resources.ilog
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/ansible/local-only/lamp_simple/roles/web/tasks/install_httpd.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # These tasks install http and the php modules.
3 |
4 | - name: Install http and php etc
5 | yum: name={{ item }} state=present
6 | with_items:
7 | - httpd
8 | - php
9 | - php-mysql
10 | - git
11 |
12 | - name: insert iptables rule for httpd
13 | lineinfile: dest=/etc/sysconfig/iptables create=yes state=present regexp="{{ httpd_port }}" insertafter="^:OUTPUT "
14 | line="-A INPUT -p tcp --dport {{ httpd_port }} -j ACCEPT"
15 | notify: restart iptables
16 |
17 | - name: http service state
18 | service: name=httpd state=started enabled=yes
19 |
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: Moonshot
2 | theme: 'mkdocs'
3 | pages:
4 | - Home: 'index.md'
5 | - Example: example.md
6 | - User Guide:
7 | - 'CLI Commands': 'user-guide/cli.md'
8 | - 'Including Moonshot in your project': 'user-guide/include_in_your_project.md'
9 | - 'Working with parent stacks': 'user-guide/parent_stacks.md'
10 | - Mechanisms:
11 | - 'Artifact Repository': 'mechanisms/artifact-repository.md'
12 | - 'Build': 'mechanisms/build.md'
13 | - 'Deployment': 'mechanisms/deployment.md'
14 | - Plugins: plugins.md
15 | - Bundled plugins:
16 | - 'Backup plugin': 'plugins/backup.md'
17 | - About:
18 | - 'How to Contribute': 'about/contribute.md'
19 | - 'License': 'about/license.md'
20 |
--------------------------------------------------------------------------------
/lib/moonshot/creds_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | # Create convenience methods for various AWS client creation.
5 | module CredsHelper
6 | def cf_client(**args)
7 | Aws::CloudFormation::Client.new(args)
8 | end
9 |
10 | def cd_client(**args)
11 | Aws::CodeDeploy::Client.new(args)
12 | end
13 |
14 | def ec2_client(**args)
15 | Aws::EC2::Client.new(args)
16 | end
17 |
18 | def iam_client(**args)
19 | Aws::IAM::Client.new(args)
20 | end
21 |
22 | def as_client(**args)
23 | Aws::AutoScaling::Client.new(args)
24 | end
25 |
26 | def s3_client(**args)
27 | Aws::S3::Client.new(args)
28 | end
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/puppet/masterless/deploy_hooks/install-puppet.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Check to see that puppet itself is installed
4 | yum list installed puppet &> /dev/null
5 | if [ $? != 0 ]; then
6 | yum -y install puppet
7 | fi
8 |
9 | # Create the base directory for the system-wide puppet modules
10 | mkdir -p /etc/puppet/modules
11 |
12 | puppet="/usr/bin/puppet"
13 |
14 | # Check for each of the modules we need. If they're not installed, install them.
15 | for module in puppetlabs/stdlib puppetlabs/java puppetlabs/tomcat stahnma/epel; do
16 | $puppet module list | grep -q $(basename $module)
17 | if [ $? != 0 ]; then
18 | $puppet module install $module
19 | fi
20 | done
21 |
22 | exit 0
23 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/chef/solo/src/main/java/com/amazonaws/sample/HelloServlet.java:
--------------------------------------------------------------------------------
1 | package com.amazonaws.sample;
2 |
3 | import javax.servlet.http.HttpServlet;
4 | import javax.servlet.ServletException;
5 | import javax.servlet.http.HttpServletRequest;
6 | import javax.servlet.http.HttpServletResponse;
7 |
8 | import java.io.IOException;
9 | import java.io.PrintWriter;
10 |
11 | public class HelloServlet extends HttpServlet {
12 |
13 | protected void doGet(HttpServletRequest request,
14 | HttpServletResponse response)
15 | throws ServletException, IOException
16 | {
17 | PrintWriter writer = response.getWriter();
18 | writer.print("Hello World
");
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/puppet/masterless/src/main/java/com/amazonaws/sample/HelloServlet.java:
--------------------------------------------------------------------------------
1 | package com.amazonaws.sample;
2 |
3 | import javax.servlet.http.HttpServlet;
4 | import javax.servlet.ServletException;
5 | import javax.servlet.http.HttpServletRequest;
6 | import javax.servlet.http.HttpServletResponse;
7 |
8 | import java.io.IOException;
9 | import java.io.PrintWriter;
10 |
11 | public class HelloServlet extends HttpServlet {
12 |
13 | protected void doGet(HttpServletRequest request,
14 | HttpServletResponse response)
15 | throws ServletException, IOException
16 | {
17 | PrintWriter writer = response.getWriter();
18 | writer.print("Hello World
");
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lib/moonshot/stack_template.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | # A StackTemplate loads the template from disk and stores information
5 | # about it.
6 | class StackTemplate
7 | attr_reader :filename
8 |
9 | def initialize(filename)
10 | @filename = filename
11 | end
12 |
13 | def parameters
14 | template_body.fetch('Parameters', {}).map do |k, v|
15 | StackParameter.new(k,
16 | default: v['Default'],
17 | description: v.fetch('Description', ''))
18 | end
19 | end
20 |
21 | def resource_names
22 | template_body.fetch('Resources', {}).keys
23 | end
24 |
25 | def exist?
26 | File.exist?(@filename)
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/chef/solo/src/main/webapp/WEB-INF/web.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 | Hello World Web Application
8 |
9 |
10 | HelloServlet
11 | com.amazonaws.sample.HelloServlet
12 |
13 |
14 |
15 | HelloServlet
16 | /
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/lib/moonshot/stack_parameter_printer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | # Displays information about existing stack parameters to the user, with
5 | # information on what a stack update would do.
6 | class StackParameterPrinter
7 | def initialize(stack, table)
8 | @stack = stack
9 | @table = table
10 | end
11 |
12 | def print
13 | p_table = @table.add_leaf('Stack Parameters')
14 | rows = @stack.parameters.sort.map do |key, value|
15 | ["#{key}:", format_value(value)]
16 | end
17 |
18 | p_table.add_table(rows)
19 | end
20 |
21 | def format_value(value)
22 | if value.size > 60
23 | "#{value[0..60]}..."
24 | else
25 | value
26 | end
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/puppet/masterless/src/main/webapp/WEB-INF/web.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 | Hello World Web Application
8 |
9 |
10 | HelloServlet
11 | com.amazonaws.sample.HelloServlet
12 |
13 |
14 |
15 | HelloServlet
16 | /
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/lib/moonshot/always_use_default_source.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | # The AlwaysUseDefaultSource will always use the previous value in
5 | # the stack, or use the default value during stack creation. This is
6 | # useful if plugins provide the value for a parameter, and we don't
7 | # want to prompt the user for an override. Of course, overrides from
8 | # answer files or command-line arguments will always apply.
9 | class AlwaysUseDefaultSource
10 | def get(sp)
11 | # Don't do anything, the default will apply on create, and the
12 | # previous value will be used on update.
13 | return if sp.default?
14 |
15 | raise "Parameter #{sp.name} does not have a default, cannot use AlwaysUseDefaultSource!"
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/ansible/local-only/lamp_simple/roles/common/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # This playbook contains common plays that will be run on all nodes.
3 |
4 | - name: Install ntp
5 | yum: name=ntp state=present
6 | tags: ntp
7 |
8 | - name: Configure ntp file
9 | template: src=ntp.conf.j2 dest=/etc/ntp.conf
10 | tags: ntp
11 | notify: restart ntp
12 |
13 | - name: Start the ntp service
14 | service: name=ntpd state=started enabled=true
15 | tags: ntp
16 |
17 | - name: Check if /etc/sysconfig/iptables exists
18 | stat: path=/etc/sysconfig/iptables
19 | register: ipt
20 |
21 | - name: Place basic iptables rules
22 | template: src=iptables.j2 dest=/etc/sysconfig/iptables
23 | notify: restart iptables
24 | when: ipt is defined and ipt.stat.exists == false
25 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/chef/README.md:
--------------------------------------------------------------------------------
1 | Chef and AWS CodeDeploy
2 | -----------------------
3 |
4 | [Chef](https://www.getchef.com) is a configuration management tool that enables users to write
5 | recipes that describe how the instances in their fleet are configured. AWS provides two template
6 | samples for integrating Chef and AWS CodeDeploy. The first is a Chef cookbook that will install and
7 | start the AWS CodeDeploy host agent, giving you the ability to continue managing your host
8 | infrastructure via Chef while also being able to take advantage of the power of AWS CodeDeploy. The
9 | second sample template demonstrates how to use CodeDeploy to orchestrate running cookbooks and
10 | recipes via chef-solo on each node.
11 |
12 | [Learn more >](https://github.com/awslabs/aws-codedeploy-samples)
13 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/ansible/README.md:
--------------------------------------------------------------------------------
1 | Ansible and AWS CodeDeploy
2 | --------------------------
3 |
4 | [Ansible](http://www.ansible.com) is a radically simple IT automation platform that makes your
5 | applications and systems easier to deploy. If you already have a set of Ansible playbooks, but just
6 | need somewhere to run them, our first template for Ansible and AWS CodeDeploy will demonstrate how a
7 | couple of simple deployment hooks will ensure Ansible is available on the local deployment instance
8 | and run the given playbooks. Alternatively, if you already have a process for building and
9 | maintaining your inventory, we've also built an Ansible module that you can use to install and run
10 | the AWS CodeDeploy host agent.
11 |
12 | [Learn more >](https://github.com/awslabs/aws-codedeploy-samples)
13 |
--------------------------------------------------------------------------------
/lib/moonshot.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'English'
4 |
5 | require 'aws-sdk-cloudformation'
6 | require 'aws-sdk-codedeploy'
7 | require 'aws-sdk-ec2'
8 | require 'aws-sdk-iam'
9 | require 'aws-sdk-autoscaling'
10 | require 'aws-sdk-s3'
11 |
12 | require 'logger'
13 | require 'thor'
14 | require 'interactive-logger'
15 |
16 | module Moonshot
17 | class << self
18 | attr_writer :config
19 | end
20 |
21 | def self.config
22 | @config ||= Moonshot::ControllerConfig.new
23 | block_given? ? yield(@config) : @config
24 | end
25 |
26 | module ArtifactRepository
27 | end
28 |
29 | module BuildMechanism
30 | end
31 |
32 | module DeploymentMechanism
33 | end
34 |
35 | module Plugins
36 | end
37 | end
38 |
39 | require 'require_all'
40 | require_rel 'moonshot'
41 | require_rel 'plugins'
42 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/puppet/README.md:
--------------------------------------------------------------------------------
1 | Puppet and AWS CodeDeploy
2 | -----------------------
3 |
4 | [Puppet](http://puppetlabs.com/) is a configuration management system that allows you to define the
5 | state of your IT infrastructure, then automatically enforces the correct state. AWS provides a
6 | couple of sample templates to get you working with Puppet and AWS CodeDeploy faster. The first is a
7 | Puppet module that will install and start the AWS CodeDeploy host agent, giving you the ability to
8 | continue managing your host infrastructure via Puppet while also being able to take advantage of the
9 | power of AWS CodeDeploy. The second sample template demonstrates how to use CodeDeploy to
10 | orchestrate running modules and manifests via masterless puppet on each node.
11 |
12 | [Learn more >](https://github.com/awslabs/aws-codedeploy-samples)
13 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/salt/README.md:
--------------------------------------------------------------------------------
1 | SaltStack and AWS CodeDeploy
2 | ----------------------------
3 |
4 | [Salt](http://www.saltstack.com) is a fast, scalable and flexible systems management software for
5 | data center automation, cloud orchestration, server provisioning, configuration management and more.
6 | The two sample templates AWS provides offer examples for how you can integrate existing Salt
7 | infrastructure with AWS CodeDeploy. On the one hand, you can use the AWS CodeDeploy module to
8 | install and run the CodeDeploy host agent on your minions. On the other hand, you can use AWS
9 | CodeDeploy via a couple of simple deployment hooks to orchestrate running your Salt States. Either
10 | way, you get to take advantage of the power of both Salt and AWS CodeDeploy.
11 |
12 | [Learn more >](https://github.com/awslabs/aws-codedeploy-samples)
13 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/ansible/local-only/lamp_simple/roles/web/templates/index.php.j2:
--------------------------------------------------------------------------------
1 |
2 |
3 | Ansible Application
4 |
5 |
6 |
7 | Homepage
8 |
9 | ";
13 | echo "List of Databases: ";
14 | {% for host in groups['dbservers'] %}
15 | $link = mysql_connect('{{ hostvars[host].ansible_eth0.ipv4.address }}', '{{ hostvars[host].dbuser }}', '{{ hostvars[host].upassword }}') or die(mysql_error());
16 | {% endfor %}
17 | $res = mysql_query("SHOW DATABASES");
18 | while ($row = mysql_fetch_assoc($res)) {
19 | echo $row['Database'] . "\n";
20 | }
21 | ?>
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/spec/moonshot/command_optional_argument_spec.rb:
--------------------------------------------------------------------------------
1 | class OptionalArgumentCommand < Moonshot::Command
2 | def execute(version = 'development')
3 | puts "selected version: #{version}"
4 | end
5 | end
6 |
7 | describe 'optional argument for command' do
8 | def try(klass, args = [])
9 | Moonshot::CommandLineDispatcher.new('stuff', klass, args)
10 | .dispatch!
11 | end
12 |
13 | Moonshot::AccountContext.set('optional-account')
14 | ARGV.clear
15 |
16 | it 'should let us run OptionalArgumentCommand without extra arguments' do
17 | expect { try(OptionalArgumentCommand) }
18 | .to output(/selected version: development/).to_stdout
19 | end
20 |
21 | it 'should let us run OptionalArgumentCommand with extra argument' do
22 | expect { try(OptionalArgumentCommand, ['production']) }
23 | .to output(/selected version: production/).to_stdout
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/docs/mechanisms/artifact-repository.md:
--------------------------------------------------------------------------------
1 | # ArtifactRepository
2 |
3 | ## S3Bucket
4 |
5 | The store action will upload the file using the S3 PutObject API call.
6 | The local environment must be configured with appropriate
7 | credentials. Running `doctor` will verify those credentials for you.
8 |
9 | To create a new S3Bucket ArtifactRepository:
10 | ```ruby
11 | Moonshot.config do |c|
12 | c.artifact_repository = S3Bucket.new('my-bucket-name')
13 | end
14 | ```
15 |
16 | ## S3BucketViaGithubReleases
17 |
18 | S3 Bucket repository backed by GitHub releases. If a SemVer package
19 | isn't found in S3, it is downloaded from GitHub releases to avoid not
20 | being able to release in case there is trouble with AWS S3.
21 |
22 | To create a new S3BucketViaGithubReleases ArtifactRepository:
23 | ```ruby
24 | Moonshot.config do |c|
25 | c.artifact_repository = S3BucketViaGithubReleases.new('my-bucket-name')
26 | end
27 | ```
28 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/applications/SampleApp_Linux/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Sample Deployment
6 |
26 |
27 |
28 |
29 |
Congratulations
30 |
This application was deployed using AWS CodeDeploy.
31 |
For next steps, read the AWS CodeDeploy Documentation.
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/spec/moonshot/commands/build_spec.rb:
--------------------------------------------------------------------------------
1 | describe Moonshot::Commands::Build do
2 |
3 | it 'should raise RuntimeError when send --skip-ci-status param without GithubRelease plugin' do
4 | cli_dispatcher = Moonshot::CommandLineDispatcher.new('build', subject, {})
5 | parser = cli_dispatcher.send(:build_parser, subject)
6 | expect { parser.parse(%w(--skip-ci-status)) }.to raise_error(RuntimeError)
7 | end
8 |
9 | it 'should not raise RuntimeError when send --skip-ci-status param with GithubRelease plugin' do
10 | Moonshot.config = Moonshot::ControllerConfig.new
11 | Moonshot.config do |c|
12 | c.build_mechanism = Moonshot::BuildMechanism::GithubRelease.new('')
13 | end
14 | cli_dispatcher = Moonshot::CommandLineDispatcher.new('build', subject, {})
15 | parser = cli_dispatcher.send(:build_parser, subject)
16 | expect { parser.parse(%w(--skip-ci-status)) }.to_not raise_error
17 | end
18 |
19 | end
20 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/applications/SampleApp_Windows/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Sample Deployment
6 |
29 |
30 |
31 |
32 |
Congratulations
33 |
This application was deployed using AWS CodeDeploy.
34 |
For next steps, read the AWS CodeDeploy Documentation.
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/lib/plugins/encrypted_parameters/parameter_encrypter.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'base64'
4 | module Moonshot
5 | module Plugins
6 | class EncryptedParameters
7 | # Class that can encrypt and decrypt parameters using KMS.
8 | class ParameterEncrypter
9 | # @param [String] key_arn The ARN for the KMS key.
10 | def initialize(key_arn)
11 | @kms_client = Aws::KMS::Client.new
12 | @key_arn = key_arn
13 | end
14 |
15 | # Encrypt and base64 encode the parameter value.
16 | #
17 | # @param [String] param_value The parameter to encrypt.
18 | # @return [String] base64 encoded encrypted ciphertext.
19 | def encrypt(param_value)
20 | resp = @kms_client.encrypt(key_id: @key_arn, plaintext: param_value)
21 |
22 | # Use strict here to avoid newlines which cause issues with parameters.
23 | Base64.strict_encode64(resp.ciphertext_blob)
24 | end
25 | end
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/moonshot/ask_user_source.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'colorize'
4 |
5 | module Moonshot
6 | class AskUserSource
7 | def get(sp)
8 | return unless Moonshot.config.interactive
9 |
10 | @sp = sp
11 |
12 | prompt
13 | loop do
14 | input = gets.chomp
15 |
16 | if String(input).empty? && @sp.default?
17 | # We will use the default value, print it here so the output is clear.
18 | puts 'Using default value.'
19 | return
20 | elsif String(input).empty?
21 | puts "Cannot proceed without value for #{@sp.name}!"
22 | else
23 | @sp.set(String(input))
24 | return
25 | end
26 |
27 | prompt
28 | end
29 | end
30 |
31 | private
32 |
33 | def prompt
34 | print "(#{@sp.name})".light_black
35 | print " #{@sp.description}" unless @sp.description.empty?
36 | print " [#{@sp.default}]".light_black if @sp.default?
37 | print ': '
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/ansible/local-only/lamp_simple/roles/db/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # This playbook will install mysql and create db user and give permissions.
3 |
4 | - name: Install Mysql package
5 | yum: name={{ item }} state=installed
6 | with_items:
7 | - mysql-server
8 | - MySQL-python
9 |
10 | - name: Create Mysql configuration file
11 | template: src=my.cnf.j2 dest=/etc/my.cnf
12 | notify:
13 | - restart mysql
14 |
15 | - name: Start Mysql Service
16 | service: name=mysqld state=started enabled=true
17 |
18 | - name: insert iptables rule
19 | lineinfile: dest=/etc/sysconfig/iptables state=present regexp="{{ mysql_port }}"
20 | insertafter="^:OUTPUT " line="-A INPUT -p tcp --dport {{ mysql_port }} -j ACCEPT"
21 | notify: restart iptables
22 |
23 | - name: Create Application Database
24 | mysql_db: name={{ dbname }} state=present
25 |
26 | - name: Create Application DB User
27 | mysql_user: name={{ dbuser }} password={{ upassword }} priv=*.*:ALL host='%' state=present
28 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/ansible/local-only/lamp_simple/roles/common/templates/iptables.j2:
--------------------------------------------------------------------------------
1 | # {{ ansible_managed }}
2 | *filter
3 | :INPUT ACCEPT [0:0]
4 | :FORWARD ACCEPT [0:0]
5 | :OUTPUT ACCEPT [0:0]
6 |
7 | #Syn-flood protection
8 | -A INPUT -p tcp ! --syn -m state --state NEW -j DROP
9 |
10 | #Force SYN packets check
11 | -A INPUT -f -j DROP
12 |
13 | #Force Fragments packets check
14 | -A INPUT -p tcp --tcp-flags ALL ALL -j DROP
15 |
16 | #XMAS packets
17 | -A INPUT -p tcp --tcp-flags ALL NONE -j DROP
18 |
19 | # Accept packets belonging to established and related connections
20 | -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
21 |
22 | # Allow pings
23 | -A INPUT -p icmp --icmp-type any -j ACCEPT
24 |
25 | # Set access for localhost
26 | -A INPUT -i lo -j ACCEPT
27 |
28 | # Allow SSH connections on tcp port 22
29 | -A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
30 |
31 | # Set default policies for INPUT, FORWARD and OUTPUT chains
32 | -P INPUT DROP
33 | -P FORWARD DROP
34 | -P OUTPUT ACCEPT
35 |
36 | COMMIT
37 |
--------------------------------------------------------------------------------
/lib/moonshot/commands/parameter_arguments.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # rubocop:disable Layout/LineLength
4 | module Moonshot
5 | module Commands
6 | module ParameterArguments
7 | def parser
8 | parser = super
9 |
10 | parser.on('--[no-]interactive', TrueClass, 'Use interactive prompts for gathering missing configuration.') do |v|
11 | Moonshot.config.interactive = v
12 | end
13 |
14 | parser.on('--answer-file FILE', '-aFILE', 'Load Stack Parameters from a YAML file') do |v|
15 | Moonshot.config.answer_file = File.expand_path(v)
16 | end
17 |
18 | parser.on('--parameter KEY=VALUE', '-PKEY=VALUE', 'Specify Stack Parameter on the command line') do |v|
19 | data = v.split('=', 2)
20 | raise "Invalid parameter format '#{v}', expected KEY=VALUE (e.g. MyStackParameter=12)" unless data.size == 2
21 |
22 | Moonshot.config.parameter_overrides[data[0]] = data[1]
23 | end
24 | end
25 | end
26 | end
27 | end
28 | # rubocop:enable Layout/LineLength
29 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/chef/solo/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | com.amazonaws.sample
5 | hello
6 | war
7 | 1.0-SNAPSHOT
8 | hello Maven Webapp
9 | http://maven.apache.org
10 |
11 |
12 |
13 | junit
14 | junit
15 | 3.8.1
16 | test
17 |
18 |
19 | javax.servlet
20 | javax.servlet-api
21 | 3.0.1
22 | provided
23 |
24 |
25 |
26 |
27 | hello
28 |
29 |
30 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/puppet/masterless/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | com.amazonaws.sample
5 | hello
6 | war
7 | 1.0-SNAPSHOT
8 | hello Maven Webapp
9 | http://maven.apache.org
10 |
11 |
12 |
13 | junit
14 | junit
15 | 3.8.1
16 | test
17 |
18 |
19 | javax.servlet
20 | javax.servlet-api
21 | 3.0.1
22 | provided
23 |
24 |
25 |
26 |
27 | hello
28 |
29 |
30 |
--------------------------------------------------------------------------------
/lib/plugins/dynamic_template.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | module Plugins
5 | class DynamicTemplate
6 | def initialize(source:, parameters:, destination:)
7 | @dynamic_template = ::Moonshot::DynamicTemplate.new(
8 | source:,
9 | parameters:,
10 | destination:
11 | )
12 | end
13 |
14 | def run_hook
15 | @dynamic_template.process
16 | end
17 |
18 | def cli_hook(parser)
19 | parser.on('--template-file=FILE', 'Override the path to the CloudFormation template.') do |v|
20 | @dynamic_template.destination = v
21 | Moonshot.config.template_file = v
22 | end
23 | end
24 |
25 | # Moonshot hooks to trigger this plugin.
26 | alias setup_create run_hook
27 | alias setup_update run_hook
28 | alias setup_delete run_hook
29 |
30 | # Moonshot hooks to add CLI options.
31 | alias create_cli_hook cli_hook
32 | alias delete_cli_hook cli_hook
33 | alias update_cli_hook cli_hook
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/docs/user-guide/parent_stacks.md:
--------------------------------------------------------------------------------
1 | # Parent Stacks
2 |
3 | Moonshot supports referencing another CloudFormation stack as a
4 | "Parent" during creation time. This relationship is used only for creation,
5 | where any outputs of that stack that match names of the parameters for the local
6 | stack will be used as parameters, and saved into a local .yml file for future
7 | use.
8 |
9 | The order of precedence for parameters is:
10 |
11 | - Existing parameter overrides in the .yml file.
12 | - The value from the parent stack's output.
13 | - Any default value in the CloudFormation template.
14 |
15 | ## A word of caution
16 |
17 | It's not advisable to use default values in the CloudFormation template.
18 | Consider the following example:
19 |
20 | - Developer A launches a stack, referencing a specific parent stack.
21 | - Values are copied into a local .yml file, and used during stack creation.
22 | - Developer B assists Developer A with a stack update issue and runs 'update'
23 | without the local overrides .yml file. In doing so, the default values in the
24 | template are used.
25 | - Developer A is sad.
26 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | ---
2 | AllCops:
3 | TargetRubyVersion: 3.3.4
4 | Exclude:
5 | - '.gemspec'
6 | - 'vendor/**/*'
7 | - 'sample/bin/aws-codedeploy-samples/**/*'
8 | - 'sample/gems/**/*'
9 | - 'spec/**/*'
10 | - 'lib/default/**/*'
11 | Metrics/AbcSize:
12 | Max: 35
13 | Metrics/BlockLength:
14 | Max: 35
15 | Metrics/MethodLength:
16 | Max: 30
17 | Layout/LineLength:
18 | Max: 120
19 | Style/ClassAndModuleChildren:
20 | Enabled: false
21 | Metrics/ClassLength:
22 | Max: 130
23 | Style/Documentation:
24 | Enabled: false
25 | Lint/MissingSuper:
26 | Enabled: false
27 | Style/MissingRespondToMissing:
28 | Enabled: false
29 | Layout/HeredocIndentation:
30 | Enabled: false
31 | Naming/HeredocDelimiterNaming:
32 | Enabled: false
33 | Naming/MemoizedInstanceVariableName:
34 | Enabled: false
35 | Naming/MethodParameterName:
36 | Enabled: false
37 | Metrics/CyclomaticComplexity:
38 | Enabled: false
39 | Metrics/PerceivedComplexity:
40 | Enabled: false
41 | Style/OptionalBooleanParameter:
42 | Enabled: false
43 | Style/IfUnlessModifier:
44 | Enabled: false
45 | Lint/EmptyFile:
46 | Enabled: false
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/ansible/local-only/lamp_simple/README.md:
--------------------------------------------------------------------------------
1 | Building a simple LAMP stack and deploying Application using Ansible Playbooks.
2 | -------------------------------------------
3 |
4 | These playbooks require Ansible 1.2.
5 |
6 | These playbooks are meant to be a reference and starter's guide to building
7 | Ansible Playbooks. These playbooks were tested on CentOS 6.x so we recommend
8 | that you use CentOS or RHEL to test these modules.
9 |
10 | This LAMP stack can be on a single node or multiple nodes. The inventory file
11 | 'hosts' defines the nodes in which the stacks should be configured.
12 |
13 | [webservers]
14 | localhost
15 |
16 | [dbservers]
17 | bensible
18 |
19 | Here the webserver would be configured on the local host and the dbserver on a
20 | server called "bensible". The stack can be deployed using the following
21 | command:
22 |
23 | ansible-playbook -i hosts site.yml
24 |
25 | Once done, you can check the results by browsing to http://localhost/index.php.
26 | You should see a simple test page and a list of databases retrieved from the
27 | database server.
28 |
--------------------------------------------------------------------------------
/lib/moonshot/ssh_command_builder.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'shellwords'
4 |
5 | module Moonshot
6 | # Create an ssh command from configuration.
7 | class SSHCommandBuilder
8 | Result = Struct.new(:cmd, :ip)
9 |
10 | def initialize(ssh_config, instance_id)
11 | @config = ssh_config
12 | @instance_id = instance_id
13 | end
14 |
15 | def build(command = nil)
16 | cmd = ['ssh', '-t']
17 | cmd << "-i #{@config.ssh_identity_file}" if @config.ssh_identity_file
18 | cmd << "-l #{@config.ssh_user}" if @config.ssh_user
19 | cmd << instance_ip
20 | cmd << Shellwords.escape(command) if command
21 | Result.new(cmd.join(' '), instance_ip)
22 | end
23 |
24 | private
25 |
26 | def instance_ip
27 | @instance_ip ||= Aws::EC2::Client.new
28 | .describe_instances(instance_ids: [@instance_id])
29 | .reservations.first.instances.first.public_ip_address
30 | rescue StandardError
31 | raise "Failed to determine public IP address for instance #{@instance_id}!"
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/lib/moonshot/interactive_logger_proxy.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'forwardable'
4 |
5 | module Moonshot
6 | # This class pretends to be an InteractiveLogger for systems that are
7 | # non-interactive.
8 | class InteractiveLoggerProxy
9 | # Non-interactive version of InteractiveLogger::Step.
10 | class Step
11 | def initialize(logger)
12 | @logger = logger
13 | end
14 |
15 | def blank; end
16 |
17 | def continue(str = nil)
18 | @logger.info(str) if str
19 | end
20 |
21 | def failure(str = 'Failure')
22 | @logger.error(str)
23 | end
24 |
25 | def repaint; end
26 |
27 | def success(str = 'Success')
28 | @logger.info(str)
29 | end
30 | end
31 |
32 | extend Forwardable
33 |
34 | def_delegator :@debug, :itself, :debug?
35 | def_delegators :@logger, :debug, :error, :info
36 | alias msg info
37 |
38 | def initialize(logger, debug: false)
39 | @debug = debug
40 | @logger = logger
41 | end
42 |
43 | def start(str)
44 | @logger.info(str)
45 | yield Step.new(@logger)
46 | end
47 | alias start_threaded start
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/chef/solo/deploy_hooks/install-chef.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [ ! -z "`which yum`" ]; then
4 |
5 | # On rpm-based system, use yum to install rubygems and related packages for building native
6 | # extensions
7 |
8 | for pkg in rubygems20 gcc-c++ ruby20-devel make autoconf automake; do
9 | yum list installed $pkg &> /dev/null
10 | if [ $? != 0 ]; then
11 | yum -y install $pkg
12 | fi
13 | done
14 |
15 | elif [ ! -z "`which apt-get`" ]; then
16 | # On debian-like system, use apt-get to install
17 |
18 | for pkg in ruby2.0-dev ruby2.0 make autoconf g++; do
19 | aptitude show $pkg | grep -q 'State: installed'
20 | if [ $? != 0 ]; then
21 | apt-get -y install $pkg
22 | fi
23 | done
24 | fi
25 |
26 | # Now, we can install the required gems
27 | for gem in chef ohai librarian-chef io-console; do
28 | gem2.0 list | grep -q $gem
29 | if [ $? != 0 ]; then
30 | gem2.0 install $gem
31 |
32 | if [ $? != 0 ]; then
33 | echo "Failed to install required gems. Cannot continue with deployment"
34 | exit 1
35 | fi
36 | fi
37 | done
38 |
--------------------------------------------------------------------------------
/lib/moonshot/ssh_command.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'thor'
4 |
5 | module Moonshot
6 | # A SSHCommand that is automatically registered with the
7 | # Moonshot::CommandLine, including options for SSH access.
8 | class SSHCommand < Moonshot::Command
9 | def parser
10 | parser = super
11 | parser.on('-l', '--user USER', 'User to log into remote machine as') do |v|
12 | Moonshot.config.ssh_config.ssh_user = v
13 | end
14 |
15 | parser.on('-i', '--identity-file FILE', 'SSH Private Key to authenticate with') do |v|
16 | Moonshot.config.ssh_config.ssh_identity_file = v
17 | end
18 |
19 | parser.on('-s', '--instance INSTANCE_ID', 'Specific AWS EC2 ID to connect to') do |v|
20 | Moonshot.config.ssh_instance = v
21 | end
22 |
23 | parser.on('-c', '--command COMMAND', 'Command to execute on the remote host') do |v|
24 | Moonshot.config.ssh_command = v
25 | end
26 |
27 | parser.on('-g', '--auto-scaling-group ASG_NAME',
28 | 'The logical ID of the ASG to SSH into, required for multiple stacks') do |v|
29 | Moonshot.config.ssh_auto_scaling_group_name = v
30 | end
31 | end
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/spec/moonshot/interactive_logger_proxy_spec.rb:
--------------------------------------------------------------------------------
1 | shared_examples 'compatible class' do |expected_class|
2 | expected_class.public_instance_methods(false).each do |name|
3 | describe "##{name}" do
4 | it 'should be defined.' do
5 | described_class.public_instance_method(name)
6 | end
7 |
8 | it "should have method arguments compatible with #{expected_class.name}##{name}" do
9 | expected_method = expected_class.instance_method(name)
10 | actual_method = described_class.instance_method(name)
11 | expect(actual_method.arity).to eq(-1).or be >= expected_method.arity
12 | actual_method.parameters.each_with_index do |(type, arg), index|
13 | # If it's a required argument, expect it to be identical.
14 | if type == :req
15 | expect(name => expected_method.parameters[index])
16 | .to eq(name => [type, arg])
17 | end
18 | end
19 | end
20 | end
21 | end
22 | end
23 |
24 | describe Moonshot::InteractiveLoggerProxy do
25 | include_examples 'compatible class', InteractiveLogger
26 | end
27 |
28 | describe Moonshot::InteractiveLoggerProxy::Step do
29 | include_examples 'compatible class', InteractiveLogger::Step
30 | end
31 |
--------------------------------------------------------------------------------
/spec/moonshot/commands/update_spec.rb:
--------------------------------------------------------------------------------
1 | describe Moonshot::Commands::Update do
2 | before(:each) do
3 | Moonshot.config = Moonshot::ControllerConfig.new
4 | end
5 |
6 | tests = {
7 | %w(-P Key=Value -P OtherKey=OtherValue) => { 'Key' => 'Value', 'OtherKey' => 'OtherValue' },
8 | %w(-PKey=ValueWith=Equals) => { 'Key' => 'ValueWith=Equals' }
9 | }
10 |
11 | tests.each do |input, expected|
12 | it "Should process #{input} correctly" do
13 | op = subject.parser
14 | op.parse(input)
15 | expect(Moonshot.config.parameter_overrides).to match(expected)
16 | end
17 | end
18 |
19 | extra_tags = {
20 | %w(-T Key=Value -T OtherKey=OtherValue) =>
21 | [{ key: 'Key', value: 'Value' }, { key: 'OtherKey', value: 'OtherValue' }],
22 | %w(-TKey=ValueWith=Equals) => [{ key: 'Key', value: 'ValueWith=Equals' }],
23 | %w(--tag Key=Value --tag OtherKey=OtherValue) =>
24 | [{ key: 'Key', value: 'Value' }, { key: 'OtherKey', value: 'OtherValue' }]
25 | }
26 |
27 | extra_tags.each do |input, expected|
28 | it "Should process #{input} correctly" do
29 | op = subject.parser
30 | op.parse(input)
31 | expect(Moonshot.config.extra_tags).to match(expected)
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/lib/moonshot/tools/asg_rollout_config.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | module Tools
5 | class ASGRolloutConfig
6 | attr_reader :pre_detach, :terminate_when, :terminate_when_timeout, :terminate
7 | attr_accessor :terminate_when_delay, :instance_health_delay
8 |
9 | def initialize
10 | @instance_health_delay = 2
11 | @terminate_when_delay = 1
12 | @terminate_when_timeout = 300
13 | @terminate = proc do |h|
14 | h.ec2_instance.terminate
15 | end
16 | end
17 |
18 | def pre_detach=(value)
19 | raise ArgumentError, 'pre_detach must be callable' unless value.respond_to?(:call)
20 |
21 | @pre_detach = value
22 | end
23 |
24 | def terminate_when=(value)
25 | raise ArgumentError, 'terminate_when must be callable' unless value.respond_to?(:call)
26 |
27 | @terminate_when = value
28 | end
29 |
30 | def terminate_when_timeout=(value)
31 | @terminate_when_timeout = Float(value)
32 | end
33 |
34 | def terminate=(value)
35 | raise ArgumentError, 'terminate must be callable' unless value.respond_to?(:call)
36 |
37 | @terminate = value
38 | end
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/lib/moonshot/tools/asg_rollout/instance_health.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | module Tools
5 | class ASGRollout
6 | class InstanceHealth
7 | attr_reader :asg_status, :elb_status
8 |
9 | VALID_ASG_IN_SERVICE_STATES = ['InService'].freeze
10 | VALID_ELB_IN_SERVICE_STATES = [nil, 'InService'].freeze
11 |
12 | VALID_ASG_OUT_OF_SERVICE_STATES = [nil, 'Missing', 'Detached'].freeze
13 | VALID_ELB_OUT_OF_SERVICE_STATES = [nil, 'Missing', 'OutOfService'].freeze
14 |
15 | def initialize(asg_status, elb_status)
16 | @asg_status = asg_status
17 | @elb_status = elb_status
18 | end
19 |
20 | def to_s
21 | result = "ASG:#{@asg_status}"
22 | result << "/ELB:#{@elb_status}" if @elb_status
23 | result
24 | end
25 |
26 | def in_service?
27 | VALID_ASG_IN_SERVICE_STATES.include?(@asg_status) &&
28 | VALID_ELB_IN_SERVICE_STATES.include?(@elb_status)
29 | end
30 |
31 | def out_of_service?
32 | VALID_ASG_OUT_OF_SERVICE_STATES.include?(@asg_status) &&
33 | VALID_ELB_OUT_OF_SERVICE_STATES.include?(@elb_status)
34 | end
35 | end
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/lib/moonshot/tools/asg_rollout/hook_exec_environment.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'moonshot/ssh_fork_executor'
4 |
5 | module Moonshot
6 | module Tools
7 | class ASGRollout
8 | # This object is passed into hooks defined in the ASGRollout
9 | # process, to give them access to instances and logging
10 | # facilities.
11 | class HookExecEnvironment
12 | attr_reader :instance_id
13 |
14 | def initialize(config, instance_id)
15 | @ilog = config.interactive_logger
16 | @command_builder = Moonshot::SSHCommandBuilder.new(config.ssh_config, instance_id)
17 | @instance_id = instance_id
18 | end
19 |
20 | def exec(cmd)
21 | cb = @command_builder.build(cmd)
22 | fe = SSHForkExecutor.new
23 | fe.run(cb.cmd)
24 | end
25 |
26 | def ec2
27 | Aws::EC2::Client.new
28 | end
29 |
30 | def ec2_instance
31 | res = Aws::EC2::Resource.new(client: ec2)
32 | res.instance(@instance_id)
33 | end
34 |
35 | def debug(msg)
36 | @ilog.debug(msg)
37 | end
38 |
39 | def info(msg)
40 | @ilog.info(msg)
41 | end
42 | end
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/puppet/aws-codedeploy/Rakefile:
--------------------------------------------------------------------------------
1 | require 'puppetlabs_spec_helper/rake_tasks'
2 | require 'puppet-lint/tasks/puppet-lint'
3 | require 'puppet-syntax/tasks/puppet-syntax'
4 |
5 | # These gems aren't always present, for instance
6 | # if run with --without development
7 | begin
8 | require 'puppet_blacksmith/rake_tasks'
9 | rescue LoadError
10 | end
11 |
12 | PuppetLint.configuration.relative = true
13 | PuppetLint.configuration.send("disable_80chars")
14 | PuppetLint.configuration.log_format = "%{path}:%{linenumber}:%{check}:%{KIND}:%{message}"
15 | PuppetLint.configuration.fail_on_warnings = true
16 |
17 | # Forsake support for Puppet 2.6.2 for the benefit of cleaner code.
18 | # http://puppet-lint.com/checks/class_parameter_defaults/
19 | PuppetLint.configuration.send('disable_class_parameter_defaults')
20 | # http://puppet-lint.com/checks/class_inherits_from_params_class/
21 | PuppetLint.configuration.send('disable_class_inherits_from_params_class')
22 |
23 | exclude_paths = [
24 | "pkg/**/*",
25 | "vendor/**/*",
26 | "spec/**/*",
27 | ]
28 | PuppetLint.configuration.ignore_paths = exclude_paths
29 | PuppetSyntax.exclude_paths = exclude_paths
30 |
31 | desc "Run syntax, lint, and spec tests."
32 | task :test => [
33 | :syntax,
34 | :lint,
35 | :spec,
36 | ]
37 |
--------------------------------------------------------------------------------
/lib/moonshot/ssh_target_selector.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | # Choose a publically accessible instance to run commands on, given a Moonshot::Stack.
5 | class SSHTargetSelector
6 | def initialize(stack, asg_name: nil)
7 | @asg_name = asg_name
8 | @stack = stack
9 | end
10 |
11 | def choose!
12 | groups = @stack.resources_of_type('AWS::AutoScaling::AutoScalingGroup')
13 |
14 | asg = if groups.count == 1
15 | groups.first
16 | elsif groups.count > 1
17 | unless @asg_name
18 | raise 'Multiple Auto Scaling Groups found in the stack. Please specify which '\
19 | 'one to SSH into using the --auto-scaling-group (-g) option.'
20 | end
21 | groups.detect { |x| x.logical_resource_id == @asg_name }
22 | end
23 | raise 'Failed to find the Auto Scaling Group.' unless asg
24 |
25 | Aws::AutoScaling::Client.new.describe_auto_scaling_groups(
26 | auto_scaling_group_names: [asg.physical_resource_id]
27 | ).auto_scaling_groups.first.instances.map(&:instance_id).first
28 | rescue StandardError => e
29 | raise "Failed to select an instance from the Auto Scaling Group: #{e.message}"
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/lib/moonshot/commands/update.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | module Commands
5 | class Update < Moonshot::Command
6 | include ParameterArguments
7 | include TagArguments
8 | include ShowAllEventsOption
9 | include ParentStackOption
10 |
11 | self.usage = 'update [options]'
12 | self.description = 'Update the CloudFormation stack within an environment.'
13 |
14 | def parser
15 | parser = super
16 |
17 | parser.on('--dry-run', TrueClass, 'Show the changes that would be applied, but do not execute them') do |v|
18 | @dry_run = v
19 | end
20 |
21 | parser.on('--force', '-f', TrueClass, 'Apply ChangeSet without confirmation') do |v|
22 | @force = v
23 | end
24 |
25 | parser.on('--refresh-parameters', TrueClass, 'Update parameters from parent stacks') do |v|
26 | @refresh_parameters = v
27 | end
28 |
29 | parser.on('--template-file=FILE', 'Override the path to the CloudFormation template.') do |v|
30 | Moonshot.config.template_file = v
31 | end
32 | end
33 |
34 | def execute
35 | @force = true unless Moonshot.config.interactive
36 | controller.update(dry_run: @dry_run, force: @force, refresh_parameters: @refresh_parameters)
37 | end
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/version-control/git/README.md:
--------------------------------------------------------------------------------
1 | Git Hooks
2 | =========
3 |
4 | The scripts here provide basic functionality to hook AWS CodeDeploy into git
5 | events. The primary script is provided as a `pre-push` hook, which executes the
6 | `aws deploy push` command for the local repository before git finishes pushing
7 | to the remote. It then starts a new deployment of the revision. Both the AWS
8 | CodeDeploy Application and Deployment Group must already exist.
9 |
10 | !!! CAUTION !!! Because this does an S3 upload on every push, you may incur S3 transfer charges.
11 |
12 | --Note, you need to make this script executable ( chmod +x pre-push ) after installing it in ./.git/hooks/pre-push
13 |
14 | No changes to the script itself should be required. Instead, it pulls the
15 | necessary information from git config. The required keys are
16 | `aws-codedeploy.application-name`, `aws-codedeploy.s3bucket`, and
17 | `aws-codedeploy.deployment-group`. They can be set with the following commands
18 | (replace values with your own):
19 |
20 | git config aws-codedeploy.application-name MyApplication
21 | git config aws-codedeploy.s3bucket MyS3Bucket
22 | git config aws-codedeploy.deployment-group MyDeploymentGroup
23 |
24 | The deployment created with this script will use the default deployment
25 | configuration for the configured deployment group.
26 |
--------------------------------------------------------------------------------
/lib/moonshot/commands/generate_template.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | module Commands
5 | class GenerateTemplate < Moonshot::Command
6 | self.usage = 'generate-template [options]'
7 | self.description = 'Processes an ERB formatted CloudFormation template.'
8 |
9 | def initialize(*)
10 | super
11 | @parameters = {}
12 | end
13 |
14 | def parser
15 | parser = super
16 |
17 | parser.on('--source SOURCE_FILE', 'The ERB template file.') do |v|
18 | @source = v
19 | end
20 |
21 | parser.on('--parameter KEY=VALUE', '-PKEY=VALUE',
22 | 'Specify Stack Parameter on the command line') do |v|
23 | data = v.split('=', 2)
24 | unless data.size == 2
25 | raise "Invalid parameter format '#{v}',"\
26 | 'expected KEY=VALUE (e.g. MyTemplateParameter=12)'
27 | end
28 |
29 | @parameters[data[0]] = data[1]
30 | end
31 |
32 | parser.on('--destination DESTINATION_FILE', 'Destionation file.') do |v|
33 | @destination = v
34 | end
35 | end
36 |
37 | def execute
38 | ::Moonshot::DynamicTemplate.new(
39 | source: @source,
40 | parameters: @parameters,
41 | destination: @destination
42 | ).process
43 | end
44 | end
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/lib/moonshot/doctor_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'colorize'
4 |
5 | module Moonshot
6 | DoctorCritical = Class.new(RuntimeError)
7 |
8 | #
9 | # A series of methods for adding "doctor" checks to a mechanism.
10 | #
11 | module DoctorHelper
12 | def doctor_hook
13 | run_all_checks
14 | end
15 |
16 | private
17 |
18 | def run_all_checks
19 | success = true
20 | puts
21 | puts self.class.name.split('::').last
22 | private_methods.each do |meth|
23 | send(meth) if meth =~ /^doctor_check_/
24 | rescue DoctorCritical
25 | # Stop running checks in this Mechanism.
26 | success = false
27 | break
28 | rescue StandardError => e
29 | success = false
30 | print ' ✗ '.red
31 | puts "Exception while running check: #{e.class}: #{e.message.lines.first}"
32 | break
33 | end
34 |
35 | success
36 | end
37 |
38 | def success(str)
39 | print ' ✓ '.green
40 | puts str
41 | end
42 |
43 | def warning(str, additional_info = nil)
44 | print ' ? '.yellow
45 | puts str
46 | additional_info&.lines&.each { |l| puts " #{l}" }
47 | end
48 |
49 | def critical(str, additional_info = nil)
50 | print ' ✗ '.red
51 | puts str
52 | additional_info&.lines&.each { |l| puts " #{l}" }
53 | raise DoctorCritical
54 | end
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/lib/moonshot/stack_lister.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | # The StackLister is world renoun for it's ability to list stacks.
5 | class StackLister
6 | EnvironmentDescription = Struct.new(:name, :creation_time, :status)
7 | include CredsHelper
8 |
9 | def initialize(app_name)
10 | @app_name = app_name
11 | end
12 |
13 | # rubocop:disable Metrics/AbcSize
14 | def list
15 | result = []
16 | next_token = nil
17 | loop do
18 | resp = cf_client.describe_stacks(next_token:)
19 | resp.stacks.each do |stack|
20 | app_tag = stack.tags.find { |t| t.key == 'moonshot_application' }
21 | env_tag = stack.tags.find { |t| t.key == 'moonshot_environment' }
22 | legacy_tag = stack.tags.find { |t| t.key == 'ah_stage' }
23 |
24 | if app_tag && app_tag.value == Moonshot.config.app_name
25 | result <<
26 | EnvironmentDescription.new(env_tag.value, stack.creation_time, stack.stack_status)
27 | elsif legacy_tag&.value&.start_with?(Moonshot.config.app_name)
28 | result <<
29 | EnvironmentDescription.new(legacy_tag.value, stack.creation_time, stack.stack_status)
30 | end
31 | end
32 | break unless resp.next_token
33 |
34 | next_token = resp.next_token
35 | end
36 | result.sort_by(&:name)
37 | end
38 | # rubocop:enable Metrics/AbcSize
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/puppet/aws-codedeploy/spec/classes/example_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe 'codedeploy' do
4 | let(:facts) {{
5 | :osfamily => 'RedHat',
6 | }}
7 | describe "codedeploy class without any parameters" do
8 |
9 | it { should compile.with_all_deps }
10 |
11 | it { should contain_class('codedeploy::params') }
12 | it { should contain_class('codedeploy::install') }
13 | it { should contain_class('codedeploy::service').that_subscribes_to('codedeploy::install') }
14 |
15 | it { should contain_service('codedeploy-agent') }
16 | it { should contain_package('codedeploy-agent')
17 | .with_ensure('present')
18 | .with_source('https://s3.amazonaws.com/aws-codedeploy-us-east-1/latest/codedeploy-agent.noarch.rpm')
19 | }
20 | end
21 |
22 | describe "codedeploy class with custom package source" do
23 | let(:params) {{ :package_source => 'https://example.com/package.rpm' }}
24 | it { should contain_package('codedeploy-agent').with_source('https://example.com/package.rpm') }
25 | end
26 |
27 | context 'unsupported operating system' do
28 | describe 'codedeploy class without any parameters on Solaris/Nexenta' do
29 | let(:facts) {{
30 | :osfamily => 'Solaris',
31 | :operatingsystem => 'Nexenta',
32 | }}
33 |
34 | it { expect { should contain_package('codedeploy') }.to raise_error(Puppet::Error, /Nexenta not supported/) }
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/docs/plugins/dynamic_template.md:
--------------------------------------------------------------------------------
1 | # Dynamic template plugin
2 |
3 | ## Overview
4 | The dynamic template plugin can be used to generate CloudFormation plugins
5 | on-the-fly. This is useful for properties that can't be set via parameters,
6 | like "DeletionPolicy". The plugin is using ERB templating and it's also
7 | available in the form of a CLI.
8 |
9 | ## Usage and configuration
10 |
11 | The plugin accepts three configuration parameters:
12 | - `source`: path to the source template file
13 | - `parameters`: parameters of your choice (see below - [parameters](#parameters))
14 | - `destination`: the target file path
15 |
16 | Your template must be conforming the ERB standards. You can use both
17 | `<%= deletion_policy %>` and `<%= @deletion_policy %>` formats in your template.
18 |
19 | ## Example
20 | ```ruby
21 | Moonshot::Plugins::DynamicTemplate.new(
22 | source: 'cloud_formation/cdb-api.erb',
23 | parameters: { deletion_policy: 'Retain' },
24 | destination: 'cloud_formation/cdb-api.json'
25 | )
26 | ```
27 |
28 | ## Parameters
29 |
30 | The 'hack' with lambdas created for the cases when you need to read
31 | CLI parameters before running the plugin.
32 |
33 | Parameters could be described in two ways:
34 | * key-value pairs
35 | ```ruby
36 | parameters: { deletion_policy: 'Retain' }
37 | ```
38 |
39 | * lambdas
40 | ```ruby
41 | parameters = -> environment_name {
42 | environment = environment_name =~ prod_regexp ? 'prod' : 'dev'
43 | parameters = YAML.load_file("cloud_formation/parameters/#{environment}.yml")
44 | }
45 | ```
46 |
--------------------------------------------------------------------------------
/lib/plugins/encrypted_parameters/kms_key.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative '../../moonshot/stack'
4 |
5 | module Moonshot
6 | module Plugins
7 | class EncryptedParameters
8 | # Class that manages KMS keys in AWS.
9 | class KmsKey
10 | attr_reader :arn
11 |
12 | class << self
13 | def create
14 | standard_tags = stack_tags
15 | resp = Aws::KMS::Client.new.create_key({
16 | tags: standard_tags # An array of tags.
17 | })
18 | arn = resp.key_metadata.arn
19 | new(arn)
20 | end
21 |
22 | def stack_tags
23 | tags = Moonshot::Stack.make_tags(Moonshot.config)
24 | tags.map { |tag| { tag_key: tag[:key], tag_value: tag[:value] } }
25 | end
26 | end
27 |
28 | def initialize(arn)
29 | @arn = arn
30 | @kms_client = Aws::KMS::Client.new
31 | end
32 |
33 | def update
34 | standard_tags = self.class.stack_tags
35 | @kms_client.tag_resource({
36 | key_id: @arn, # arn of the CMK being tagged
37 | tags: standard_tags # An array of tags.
38 | })
39 | end
40 |
41 | def delete
42 | @kms_client.schedule_key_deletion(key_id: @arn, pending_window_in_days: 7)
43 | end
44 | end
45 | end
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/sample/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | activesupport (4.2.6)
5 | i18n (~> 0.7)
6 | json (~> 1.7, >= 1.7.7)
7 | minitest (~> 5.1)
8 | thread_safe (~> 0.3, >= 0.3.4)
9 | tzinfo (~> 1.1)
10 | aws-sdk (2.2.36)
11 | aws-sdk-resources (= 2.2.36)
12 | aws-sdk-core (2.2.36)
13 | jmespath (~> 1.0)
14 | aws-sdk-resources (2.2.36)
15 | aws-sdk-core (= 2.2.36)
16 | colorize (0.7.7)
17 | github-markup (1.4.0)
18 | highline (1.7.8)
19 | i18n (0.7.0)
20 | interactive-logger (0.1.1)
21 | colorize (~> 0.7.7)
22 | ruby-duration (~> 3.2, >= 3.2.1)
23 | iso8601 (0.9.0)
24 | jmespath (1.2.4)
25 | json_pure (>= 1.8.1)
26 | json (1.8.3)
27 | json_pure (1.8.3)
28 | minitest (5.8.4)
29 | moonshot (0.7.0)
30 | aws-sdk (~> 2.2.0)
31 | colorize
32 | highline (~> 1.7.2)
33 | interactive-logger (~> 0.1.1)
34 | rotp (~> 2.1.1)
35 | ruby-duration (~> 3.2.3)
36 | semantic
37 | thor (~> 0.19.1)
38 | vandamme
39 | redcarpet (3.3.4)
40 | rotp (2.1.2)
41 | ruby-duration (3.2.3)
42 | activesupport (>= 3.0.0)
43 | i18n
44 | iso8601
45 | semantic (1.4.1)
46 | thor (0.19.1)
47 | thread_safe (0.3.5)
48 | tzinfo (1.2.2)
49 | thread_safe (~> 0.1)
50 | vandamme (0.0.11)
51 | github-markup (~> 1.3)
52 | redcarpet (~> 3.3.2)
53 |
54 | PLATFORMS
55 | ruby
56 |
57 | DEPENDENCIES
58 | moonshot (~> 0.7.0)
59 |
60 | BUNDLED WITH
61 | 1.11.2
--------------------------------------------------------------------------------
/lib/moonshot/commands/create.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative 'parameter_arguments'
4 | require_relative 'tag_arguments'
5 | require_relative 'show_all_events_option'
6 | require_relative 'parent_stack_option'
7 |
8 | module Moonshot
9 | module Commands
10 | class Create < Moonshot::Command
11 | include ParameterArguments
12 | include TagArguments
13 | include ShowAllEventsOption
14 | include ParentStackOption
15 |
16 | self.usage = 'create [options]'
17 | self.description = 'Create a new environment'
18 |
19 | attr_reader :version, :deploy
20 |
21 | def parser
22 | @deploy = true
23 |
24 | parser = super
25 | desc = 'Choose if code should be deployed immediately after the stack is created'
26 | parser.on('-d', '--[no-]deploy', TrueClass, desc) do |v|
27 | @deploy = v
28 | end
29 |
30 | desc = 'Version for initial deployment. If unset, a new development build is created from the local directory'
31 | parser.on('--version VERSION_NAME', desc) do |v|
32 | @version = v
33 | end
34 |
35 | parser.on('--template-file=FILE', 'Override the path to the CloudFormation template.') do |v|
36 | Moonshot.config.template_file = v
37 | end
38 | end
39 |
40 | def execute
41 | controller.create
42 |
43 | if @deploy && @version.nil?
44 | controller.push
45 | elsif @deploy
46 | controller.deploy_version(@version)
47 | end
48 | end
49 | end
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/lib/moonshot/stack_parameter.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | class StackParameter
5 | attr_reader :name, :default, :description
6 |
7 | def initialize(name, default: nil, use_previous: false, description: '')
8 | @default = default
9 | @description = description
10 | @name = name
11 | @use_previous = use_previous
12 | @value = nil
13 | end
14 |
15 | # Does this Stack Parameter have a default value that will be used?
16 | def default?
17 | !@default.nil?
18 | end
19 |
20 | def use_previous?
21 | @use_previous ? true : false
22 | end
23 |
24 | # Has the user provided a value for this parameter?
25 | def set?
26 | !@value.nil?
27 | end
28 |
29 | def set(value)
30 | @value = value
31 | @use_previous = false
32 | end
33 |
34 | def use_previous!(value)
35 | raise "Value already set for StackParameter #{@name}, cannot use previous value!" if @value
36 |
37 | # Make the current value available to plugins.
38 | @value = value
39 | @use_previous = true
40 | end
41 |
42 | def value
43 | raise "No value set and no default for StackParameter #{@name}!" unless @value || default?
44 |
45 | @value || default
46 | end
47 |
48 | def to_cf
49 | result = { parameter_key: @name }
50 |
51 | if use_previous?
52 | result[:use_previous_value] = true
53 | else
54 | result[:parameter_value] = value
55 | end
56 |
57 | result
58 | end
59 | end
60 | end
61 |
--------------------------------------------------------------------------------
/lib/moonshot/parameter_collection.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | # A Rigid Hash-like structure that only accepts manipulation of
5 | # parameters defined in the Stack template. Anything else results in
6 | # an exception.
7 | class ParameterCollection
8 | extend Forwardable
9 |
10 | def_delegators :@hash, :key?, :fetch, :[], :keys, :values
11 | attr_reader :hash
12 |
13 | def self.from_template(template)
14 | obj = new
15 |
16 | template.parameters.each do |stack_parameter|
17 | obj.add(stack_parameter)
18 | end
19 |
20 | obj
21 | end
22 |
23 | def initialize
24 | @hash = {}
25 | end
26 |
27 | def []=(key, value)
28 | raise "Invalid StackParameter #{key}!" unless @hash.key?(key)
29 |
30 | @hash[key].set(value)
31 | end
32 |
33 | def add(parameter)
34 | raise ArgumentError, 'Can only add StackParameters!' unless parameter.is_a?(StackParameter)
35 |
36 | @hash[parameter.name] = parameter
37 | end
38 |
39 | # What parameters are missing for a CreateStack call, where
40 | # UsePreviousValue has no meaning.
41 | def missing_for_create
42 | # If we haven't set a value, and there is no default, we can't
43 | # create the stack.
44 | @hash.values.select { |v| !v.set? && !v.default? }
45 | end
46 |
47 | def missing_for_update
48 | # If we don't have a previous value to use, we haven't set a
49 | # value, and there is no default, we can't update a stack.
50 | @hash.values.select { |v| !v.set? && !v.default? && !v.use_previous? }
51 | end
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/docs/user-guide/custom-commands.md:
--------------------------------------------------------------------------------
1 | # Custom Commands
2 |
3 | One powerful feature of Moonshot is the ability to add custom
4 | project-specific commands. You can hook into many of the features
5 | provided by the `Moonshot::Controller` and `Moonshot::Stack`
6 | interface, and make native calls to AWS using the AWS SDK for Ruby
7 | v2.
8 |
9 | To do this, place a file in `moonshot/cli_extensions/my_command.rb`
10 | that extends `Moonshot::Command` (or `Moonshot::SSHCommand`, if you
11 | want to use the Moonshot SSH interface).
12 |
13 | ## Example
14 |
15 | ```ruby
16 | # moonshot/cli_extensions/get_elb_address.rb
17 | class GetElbAddress < Moonshot::Command
18 | self.description = 'Display the ELBs external hostname.'
19 | self.usage = 'get-elb-address'
20 |
21 | def execute
22 | puts controller.stack.outputs['APIElasticLoadBalancerDNS']
23 | end
24 | end
25 | ```
26 |
27 | ## Account Restrictions
28 |
29 | If you have a development- or production-only command you want
30 | restricted to running in appropriately labelled accounts, you can use
31 | the `only_in_account` option. This can be either a single string, or
32 | an array of strings. For example:
33 |
34 | ```ruby
35 | # moonshot/cli_extensions/drop_database.rb
36 | class DropDatabase < Moonshot::Command
37 | self.description = 'Delete all the data (development only!)'
38 | self.usage = 'drop-database'
39 | self.only_in_account = 'company-dev'
40 |
41 | def execute
42 | # ...
43 | end
44 | end
45 | ```
46 |
47 | If the IAM account alias for the current AWS account does not exactly
48 | match, the user will receive an error:
49 |
50 | ```
51 | 'drop-database' can only be run in the following accounts:
52 | - company-dev
53 | ```
54 |
--------------------------------------------------------------------------------
/lib/moonshot/stack_events_poller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | # The StackEventsPoller queries DescribeStackEvents every time #latest_events
5 | # is invoked, filtering out events that have already been returned. It can
6 | # also, optionally, filter all non-error events (@see #show_errors_only).
7 | class StackEventsPoller
8 | def initialize(cf_client, stack_name)
9 | @cf_client = cf_client
10 | @stack_name = stack_name
11 |
12 | # Start showing events from now.
13 | @last_time = Time.now
14 | end
15 |
16 | def show_only_errors
17 | @errors_only = true
18 | end
19 |
20 | # Build a list of events that have occurred since the last call to this
21 | # method.
22 | #
23 | # @return [Array]
24 | def latest_events
25 | events = get_stack_events.select do |event|
26 | event.timestamp > @last_time
27 | end
28 |
29 | @last_time = Time.now
30 |
31 | filter_events(events.sort_by(&:timestamp))
32 | end
33 |
34 | def filter_events(events)
35 | if @errors_only
36 | events.select do |event|
37 | %w[CREATE_FAILED UPDATE_FAILED DELETE_FAILED].include?(event.resource_status)
38 | end
39 | else
40 | events
41 | end
42 | end
43 |
44 | def get_stack_events(token = nil)
45 | opts = {
46 | stack_name: @stack_name
47 | }
48 |
49 | opts[:next_token] = token if token
50 |
51 | result = @cf_client.describe_stack_events(**opts)
52 | events = result.stack_events
53 | events += get_stack_events(result.next_token) if result.next_token
54 |
55 | events
56 | end
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/puppet/aws-codedeploy/README.md:
--------------------------------------------------------------------------------
1 | AWS CodeDeploy Puppet Module
2 | ============================
3 |
4 | #### Table of Contents
5 |
6 | 1. [Overview](#overview)
7 | 2. [Module Description - What the module does and why it is useful](#module-description)
8 | 3. [Usage - Configuration options and additional functionality](#usage)
9 | 4. [Reference - An under-the-hood peek at what the module is doing and how](#reference)
10 | 5. [Limitations - OS compatibility, etc.](#limitations)
11 | 6. [Development - Guide for contributing to the module](#development)
12 |
13 | ## Overview
14 |
15 | This module installs the AWS CodeDeploy Agent.
16 |
17 | ## Module Description
18 |
19 | The AWS CodeDeploy agent will copy packages to an EC2 instance from a configured S3 bucket. This
20 | module should be used on an EC2 instance that already has Puppet installed. At this time, the only
21 | supported operating systems are RedHat variants and Amazon Linux, through the installation of an RPM
22 | package.
23 |
24 | ## Usage
25 |
26 | Put the classes, types, and resources for customizing, configuring, and doing the fancy stuff with
27 | your module here.
28 |
29 |
30 | ```puppet
31 | codedeploy {}
32 | ```
33 |
34 | ##Reference
35 |
36 | ###Class: codedeploy
37 |
38 | ####Parameters
39 |
40 | * **package_source** - The url to install the CodeDeploy package from. Defaults to
41 | `https://s3.amazonaws.com/aws-codedeploy-us-east-1/latest/codedeploy-agent.noarch.rpm`
42 |
43 | ##Limitations
44 |
45 | * This module requires a RedHat or Amazon Linux based operating system that is capable of
46 | installing RPM packages.
47 |
48 | ##Development
49 |
50 | Please see our [GitHub repositories](https://github.com/awslabs)
51 |
--------------------------------------------------------------------------------
/docs/mechanisms/build.md:
--------------------------------------------------------------------------------
1 | # BuildMechanism
2 |
3 | ## Script
4 |
5 | The Script BuildMechanism will execute a local shell script, with certain
6 | expectations. The script will run with some environment variables:
7 |
8 | - `VERSION`: The named version string passed to `build-version`.
9 | - `OUTPUT_FILE`: The file that the script is expected to produce.
10 |
11 | If the file is not created by the build script, deployment will
12 | fail. Otherwise, the output file will be uploaded using the
13 | ArtifactRepository.
14 |
15 | Sample Usage
16 | ```ruby
17 | Moonshot.config do |c|
18 | c.build_mechanism = Script.new('bin/build.sh')
19 | ...
20 | ```
21 |
22 | ## GithubRelease
23 |
24 | A build mechanism that creates a tag and GitHub release. Could be used
25 | to delegate other building steps after GitHub release is created.
26 |
27 | Sample Usage
28 |
29 | ```ruby
30 | Moonshot.config do |c|
31 | c.build_mechanism = GithubRelease.new
32 | ...
33 | ```
34 |
35 | **skip_ci_status** is an optional flag. It would allow us to skip checks
36 | on the commit's CI job status. Without this option, the GithubRelease mechanism will wait until the build is finished.
37 |
38 | Sample Usage
39 |
40 | ```ruby
41 | Moonshot.config do |c|
42 | c.build_mechanism = GithubRelease.new
43 | ...
44 | ```
45 | Also a command-line option is available to override this value.
46 |
47 | Usage: moonshot build VERSION
48 | -v, --[no-]verbose Show debug logging
49 | -s, --skip-ci-status Skip checks on CI jobs
50 | -n, --environment=NAME Which environment to operate on.
51 | --[no-]interactive-logger Enable or disable fancy logging
52 | -F, --foo
53 |
54 | ## Version Proxy
55 |
56 | @Todo Document and clarify the use-case of the Version Proxy.
57 |
--------------------------------------------------------------------------------
/moonshot.gemspec:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Gem::Specification.new do |s|
4 | s.name = 'moonshot'
5 | s.version = '3.0.5'
6 | s.licenses = ['Apache-2.0']
7 | s.summary = 'A library and CLI tool for launching services into AWS'
8 | s.description = 'A library and CLI tool for launching services into AWS.'
9 | s.authors = [
10 | 'Cloud Engineering '
11 | ]
12 | s.email = 'engineering@acquia.com'
13 | s.files = Dir['lib/**/*.rb'] + Dir['lib/default/**/*'] + Dir['bin/*']
14 | s.bindir = 'bin'
15 | s.executables = ['moonshot']
16 | s.homepage = 'https://github.com/acquia/moonshot'
17 |
18 | s.required_ruby_version = '>= 3.3.4'
19 |
20 | s.add_dependency('aws-sdk-autoscaling', '~> 1.5')
21 | s.add_dependency('aws-sdk-cloudformation', '~> 1.4')
22 | s.add_dependency('aws-sdk-codedeploy', '~> 1.5')
23 | s.add_dependency('aws-sdk-ec2', '~> 1.34')
24 | s.add_dependency('aws-sdk-elasticloadbalancing', '~> 1.3')
25 | s.add_dependency('aws-sdk-iam', '~> 1.4')
26 | s.add_dependency('aws-sdk-s3', '~> 1.12')
27 |
28 | s.add_dependency('activesupport')
29 | s.add_dependency('colorize')
30 | s.add_dependency('faraday', '~> 1.0')
31 | s.add_dependency('faraday-rack', '~> 1.0.0')
32 | s.add_dependency('faraday-retry', '~> 1.0')
33 | s.add_dependency('highline')
34 | s.add_dependency('interactive-logger')
35 | s.add_dependency('pry')
36 | s.add_dependency('require_all')
37 | s.add_dependency('retriable')
38 | s.add_dependency('rexml')
39 | s.add_dependency('rotp')
40 | s.add_dependency('ruby-duration')
41 | s.add_dependency('semantic')
42 | s.add_dependency('thor')
43 |
44 | s.add_development_dependency('fakefs')
45 | s.add_development_dependency('rspec')
46 | s.add_development_dependency('simplecov')
47 | end
48 |
--------------------------------------------------------------------------------
/spec/moonshot/plugins_spec.rb:
--------------------------------------------------------------------------------
1 | class MockPlugin
2 | def pre_create
3 | end
4 |
5 | def post_create
6 | end
7 | end
8 |
9 | describe 'Plugins support' do
10 | let(:plugin1) { MockPlugin.new }
11 | let(:plugin2) { MockPlugin.new }
12 |
13 | let(:stack) { instance_double('Moonshot::Stack') }
14 |
15 | subject do
16 | config = Moonshot::ControllerConfig.new
17 | config.app_name = 'my-app'
18 | config.plugins = [plugin1, plugin2]
19 |
20 | Moonshot::Controller.new(config)
21 | end
22 |
23 | before(:each) do
24 | expect(Moonshot::Stack).to receive(:new).and_return(stack)
25 | template = Moonshot::YamlStackTemplate.new(fixture_path('empty1.yml'))
26 | allow(stack).to receive(:template).and_return(template)
27 | allow(stack).to receive(:parameters).and_return({})
28 | end
29 |
30 | it 'calls defined methods on plugins in order, providing them with a Moonshot::Resources' do
31 | expect(plugin1).to receive(:pre_create).with(an_instance_of(Moonshot::Resources)).ordered
32 | expect(plugin2).to receive(:pre_create).with(an_instance_of(Moonshot::Resources)).ordered
33 | expect(stack).to receive(:create).ordered.and_return(true)
34 | expect(plugin1).to receive(:post_create).with(an_instance_of(Moonshot::Resources)).ordered
35 | expect(plugin2).to receive(:post_create).with(an_instance_of(Moonshot::Resources)).ordered
36 |
37 | subject.create
38 | end
39 |
40 | it "doesn't call an undefined method" do
41 | expect(stack).to receive(:delete).and_return(true)
42 |
43 | # The assertion here is that calling MockPlugin#pre_delete would cause an
44 | # exception. Using an expect().not_to receive() changes the behavior of
45 | # #respond_to?, so we can't write that expectation.
46 | expect { subject.delete }
47 | .not_to raise_error
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/lib/moonshot/command.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'optparse'
4 |
5 | module Moonshot
6 | # A Command that is automatically registered with the Moonshot::CommandLine
7 | class Command
8 | module ClassMethods
9 | # TODO: Can we auto-generate usage for commands with no positional arguments, at least?
10 | attr_accessor :usage, :description, :only_in_account
11 | end
12 |
13 | def self.inherited(base)
14 | Moonshot::CommandLine.register(base)
15 | base.extend(ClassMethods)
16 | end
17 |
18 | def parser
19 | @use_interactive_logger = true
20 |
21 | OptionParser.new do |o|
22 | o.banner = "Usage: moonshot #{self.class.usage}"
23 |
24 | o.on('-v', '--[no-]verbose', 'Show debug logging') do |v|
25 | Moonshot.config.interactive_logger = InteractiveLogger.new(debug: true) if v
26 | end
27 |
28 | o.on('-nNAME', '--environment=NAME', 'Which environment to operate on.') do |v|
29 | Moonshot.config.environment_name = v
30 | end
31 |
32 | o.on('--[no-]interactive-logger', TrueClass, 'Enable or disable fancy logging') do |v|
33 | @use_interactive_logger = v
34 | end
35 | end
36 | end
37 |
38 | private
39 |
40 | # Build a Moonshot::Controller from the CLI options.
41 | def controller
42 | config = Moonshot.config
43 | config.update_for_account!
44 | controller = Moonshot::Controller.new(config)
45 |
46 | # Degrade to a more compatible logger if the terminal seems outdated,
47 | # or at the users request.
48 | if !$stdout.isatty || !@use_interactive_logger
49 | log = Logger.new($stdout)
50 | controller.config.interactive_logger = InteractiveLoggerProxy.new(log)
51 | end
52 |
53 | controller
54 | end
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/spec/moonshot/commands/create_spec.rb:
--------------------------------------------------------------------------------
1 | describe Moonshot::Commands::Create do
2 | before(:each) do
3 | Moonshot.config = Moonshot::ControllerConfig.new
4 | end
5 |
6 | tests = {
7 | %w(-P Key=Value -P OtherKey=OtherValue) => { 'Key' => 'Value', 'OtherKey' => 'OtherValue' },
8 | %w(-PKey=ValueWith=Equals) => { 'Key' => 'ValueWith=Equals' }
9 | }
10 |
11 | tests.each do |input, expected|
12 | it "Should process #{input} correctly" do
13 | op = subject.parser
14 | op.parse(input)
15 | expect(Moonshot.config.parameter_overrides).to match(expected)
16 | end
17 | end
18 |
19 | extra_tags = {
20 | %w(-T Key=Value -T OtherKey=OtherValue) =>
21 | [{ key: 'Key', value: 'Value' }, { key: 'OtherKey', value: 'OtherValue' }],
22 | %w(-TKey=ValueWith=Equals) => [{ key: 'Key', value: 'ValueWith=Equals' }],
23 | %w(--tag Key=Value --tag OtherKey=OtherValue) =>
24 | [{ key: 'Key', value: 'Value' }, { key: 'OtherKey', value: 'OtherValue' }]
25 | }
26 |
27 | extra_tags.each do |input, expected|
28 | it "Should process #{input} correctly" do
29 | op = subject.parser
30 | op.parse(input)
31 | expect(Moonshot.config.extra_tags).to match(expected)
32 | end
33 | end
34 |
35 | it 'should handle version and deploy correctly' do
36 | op = subject.parser
37 | op.parse(%w(--version 1.2.3 --no-deploy -P Key=Value))
38 | expect(subject.version).to eq('1.2.3')
39 | expect(subject.deploy).to eq(false)
40 | expect(Moonshot.config.parameter_overrides).to match('Key' => 'Value')
41 | end
42 |
43 | it 'should process multiple parent stacks' do
44 | op = subject.parser
45 | op.parse(%w(--parents parent1,parent2,parent3 --no-deploy))
46 | expect(subject.deploy).to eq(false)
47 | expect(Moonshot.config.parent_stacks.count).to be(3)
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/lib/moonshot/unicode_table.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'colorize'
4 |
5 | module Moonshot
6 | # A class for drawing hierarchical information using unicode lines.
7 | class UnicodeTable
8 | def initialize(name)
9 | @name = name
10 | @lines = []
11 | @children = []
12 | end
13 |
14 | def add_leaf(name)
15 | new_leaf = UnicodeTable.new(name)
16 | @children << new_leaf
17 | new_leaf
18 | end
19 |
20 | def add_line(line)
21 | @lines << line
22 | self
23 | end
24 |
25 | def add_table(table)
26 | # Calculate widths
27 | widths = []
28 | table.each do |line|
29 | line.each_with_index do |col, i|
30 | col = '?' unless col.respond_to?(:length)
31 | widths[i] = [widths[i] || 0, col.length].max
32 | end
33 | end
34 |
35 | format = widths.collect { |n| "%-#{n}s" }.join(' ') << "\n"
36 | table.each { |line| add_line(format(format, *line)) }
37 | end
38 |
39 | def draw(depth = 1, first = true)
40 | space = ' '
41 | pipe = '|'
42 | print first ? '┌' : '├'
43 | print '─' * depth
44 | puts "#{space}" << @name.light_black # rubocop:disable Style/RedundantInterpolation
45 | @lines = [''] + @lines + ['']
46 | @lines.each do |line|
47 | puts "#{pipe}" << (' ' * depth) << line # rubocop:disable Style/RedundantInterpolation
48 | end
49 | @children.each do |child|
50 | child.draw(depth + 1, false)
51 | end
52 | end
53 |
54 | # Draw all children at the same level, for having multiple top-level
55 | # peer leaves.
56 | def draw_children
57 | first = true
58 | @children.each do |child|
59 | child.draw(1, first)
60 | first = false
61 | end
62 | puts '└──'
63 | end
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/lib/plugins/code_deploy_setup.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | module Plugins
5 | # Plugin to ensure CodeDeploy has all necessary S3 buckets created.
6 | # Defaults to using ENV['AWS_REGION'] as default region if not configured.
7 | class CodeDeploySetup
8 | include Moonshot::CredsHelper
9 |
10 | attr_reader :name, :prefix, :regions
11 |
12 | def initialize(name, prefix: '', regions: [ENV['AWS_REGION']])
13 | @regions = regions.reject(&:nil?)
14 | if @regions.empty?
15 | raise ArgumentError, 'CodeDeploySetup requires at least one region.' \
16 | ' Set regions argument or ENV[\'AWS_REGION\'].'
17 | end
18 |
19 | @name = name
20 | @prefix = prefix
21 | end
22 |
23 | def bucket_name(region = ENV['AWS_REGION'])
24 | if ENV.key?('S3_BUCKET')
25 | ENV['S3_BUCKET']
26 | else
27 | "#{@name}-#{region}"
28 | end
29 | end
30 |
31 | def bucket_prefix
32 | @prefix.empty? ? '' : "#{@prefix}/"
33 | end
34 |
35 | # Create an S3 bucket in each supported region for CodeDeploy
36 | def setup_code_deploy_s3_buckets
37 | @regions.uniq.each do |region|
38 | client = s3_client(region:)
39 | name = bucket_name(region)
40 | bucket = Aws::S3::Bucket.new(
41 | name,
42 | client:
43 | )
44 | bucket.create unless bucket.exists?
45 | end
46 | end
47 |
48 | # Hook entry points to ensure S3 Buckets are available for CodeDeploy
49 | def run_hook(_resources)
50 | setup_code_deploy_s3_buckets
51 | end
52 |
53 | # Moonshot hooks to trigger this plugin
54 | alias pre_create run_hook
55 | alias pre_deploy run_hook
56 | end
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/conf-mgmt/chef/aws-codedeploy-agent/README.md:
--------------------------------------------------------------------------------
1 | Installing the AWS CodeDeploy Agent with Chef
2 | =============================================
3 |
4 | In the previous post, we learned how to use the power of AWS CodeDeploy to orchestrate chef-solo. It took
5 | the perspective of having half of your dependencies already installed – namely, the CodeDeploy agent. For
6 | this post, we'll look at it from a different angle: the CodeDeploy host agent isn't installed yet, but you
7 | have a pre-existing Chef environment running on Amazon EC2 instances.
8 |
9 | Setup and Preconditions
10 | -----------------------
11 |
12 | The post below makes a few assumptions about your environment that may or may not be true. First and
13 | foremost is that you have a working Chef environment. We'll assume that you've worked through your
14 | own workflow for managing that environment and your chef-repo. If you are still new to Chef, their
15 | documentation has a lot of very helpful information: [http://docs.getchef.com](http://docs.getchef.com)
16 |
17 | AWS CodeDeploy Host Agent Cookbook
18 | ----------------------------------
19 |
20 | We've built a custom Chef cookbook to help ease the process of installing the CodeDeploy agent. You
21 | can download that cookbook
22 | [here](https://github.com/awslabs/aws-codedeploy-samples/tree/master/conf-mgmt/chef/aws-codedeploy-agent/cookbooks/codedeploy-agent/recipes).
23 | To install the CodeDeploy agent, simply download the linked archive, copy the codedeploy-agent
24 | directory into your chef-repo, and add `recipe[codedeploy-agent]` to your run list. If you just want
25 | to test this out with a chef-solo instance, we've included a sample configuration for you.
26 |
27 | The cookbook has three simple steps:
28 |
29 | 1. Download the package for the CodeDeploy host agent.
30 | 1. Install the agent.
31 | 1. Start the agent.
32 |
--------------------------------------------------------------------------------
/lib/moonshot/build_mechanism/version_proxy.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'forwardable'
4 | require 'semantic'
5 |
6 | # This proxies build request do different mechanisms. One for semver compliant
7 | # releases and another for everything else.
8 | class Moonshot::BuildMechanism::VersionProxy
9 | extend Forwardable
10 | include Moonshot::ResourcesHelper
11 |
12 | def_delegator :@active, :output_file
13 |
14 | def initialize(release:, dev:)
15 | @release = release
16 | @dev = dev
17 | end
18 |
19 | def doctor_hook
20 | @release.doctor_hook
21 | @dev.doctor_hook
22 | end
23 |
24 | def resources=(r)
25 | super
26 | @release.resources = r
27 | @dev.resources = r
28 | end
29 |
30 | def pre_build_hook(version)
31 | active(version).pre_build_hook(version)
32 | end
33 |
34 | def build_hook(version)
35 | active(version).build_hook(version)
36 | end
37 |
38 | def post_build_hook(version)
39 | active(version).post_build_hook(version)
40 | end
41 |
42 | def build_cli_hook(parser)
43 | # Expose any command line arguments provided by the build mechanisms. We
44 | # don't know the version at this point, so we can't call the hook on only
45 | # the one we're going to use, which may result in options being exposed that
46 | # are only applicable for one of the build mechanisms.
47 | parser = @release.build_cli_hook(parser) if @release.respond_to?(:build_cli_hook)
48 | parser = @dev.build_cli_hook(parser) if @dev.respond_to?(:build_cli_hook)
49 |
50 | parser
51 | end
52 |
53 | private
54 |
55 | def active(version)
56 | @active = if release?(version)
57 | @release
58 | else
59 | @dev
60 | end
61 | end
62 |
63 | def release?(version)
64 | ::Semantic::Version.new(version)
65 | rescue ArgumentError
66 | false
67 | end
68 | end
69 |
--------------------------------------------------------------------------------
/docs/user-guide/include_in_your_project.md:
--------------------------------------------------------------------------------
1 | # Including Moonshot-based tooling in your project
2 |
3 | ## Create a Moonfile.rb
4 |
5 | (TODO: Implement `moonshot init`)
6 |
7 | In the root of your project, create a file called `Moonfile.rb` with
8 | the following contents:
9 |
10 | ```ruby
11 | Moonshot.config do |c|
12 | c.application_name = 'my-service'
13 | c.artifact_repository = S3Bucket.new('my-service-builds')
14 | c.build_mechanism = Script.new('build/script.sh')
15 | c.deployment_mechanism = CodeDeploy.new(asg: 'AutoScalingGroup')
16 | end
17 | ```
18 |
19 | This example assumes:
20 | - You have a CloudFormation JSON template in folder called "moonshot/template.json".
21 | - You have an S3 bucket called "my-service-builds".
22 | - You have a script in "script/build.sh" that will build a tarball output.tar.gz.
23 | - You have a working CodeDeploy setup, including the CodeDeployRole.
24 |
25 | If all that is true, you can now deploy your software to a new stack with:
26 | ```
27 | $ moonshot create
28 | ```
29 |
30 | By default, you'll get a development environment named `my-service-dev-giraffe`,
31 | where `giraffe` is your username. If you want to provision test or production
32 | named environment, use:
33 | ```
34 | $ moonshot create -n my-service-staging
35 | $ moonshot create -n my-service-production
36 | ```
37 |
38 | By default, create launches the stack and deploys code. If you want to only
39 | create the stack and not deploy code, use:
40 | ```
41 | $ moonshot create --no-deploy
42 | ```
43 |
44 | If you make changes to your application and want to release a development build
45 | to your stack, run:
46 | ```
47 | $ moonshot push
48 | ```
49 |
50 | To build a "named build" for releasing through test and production environments,
51 | use:
52 | ```
53 | $ moonshot build v0.1.0
54 | $ moonshot deploy v0.1.0 -n
55 | ```
56 |
57 | We recommend using a CI system like Jenkins to perform those activities, for
58 | consistency.
59 |
--------------------------------------------------------------------------------
/lib/moonshot/parent_stack_parameter_loader.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | class ParentStackParameterLoader
5 | def initialize(config)
6 | @config = config
7 | end
8 |
9 | def load!
10 | @config.parent_stacks.each do |stack_name|
11 | count = 0
12 |
13 | resp = cf_client.describe_stacks(stack_name:)
14 | raise "Parent Stack #{stack_name} not found!" unless resp.stacks.size == 1
15 |
16 | # If there is an input parameters matching a stack output, pass it.
17 | resp.stacks[0].outputs.each do |output|
18 | next unless @config.parameters.key?(output.output_key)
19 |
20 | # Our Stack has a Parameter matching this output. Set it's
21 | # value to the Output's value.
22 | count += 1
23 | @config.parameters.fetch(output.output_key).set(output.output_value)
24 | end
25 |
26 | puts "Imported #{count} parameters from parent stack #{stack_name.blue}!" if count.positive?
27 | end
28 | end
29 |
30 | def load_missing_only!
31 | @config.parent_stacks.each do |stack_name|
32 | resp = cf_client.describe_stacks(stack_name:)
33 | raise "Parent Stack #{stack_name} not found!" unless resp.stacks.size == 1
34 |
35 | # If there is an input parameters matching a stack output, pass it.
36 | resp.stacks[0].outputs.each do |output|
37 | next unless @config.parameters.key?(output.output_key)
38 |
39 | # Our Stack has a Parameter matching this output. Set it's
40 | # value to the Output's value, but only if we don't already
41 | # have a previous value we're using.
42 | unless @config.parameters.fetch(output.output_key).use_previous?
43 | @config.parameters.fetch(output.output_key).set(output.output_value)
44 | end
45 | end
46 | end
47 | end
48 |
49 | private
50 |
51 | def cf_client
52 | Aws::CloudFormation::Client.new
53 | end
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/spec/moonshot/ssh_spec.rb:
--------------------------------------------------------------------------------
1 | describe 'Moonshot SSH features' do
2 | subject do
3 | c = Moonshot::ControllerConfig.new
4 | c.app_name = 'MyApp'
5 | c.environment_name = 'prod'
6 | c.ssh_config.ssh_user = 'joeuser'
7 | c.ssh_config.ssh_identity_file = '/Users/joeuser/.ssh/thegoods.key'
8 | c.ssh_command = 'cat /etc/passwd'
9 |
10 | Moonshot::Controller.new(c)
11 | end
12 |
13 | describe 'Moonshot::Controller#ssh' do
14 | context 'normally' do
15 | it 'should execute an ssh command with proper parameters' do
16 | ts = instance_double(Moonshot::SSHTargetSelector)
17 | expect(Moonshot::SSHTargetSelector).to receive(:new).and_return(ts)
18 | expect(ts).to receive(:choose!).and_return('i-04683a82f2dddcc04')
19 |
20 | expect_any_instance_of(Moonshot::SSHCommandBuilder).to receive(:instance_ip).exactly(2)
21 | .and_return('123.123.123.123')
22 | expect(subject).to receive(:exec)
23 | .with('ssh -t -i /Users/joeuser/.ssh/thegoods.key -l joeuser 123.123.123.123 cat\ /etc/passwd') # rubocop:disable LineLength
24 | expect { subject.ssh }
25 | .to output("Opening SSH connection to i-04683a82f2dddcc04 (123.123.123.123)...\n")
26 | .to_stderr
27 | end
28 | end
29 |
30 | context 'when an instance id is given' do
31 | subject do
32 | c = super()
33 | c.config.ssh_instance = 'i-012012012012012'
34 | c
35 | end
36 |
37 | it 'should execute an ssh command with proper parameters' do
38 | expect_any_instance_of(Moonshot::SSHCommandBuilder).to receive(:instance_ip).exactly(2)
39 | .and_return('123.123.123.123')
40 | expect(subject).to receive(:exec)
41 | .with('ssh -t -i /Users/joeuser/.ssh/thegoods.key -l joeuser 123.123.123.123 cat\ /etc/passwd') # rubocop:disable LineLength
42 | expect { subject.ssh }
43 | .to output("Opening SSH connection to i-012012012012012 (123.123.123.123)...\n").to_stderr
44 | end
45 | end
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/spec/moonshot/stub_rollout_asg.rb:
--------------------------------------------------------------------------------
1 | class StubRolloutASG
2 | InstanceSpec = Struct.new(:id, :conforming)
3 |
4 | attr_accessor :max, :desired
5 | attr_reader :instances
6 |
7 | def initialize
8 | @max = 3
9 | @desired = 2
10 | @instances = []
11 | @replacement_instances = []
12 | @instance_healths = {}
13 | end
14 |
15 | # Test config methods
16 | def add_instances(*instances)
17 | instances.each { |i| @instances << InstanceSpec.new(*i) }
18 | end
19 |
20 | def add_replacement_instances(*instances)
21 | instances.each { |i| @replacement_instances << InstanceSpec.new(*i) }
22 | end
23 |
24 | def add_health_response(id, *healths)
25 | @instance_healths[id] ||= []
26 |
27 | healths.each do |h|
28 | @instance_healths[id] << Moonshot::Tools::ASGRollout::InstanceHealth.new(*h)
29 | end
30 | end
31 |
32 | def everything_used?
33 | # All replacement instances were returned.
34 | @replacement_instances.empty? &&
35 | # And all health queries were returned.
36 | @instance_healths.all? { |_, v| v.empty? }
37 | end
38 |
39 | # Implementation methods
40 | def current_max_and_desired
41 | [@max, @desired]
42 | end
43 |
44 | def set_max_and_desired(max, desired)
45 | @max = max
46 | @desired = desired
47 | end
48 |
49 | def wait_for_new_instance
50 | new_instance = @replacement_instances.shift
51 |
52 | unless new_instance
53 | raise 'No more instances with call to #wait_for_new_instance!'
54 | end
55 |
56 | @instances << new_instance
57 | new_instance.id
58 | end
59 |
60 | def instance_health(id)
61 | next_health = @instance_healths.fetch(id, []).shift
62 |
63 | raise "No InstanceHealth responses set for #{id}!" unless next_health
64 |
65 | next_health
66 | end
67 |
68 | def non_conforming_instances
69 | @instances.select { |i| !i.conforming }.map(&:id)
70 | end
71 |
72 | def detach_instance(id, decrement:)
73 | @desired -= 1 if decrement
74 | @instances.delete_if { |i| i.id == id }
75 | end
76 | end
77 |
--------------------------------------------------------------------------------
/docs/about/contribute.md:
--------------------------------------------------------------------------------
1 | # Contributing to Moonshot
2 | An introduction to contributing to the Moonshot project.
3 |
4 | The Moonshot project welcomes, and depends, on contributions from
5 | developers and users in the open source community. Contributions can
6 | be made in a number of ways, a few examples are:
7 |
8 | - Code patches via pull requests
9 | - Documentation improvements
10 | - Bug reports and patch reviews
11 | - Reporting an Issue
12 |
13 | ## How to Contribute
14 |
15 | So, you want to help? Awesome! In order to guide this the best way we
16 | can and to make sure we can help you in either getting a bug fixed or
17 | improve our documentation. we are asking you to please include as much
18 | detail as you can. Let us know your platform and Moonshot version. If
19 | the problem is visual please add a screenshot and if you get an error
20 | please include the full error and traceback.
21 |
22 | ## Submitting Pull Requests
23 |
24 | Once you are happy with your changes or you are ready for some
25 | feedback, push it to your fork and send a pull request. For a change
26 | to be accepted it will most likely need to have tests and
27 | documentation if it is a new feature.
28 |
29 | ## Expectations
30 | Even though this is an Open project, it does not mean that we have
31 | 24/7 support for it. The best way to make sure that we accept your
32 | bugfix or add in a feature you want is still to make a pull request
33 | and link it in the issue. Include some reasons how to reproduce or
34 | even better, make sure the bug is tested and passes highy increases
35 | your chances for the change to get committed.
36 |
37 | So, you've done all that? It very likely that it could get committed
38 | really quickly but since some of the contributers to this project
39 | could be on a holiday or we are in no way responsible in actually
40 | merging it in soon. But have no fear! Since you filed a pull request
41 | from your fork, you are able to use a version of moonshot
42 | yourselves. And we promise you, we will do our utter best.
43 |
44 | And again, thanks! We're looking forward working with you.
45 |
--------------------------------------------------------------------------------
/spec/moonshot/plugins/code_deploy_setup_spec.rb:
--------------------------------------------------------------------------------
1 | describe Moonshot::Plugins::CodeDeploySetup do
2 | subject { Moonshot::Plugins::CodeDeploySetup }
3 |
4 | describe '#new' do
5 | it 'should raise ArgumentError if no region arg is set' do
6 | allow(ENV).to receive(:[]).with("AWS_REGION").and_return(nil)
7 | expect { subject.new('example') }.to raise_error(ArgumentError)
8 | end
9 |
10 | it 'should use the ENV[AWS_REGION] if no region arg is set' do
11 | allow(ENV).to receive(:[]).with("AWS_REGION").and_return('us-west-2')
12 | expect(subject.new('example').regions).to include('us-west-2')
13 | end
14 |
15 | it 'should use return the correct bucket name for the current active region' do
16 | allow(ENV).to receive(:[]).with("AWS_REGION").and_return('ap-southeast-1')
17 | expect(subject.new('example-builds').bucket_name).to eq('example-builds-ap-southeast-1')
18 | end
19 |
20 | it 'should use return the correct bucket prefix' do
21 | allow(ENV).to receive(:[]).with("AWS_REGION").and_return('us-east-1')
22 | expect(subject.new('example-builds').bucket_prefix).to eq('')
23 | expect(subject.new('example-builds', prefix: 'api').bucket_prefix).to eq('api/')
24 | end
25 | end
26 |
27 | describe '#run_hook' do
28 | let(:s3_client) { instance_double(Aws::S3::Client) }
29 | let(:s3_bucket) { instance_double(Aws::S3::Bucket) }
30 |
31 | before(:each) do
32 | allow(Aws::S3::Client).to receive(:new).and_return(s3_client)
33 | allow(Aws::S3::Bucket).to receive(:new).and_return(s3_bucket)
34 | allow(s3_bucket).to receive(:exists?).and_return(false)
35 | allow(s3_bucket).to receive(:create).and_return(true)
36 | end
37 |
38 | it 'creates a bucket for all set regions' do
39 | allow(ENV).to receive(:[]).with("AWS_REGION").and_return('us-east-1')
40 | plugin = subject.new('example')
41 | expect(plugin.regions).to include('us-east-1')
42 | expect(s3_bucket).to receive(:exists?)
43 | expect(s3_bucket).to receive(:create)
44 |
45 | plugin.pre_create(nil)
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/docs/plugins/code_deploy_setup.md:
--------------------------------------------------------------------------------
1 | # Moonshot CodeDeploy Setup plugin
2 | Moonshot plugin to ensure CodeDeploy has all the necessary S3 buckets created
3 | to support deploying to multiple configured regions.
4 |
5 | ## Moonshot configuration
6 | Update your Moonfile.rb with the following configuration
7 |
8 | ```ruby
9 | Moonshot.config do |c|
10 | # ...
11 | c.app_name = 'example-project'
12 |
13 | bucket_name = "#{c.app_name}-builds"
14 |
15 | # bucket_name: Base name of the S3 bucket to create, will have region appended to it.
16 | # prefix: Optional. If using the same bucket for multiple apps then recommend adding a prefix.
17 | # regions: list of all supported regions you would like to use. Default: ENV['AWS_REGION'].
18 | code_deploy_setup_plugin = Moonshot::Plugins::CodeDeploySetup.new(
19 | bucket_name,
20 | prefix: c.app_name,
21 | regions: [ 'us-east-1', 'us-west-2' ]
22 | c.plugins << code_deploy_setup_plugin
23 |
24 | c.deployment_mechanism = CodeDeploy.new
25 | c.artifact_repository = S3BucketViaGithubReleases.new(
26 | code_deploy_setup_plugin.bucket_name,
27 | prefix: code_deploy_setup_plugin.bucket_prefix
28 | )
29 | ```
30 |
31 | ## CloudFormation Template additions
32 | To use the auto-created S3 Buckets in your Cloudformation add the following Parameter and
33 | IAM Role Policy to your template to allow CodeDeploy running on instances access to your
34 | newly created S3 buckets.
35 |
36 | Parameters
37 | ```
38 | "CodeDeployBucketName": {
39 | "Type": "String",
40 | "Description": "Base name of the S3 Bucket used for CodeDeploy",
41 | "Default": "example-project-builds"
42 | }
43 | ```
44 |
45 | Instance Role Policy:
46 | ```
47 | {
48 | "PolicyName": "CodeDeployBuildAccess",
49 | "PolicyDocument": {
50 | "Version": "2012-10-17",
51 | "Statement": [{
52 | "Effect": "Allow",
53 | "Action": [
54 | "s3:GetObject"
55 | ],
56 | "Resource": {"Fn::Join": ["", ["arn:aws:s3:::" {"Ref": "CodeDeployBucketName"}, "-", { "Ref": "AWS::Region" } , "/*"]]}
57 | }]
58 | }
59 | }
60 | ```
61 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/version-control/git/pre-push:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License").
6 | # You may not use this file except in compliance with the License.
7 | # A copy of the License is located at
8 | #
9 | # http://aws.amazon.com/apache2.0
10 | #
11 | # or in the "license" file accompanying this file. This file is distributed
12 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
13 | # express or implied. See the License for the specific language governing
14 | # permissions and limitations under the License.
15 |
16 | AWS="/usr/local/bin/aws"
17 |
18 | APPLICATION_NAME=$(git config --get aws-codedeploy.application-name)
19 | DEPLOYMENT_GROUP=$(git config --get aws-codedeploy.deployment-group)
20 | BUCKET_NAME=$(git config --get aws-codedeploy.s3bucket)
21 | BUNDLE_NAME=$(echo $(basename `pwd`).zip)
22 |
23 | # `aws deploy push` will overwrite the bundle in S3. If you'd rather ensure that you have a unique
24 | # object for each push, you could, for example, append the commit hash:
25 | #BUNDLE_NAME=$(echo $(basename `pwd`)-$(git log -n 1 --format=%H).zip)
26 |
27 | # Call `aws deploy push` to create a new revision of the current repo
28 | echo "Pushing $BUNDLE_NAME to s3://${BUCKET_NAME} and registering with application '${APPLICATION_NAME}'" 1>&2
29 | $AWS deploy push \
30 | --application-name ${APPLICATION_NAME} \
31 | --s3-location s3://${BUCKET_NAME}/${BUNDLE_NAME} \
32 | --ignore-hidden-files \
33 | --source .
34 |
35 | revision_json="{\"revisionType\":\"S3\",\"s3Location\":{\"bucket\":\"${BUCKET_NAME}\",\"bundleType\":\"zip\",\"key\":\"${BUNDLE_NAME}\"}}"
36 |
37 | if [ $? != 0 ]; then
38 | echo "Push to codedeploy failed; skipping create-deployment" 1>&2
39 | else
40 | echo "Deploying s3://${BUCKET_NAME}/${BUNDLE_NAME} to application ${APPLICATION_NAME} and deployment group ${DEPLOYMENT_GROUP}" 1>&2
41 | $AWS deploy create-deployment \
42 | --application-name ${APPLICATION_NAME} \
43 | --deployment-group-name ${DEPLOYMENT_GROUP} \
44 | --revision $revision_json
45 | fi
46 |
--------------------------------------------------------------------------------
/docs/mechanisms/deployment.md:
--------------------------------------------------------------------------------
1 | # DeploymentMechanism
2 |
3 | ## CodeDeploy
4 |
5 | The CodeDeploy DeploymentMechanism will create a CodeDeploy
6 | Application and Deployment Group matching the application name. The
7 | created Deployment Group will point at the logical resource id
8 | provided to the constructor (e.g. `CodeDeploy.new(asg:
9 | 'MyAutoScalingGroup')`). During the `deploy-code` action, the
10 | ArtifactRepository is checked for compatibility with
11 | CodeDeploy. Currently only the S3Bucket is supported, though
12 | CodeDeploy itself supports deploying from a git source.
13 |
14 | Assumptions made by the CodeDeploy mechanism:
15 |
16 | - You are using an S3Bucket ArtifactRepository.
17 | - Your build artifact contains an appspec.yml file.
18 |
19 | Sample Usage
20 | ```ruby
21 | Moonshot.config do |c|
22 | c.deployment_mechanism = CodeDeploy.new(
23 | asg: 'AutoScalingGroup',
24 | role: 'CodeDeployRole',
25 | app_name: 'my_app_name',
26 | config_name: 'CodeDeployDefault.OneAtATime')
27 | ...
28 | ```
29 | Parameters
30 |
31 | ### asg | string,array
32 |
33 | The logical name of one or more Auto Scaling Groups to create and
34 | manage a Deployment Group for in CodeDeploy.
35 |
36 | ### optional_asg | string,array
37 |
38 | The logical name of one or more Auto Scaling Groups to add to the
39 | Deployment Group in CodeDeploy. These ASGs don't have to exist.
40 | If they do, they will be added to the Deployment Group.
41 |
42 | ### role | string
43 |
44 | IAM role with AWSCodeDeployRole policy. CodeDeployRole is considered
45 | as default role if its not specified.
46 |
47 | ### app_name | string,nil
48 |
49 | The name of the CodeDeploy Application and Deployment Group. By
50 | default, this is the same as the stack name, and probably what you
51 | want. If you have multiple deployments in a single Stack, they must
52 | have unique names.
53 |
54 | ### config_name | string
55 |
56 | The name of the Deplloyment
57 | Configuration. CodeDeployDefault.OneAtATime is the default if its not
58 | specified.
59 |
60 | For more information about CodeDeploy, see the [AWS Documentation][1].
61 |
62 | [1]: http://docs.aws.amazon.com/codedeploy/latest/userguide/welcome.html
63 |
--------------------------------------------------------------------------------
/spec/moonshot/shell_spec.rb:
--------------------------------------------------------------------------------
1 | require 'securerandom'
2 |
3 | module Moonshot
4 | describe Shell do
5 | include ResourcesHelper
6 | include described_class
7 |
8 | let(:resources) do
9 | Resources.new(
10 | ilog: InteractiveLoggerProxy.new(log),
11 | stack: double(Stack).as_null_object,
12 | controller: instance_double(Moonshot::Controller).as_null_object
13 | )
14 | end
15 |
16 | before { self.resources = resources }
17 |
18 | describe '#shell' do
19 | it 'should return a shell compatible with Thor::Shell::Basic.' do
20 | expect(shell).to be_a(Thor::Shell::Basic)
21 | end
22 | end
23 |
24 | describe '#sh_out' do
25 | it 'should raise on non-zero exit.' do
26 | expect { sh_out('false') }.to raise_error(/`false` exited 1/)
27 | end
28 |
29 | it 'should not raise if fail is disabled.' do
30 | sh_out('false', false)
31 | end
32 | end
33 |
34 | describe '#sh_step' do
35 | before do
36 | expect(InteractiveLoggerProxy::Step).to receive(:new).and_return(step)
37 | end
38 |
39 | let(:step) { instance_double(InteractiveLoggerProxy::Step) }
40 |
41 | it 'should raise an error if the step fails.' do
42 | expect { sh_step('false') }.to raise_error(/`false` exited 1/)
43 | end
44 |
45 | it 'should provide the step and sh output to a block.' do
46 | output = nil
47 | expect(step).to receive(:continue).with('reticulating splines')
48 | expect(step).to receive(:success)
49 | sh_step('echo yo') do |step, out|
50 | step.continue('reticulating splines')
51 | output = out
52 | end
53 | expect(output).to match('yo')
54 | end
55 |
56 | it 'should truncate a long messages.' do
57 | long_s = SecureRandom.urlsafe_base64(terminal_width)
58 | cmd = "echo #{long_s}"
59 | truncated_s = "#{cmd[0..(terminal_width - 22)]}..."
60 | expect(resources.ilog).to receive(:start_threaded).with(truncated_s)
61 | .and_call_original
62 | allow(step).to receive(:success)
63 | sh_step(cmd)
64 | end
65 | end
66 | end
67 | end
68 |
--------------------------------------------------------------------------------
/lib/moonshot/dynamic_template.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'erb'
4 |
5 | module Moonshot
6 | class TemplateExists < StandardError; end
7 | class InvalidTemplate < StandardError; end
8 |
9 | class DynamicTemplate
10 | # A class to encapsulate template parameters, passing a hash to `process` is
11 | # only available from Ruby 2.5.
12 | class Parameters
13 | def initialize(parameters)
14 | parameters.each do |k, v|
15 | instance_variable_set("@#{k}".to_sym, v)
16 | # Adding an attribute reader for flexibility, this way you can add
17 | # either `@parameter` or just `parameter` to your template.
18 | self.class.send(:attr_reader, k.to_sym)
19 | end
20 | end
21 |
22 | def expose_binding
23 | binding
24 | end
25 | end
26 |
27 | attr_writer :destination
28 |
29 | def initialize(source:, parameters:, destination:)
30 | @source = File.read(source)
31 | @parameters = parameters
32 | @destination = destination
33 | end
34 |
35 | def parameters_obj
36 | @parameters_obj ||= Parameters.new(parameters_file)
37 | end
38 |
39 | def parameters_file
40 | env_name = Moonshot.config.environment_name
41 | @parameters.respond_to?(:call) ? @parameters.call(env_name) : @parameters
42 | end
43 |
44 | def process
45 | validate_destination_exists
46 | new_template = generate_template
47 |
48 | validate_template(new_template)
49 | write_output(new_template)
50 | end
51 |
52 | private
53 |
54 | def validate_destination_exists
55 | return unless File.file?(@destination)
56 |
57 | raise TemplateExists, "Output file '#{@destination}' already exists."
58 | end
59 |
60 | def validate_template(template)
61 | Aws::CloudFormation::Client.new.validate_template(
62 | template_body: template
63 | )
64 | rescue Aws::CloudFormation::Errors::ValidationError => e
65 | raise InvalidTemplate, "Invalid template:\n#{e}"
66 | end
67 |
68 | def generate_template
69 | ERB.new(@source).result(parameters_obj.expose_binding)
70 | end
71 |
72 | def write_output(content)
73 | File.write(@destination, content)
74 | end
75 | end
76 | end
77 |
--------------------------------------------------------------------------------
/lib/moonshot/controller_config.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | # Holds configuration for Moonshot::Controller
5 | class ControllerConfig
6 | attr_reader :account_alias
7 |
8 | attr_accessor :additional_tag, :answer_file, :app_name, :artifact_repository, :build_mechanism,
9 | :changeset_wait_time, :deployment_mechanism, :dev_build_name_proc, :environment_name,
10 | :interactive, :interactive_logger, :parameter_overrides, :parameters, :parent_stacks,
11 | :default_parameter_source, :parameter_sources, :plugins, :project_root,
12 | :show_all_stack_events, :ssh_auto_scaling_group_name, :ssh_command, :ssh_config,
13 | :ssh_instance, :template_file, :template_s3_bucket, :extra_tags
14 |
15 | def initialize
16 | @default_parameter_source = AskUserSource.new
17 | @interactive = true
18 | @interactive_logger = InteractiveLogger.new
19 | @parameter_overrides = {}
20 | @parameter_sources = {}
21 | @parameters = ParameterCollection.new
22 | @parent_stacks = []
23 | @account_alias = nil
24 | @per_account_config = {}
25 | @plugins = []
26 | @project_root = Dir.pwd
27 | @show_all_stack_events = false
28 | @ssh_config = SSHConfig.new
29 | @extra_tags = []
30 |
31 | @dev_build_name_proc = lambda do |c|
32 | ['dev', c.app_name, c.environment_name, Time.now.to_i].join('/')
33 | end
34 |
35 | user = ENV.fetch('USER', 'default-user').gsub(/\W/, '')
36 | @environment_name = "dev-#{user}"
37 | end
38 |
39 | def in_account(name, &blk)
40 | # Store account specific configs as lambdas, to be evaluated
41 | # if the account name matches during controller execution.
42 | @per_account_config[name] = blk
43 | end
44 |
45 | def update_for_account!
46 | # Evaluated any account-specific configuration.
47 | @account_alias = Moonshot::AccountContext.get
48 | return unless @account_alias
49 | return unless @per_account_config.key?(@account_alias)
50 |
51 | @per_account_config[@account_alias].call(self)
52 | end
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | if ENV['COVERAGE']
2 | require 'simplecov'
3 |
4 | SimpleCov.start do
5 | add_filter '/spec/'
6 | end
7 | end
8 |
9 | # RSpec brings this in ad-hoc, but if it comes in after fakefs we get
10 | # superclass mismatch errors.
11 | require 'pp'
12 | require 'moonshot'
13 | require 'fakefs/spec_helpers'
14 |
15 | puts "rspec pid: #{Process.pid}"
16 | trap 'USR1' do
17 | threads = Thread.list
18 |
19 | puts
20 | puts '=' * 80
21 | puts "Received USR1 signal; printing all #{threads.count} thread backtraces."
22 |
23 | threads.each do |thr|
24 | description = thr == Thread.main ? 'Main thread' : thr.inspect
25 | puts
26 | puts "#{description} backtrace: "
27 | puts thr.backtrace.join("\n")
28 | end
29 |
30 | puts '=' * 80
31 | end
32 |
33 | class MockInteractiveLogger
34 | attr_reader :final_logs
35 |
36 | def initialize
37 | @final_logs = []
38 | end
39 |
40 | def start(msg = nil)
41 | @msg = msg
42 | yield self
43 | end
44 | alias start_threaded start
45 |
46 | def continue(msg = nil)
47 | @msg = msg if msg
48 | end
49 |
50 | def success(msg = nil)
51 | @final_logs << [:success, msg || @msg]
52 | end
53 |
54 | def failure(msg = nil)
55 | @final_logs << [:failure, msg || @msg]
56 | end
57 |
58 | def debug(msg)
59 | @final_logs << [:debug, msg]
60 | end
61 |
62 | def info(msg)
63 | @final_logs << [:info, msg]
64 | end
65 | end
66 |
67 | shared_examples 'with a working moonshot application' do
68 | include FakeFS::SpecHelpers
69 |
70 | # Force aws-sdk to load metadata before FakeFS takes over.
71 | before(:all) do
72 | Aws::CloudFormation::Client.new(stub_responses: true)
73 | end
74 |
75 | before(:each) do
76 | FakeFS::FileSystem.clone(File.join(File.dirname(__FILE__), 'fs_fixtures'), '/')
77 | end
78 | end
79 |
80 | shared_examples 'with a CloudFormation stubbed client' do
81 | let(:cf_client_stubs) { {} }
82 | let(:cf_client) { Aws::CloudFormation::Client.new(stub_responses: cf_client_stubs) }
83 |
84 | before(:each) do
85 | allow(Aws::CloudFormation::Client).to receive(:new).and_return(cf_client)
86 | end
87 | end
88 |
89 | def fixture_path(path)
90 | File.join(File.dirname(__FILE__), 'fixtures', path)
91 | end
92 |
93 | def fixture(path)
94 | File.read(fixture_path(path))
95 | end
96 |
--------------------------------------------------------------------------------
/lib/moonshot/command_line_dispatcher.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | class CommandLineDispatcher
5 | def initialize(command, klass, args)
6 | @command = command
7 | @klass = klass
8 | @args = args
9 | end
10 |
11 | def dispatch!
12 | # Look to see if we're allowed only to run in certain accounts, or
13 | # not allowed to run in specific accounts.
14 | check_account_restrictions
15 |
16 | # Allow any mechanisms or plugins to hook into this CLI command.
17 | handler = @klass.new
18 | parser = build_parser(handler)
19 | parser.parse!
20 |
21 | req_arguments = handler.method(:execute).parameters.select { |arg| arg[0] == :req }
22 | if ARGV.size < req_arguments.size
23 | warn handler.parser.help
24 | raise "Invalid command line for '#{@command}'."
25 | end
26 |
27 | handler.execute(*@args)
28 | end
29 |
30 | private
31 |
32 | def check_account_restrictions
33 | this_account = Moonshot::AccountContext.get
34 |
35 | return if @klass.only_in_account.nil? ||
36 | Array(@klass.only_in_account).any? { |a| a == this_account }
37 |
38 | warn "'#{@command}' can only be run in the following accounts:"
39 | Array(@klass.only_in_account).each do |account_name|
40 | warn "- #{account_name}"
41 | end
42 |
43 | raise 'Command account restriction violation.'
44 | end
45 |
46 | def build_parser(handler)
47 | parser = handler.parser
48 |
49 | # Each mechanism / plugin may manipulate the OptionParser object
50 | # associated with this command.
51 | %i[build_mechanism deployment_mechanism artifact_repository].each do |prov|
52 | provider = Moonshot.config.send(prov)
53 |
54 | parser = provider.send(hook_func_name(@command), parser) if provider.respond_to?(hook_func_name(@command))
55 | end
56 |
57 | Moonshot.config.plugins.each do |plugin|
58 | parser = plugin.send(hook_func_name(@command), parser) if plugin.respond_to?(hook_func_name(@command))
59 | end
60 |
61 | parser
62 | end
63 |
64 | # Name of the function a plugin or mechanism could define to manipulate
65 | # the parser for a command.
66 | def hook_func_name(command)
67 | command.tr('-', '_') << '_cli_hook'
68 | end
69 | end
70 | end
71 |
--------------------------------------------------------------------------------
/spec/moonshot/artifact_repository/s3_bucket_via_github_releases_spec.rb:
--------------------------------------------------------------------------------
1 | require 'fakefs/spec_helpers'
2 |
3 | module Moonshot
4 | describe ArtifactRepository::S3BucketViaGithubReleases do
5 | let(:version) { '0.0.0'}
6 | let(:bucket_name) { 'moonshot-test-bucket' }
7 | let(:artifact_name) { "moonshot-#{version}-beta.tar.gz" }
8 | let(:hub_command) { "hub release download #{version}" }
9 |
10 | def fake_artifact
11 | FakeFS { FileUtils.touch(artifact_name) }
12 | end
13 |
14 | def fake_checksum_file
15 | FakeFS do
16 | base = File.basename(artifact_name, '.tar.gz')
17 | FileUtils.touch("#{base}.md5")
18 | end
19 | end
20 |
21 | subject { described_class.new(bucket_name) }
22 |
23 | describe '#download_from_github' do
24 | include FakeFS::SpecHelpers
25 |
26 | it 'should use hub to download the latest version' do
27 | obj = subject
28 | expect(obj).to receive(:sh_out) { fake_artifact }.with(hub_command)
29 |
30 | subject.send(:download_from_github, version)
31 | end
32 |
33 | it 'should raise RuntimeError when download has failed' do
34 | obj = subject
35 | expect(obj).to receive(:sh_out).at_least(:once)
36 |
37 | expect do
38 | obj.send(:download_from_github, version)
39 | end.to raise_error(RuntimeError)
40 | end
41 |
42 | it 'should retry if the download has failed' do
43 | obj = subject
44 | expect(obj).to receive(:sh_out).with(hub_command).exactly(3).times
45 |
46 | expect { obj.send(:download_from_github, version) }.to raise_error(RuntimeError)
47 | end
48 |
49 | it 'should verify the checksum of the downloaded artifact' do
50 | obj = subject
51 |
52 | expect(obj).to receive(:sh_out) do
53 | fake_artifact
54 | fake_checksum_file
55 | end.with(hub_command)
56 |
57 | expect(obj).to receive(:verify_download_checksum)
58 | obj.send(:download_from_github, version)
59 | end
60 |
61 | it 'should return the name of the downloaded artifact' do
62 | obj = subject
63 |
64 | allow(obj).to receive(:sh_out) { fake_artifact }.with(hub_command)
65 |
66 | expect(obj.send(:download_from_github, version))\
67 | .to eql("/#{artifact_name}")
68 | end
69 | end
70 | end
71 | end
72 |
--------------------------------------------------------------------------------
/lib/moonshot/build_mechanism/script.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'open3'
4 |
5 | # Compile a release artifact using a shell script.
6 | #
7 | # The output file will be deleted before the script is run, and is expected to
8 | # exist after the script exits. Any non-zero exit status will be consider a
9 | # build failure, and any output will be displayed to the user.
10 | #
11 | # Creating a new Script BuildMechanism looks like this:
12 | #
13 | # class MyReleaseTool < Moonshot::CLI
14 | # include Moonshot::BuildMechanism
15 | # self.build_mechanism = Script.new('script/build.sh')
16 | # end
17 | #
18 | class Moonshot::BuildMechanism::Script
19 | include Moonshot::ResourcesHelper
20 | include Moonshot::DoctorHelper
21 |
22 | include Open3
23 |
24 | attr_reader :output_file
25 |
26 | def initialize(script, output_file: 'output.tar.gz')
27 | @script = script
28 | @output_file = output_file
29 | end
30 |
31 | def pre_build_hook(_version)
32 | File.delete(@output_file) if File.exist?(@output_file)
33 | end
34 |
35 | def build_hook(version)
36 | env = {
37 | 'VERSION' => version,
38 | 'OUTPUT_FILE' => @output_file
39 | }
40 | ilog.start_threaded "Running Script: #{@script}" do |s|
41 | run_script(s, env:)
42 | end
43 | end
44 |
45 | def post_build_hook(_version)
46 | unless File.exist?(@output_file) # rubocop:disable Style/GuardClause
47 | raise 'Build command did not produce output file!'
48 | end
49 | end
50 |
51 | private
52 |
53 | def run_script(step, env: {})
54 | popen2e(env, @script) do |_, out, wait|
55 | output = []
56 |
57 | loop do
58 | str = out.gets
59 | unless str.nil?
60 | output << str.chomp
61 | ilog.debug(str.chomp)
62 | end
63 | break if out.eof?
64 | end
65 |
66 | result = wait.value
67 | step.success "Build script #{@script} exited successfully!" if result.exitstatus.zero?
68 | unless result.exitstatus.zero?
69 | ilog.error "Build script failed with exit status #{result.exitstatus}!"
70 | ilog.error output.join("\n")
71 | step.failure "Build script #{@script} failed with exit status #{result.exitstatus}!"
72 | end
73 | end
74 | end
75 |
76 | def doctor_check_script_exists
77 | if File.exist?(@script)
78 | success "Script '#{@script}' exists."
79 | else
80 | critical "Could not find build script '#{@script}'!"
81 | end
82 | end
83 | end
84 |
--------------------------------------------------------------------------------
/lib/moonshot/changelog_parser.rb:
--------------------------------------------------------------------------------
1 | # Makes all string literals in this file frozen/immutable for performance
2 | # frozen_string_literal: true
3 |
4 | module Moonshot
5 | # Custom changelog parser to replace the unmaintained Vandamme gem (previously used in Moonshot until v3.0.4)
6 | class ChangelogParser
7 | # Parses a changelog and extracts content for a specific version
8 | #
9 | # @param changelog_content [String] The full changelog content
10 | # @param version [String] The version to extract content for
11 | # @return [String] The changelog content for the specified version
12 | # @raise [RuntimeError] if the version is not found in the changelog
13 | def self.parse(changelog_content, version)
14 | new(changelog_content).parse(version)
15 | end
16 |
17 | def initialize(changelog_content)
18 | @content = changelog_content
19 | end
20 |
21 | def parse(version)
22 | version = normalize_version(version)
23 | version_section = find_version_section(version)
24 |
25 | raise "#{version} not found in CHANGELOG.md" unless version_section
26 |
27 | extract_version_content(version_section)
28 | end
29 |
30 | private
31 |
32 | def normalize_version(version)
33 | version.to_s.strip
34 | end
35 |
36 | def find_version_section(version)
37 | sections = split_into_sections
38 |
39 | sections.find do |section|
40 | section_matches_version?(section, version)
41 | end
42 | end
43 |
44 | def split_into_sections
45 | @content.split(/^#+\s+/).reject(&:empty?)
46 | end
47 |
48 | def section_matches_version?(section, version)
49 | # Match version patterns like:
50 | # - "1.0.0"
51 | # - "[1.0.0]"
52 | # - "v1.0.0"
53 | # - "1.0.0 - 2023-01-01"
54 | # - "1.0.0 / 2023-01-01"
55 | # - "1.0.0 (2023-01-01)"
56 | version_pattern = %r{^(\[?v?#{Regexp.escape(version)}\]?)(\s+[-–]\s+|\s+/\s+|\s+\(|\s*$)}i
57 | section.match(version_pattern)
58 | end
59 |
60 | def extract_version_content(version_section)
61 | lines = version_section.lines
62 | content_lines = []
63 |
64 | # Skip the version header line
65 | lines[1..]&.each do |line|
66 | # Stop at next version header
67 | break if next_version_header?(line)
68 |
69 | content_lines << line
70 | end
71 |
72 | # Clean up and return the content
73 | content_lines.join.strip
74 | end
75 |
76 | def next_version_header?(line)
77 | # Check if this line is the start of another version section
78 | line.match(/^#+\s+(\[?v?\d+\.\d+\.\d+)/i)
79 | end
80 | end
81 | end
82 |
--------------------------------------------------------------------------------
/lib/moonshot/commands/new.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | module Commands
5 | class New < Moonshot::Command
6 | self.usage = 'new [options]'
7 | self.description = 'Creates a new Moonshot project.'
8 |
9 | DEFAULT_DIRECTORY = File.join(__dir__, '..', '..', 'default').freeze
10 |
11 | def execute
12 | warn 'Looks like your project is already set up!'
13 | end
14 |
15 | class << self
16 | def run!(application_name)
17 | @application_name = application_name
18 |
19 | create_project_dir
20 | copy_defaults
21 | fill_moonfile
22 | print_success_message
23 | end
24 |
25 | private
26 |
27 | def create_project_dir
28 | raise "Directory '#{@application_name}' already exists!" \
29 | if Dir.exist?(project_path)
30 |
31 | Dir.mkdir(project_path)
32 | end
33 |
34 | def project_path
35 | @project_path ||= File.join(Dir.pwd, @application_name)
36 | end
37 |
38 | def copy_defaults
39 | target_path = File.join(DEFAULT_DIRECTORY.dup, '.')
40 | FileUtils.cp_r(target_path, project_path)
41 | end
42 |
43 | def fill_moonfile
44 | File.open(File.join(project_path, 'Moonfile.rb'), 'w') { |f| f.write generate_moonfile }
45 | end
46 |
47 | def generate_moonfile
48 | <<-EOF
49 | Moonshot.config do |m|
50 | m.app_name = '#{@application_name}'
51 | m.artifact_repository = S3Bucket.new('')
52 | m.build_mechanism = Script.new('bin/build.sh')
53 | m.deployment_mechanism = CodeDeploy.new(asg: 'AutoScalingGroup')
54 | end
55 | EOF
56 | end
57 |
58 | def print_success_message
59 | warn <<-EOF
60 | Your application is configured, the following changes have been made
61 | to your project directory:
62 |
63 | * Created Moonfile.rb, where you can configure your project.
64 | * Created moonshot/plugins, where you can place custom Ruby code
65 | to add hooks to core Moonshot actions (create, update, delete, etc.)
66 | * Created moonshot/cli_extensions, where you can place custom Ruby
67 | code to add your own project-specific commands to Moonshot.
68 | * Created moonshot/template.yml, where you can build your
69 | CloudFormation template.
70 |
71 | You will also need to ensure your Amazon account is configured for
72 | CodeDeploy by creating a role that allows deployments.
73 |
74 | See: http://moonshot.readthedocs.io/en/latest/mechanisms/deployment/
75 | EOF
76 | end
77 | end
78 | end
79 | end
80 | end
81 |
--------------------------------------------------------------------------------
/spec/moonshot/command_restrictions.rb:
--------------------------------------------------------------------------------
1 | class RestrictedCommand1 < Moonshot::Command
2 | self.only_in_account = 'jeff'
3 |
4 | def execute
5 | puts "Yep, I ran!"
6 | end
7 | end
8 |
9 | class RestrictedCommand2 < Moonshot::Command
10 | self.only_in_account = [ 'jeff', 'panda' ]
11 |
12 | def execute
13 | puts "Yep, I ran!"
14 | end
15 | end
16 |
17 | class UnrestrictedCommand < Moonshot::Command
18 | def execute
19 | puts "Yep, I ran!"
20 | end
21 | end
22 |
23 | describe 'command account restrictions' do
24 | def try(klass)
25 | Moonshot::CommandLineDispatcher.new('stuff', klass, [])
26 | .dispatch!
27 | end
28 |
29 | before(:each) do
30 | Moonshot::AccountContext.set(account_name)
31 | ARGV.clear
32 | end
33 |
34 | context 'in the "jeff" account' do
35 | let(:account_name) { 'jeff' }
36 |
37 | it 'should let us run RestrictedCommand1' do
38 | expect { try(RestrictedCommand1) }
39 | .to output(/Yep, I ran!/).to_stdout
40 | end
41 |
42 | it 'should let us run RestrictedCommand2' do
43 | expect { try(RestrictedCommand2) }
44 | .to output(/Yep, I ran!/).to_stdout
45 | end
46 |
47 | it 'should let us run UnrestrictedCommand' do
48 | expect { try(UnrestrictedCommand) }
49 | .to output(/Yep, I ran!/).to_stdout
50 | end
51 | end
52 |
53 | context 'in the "panda" account' do
54 | let(:account_name) { 'panda' }
55 |
56 | it 'should not let us run RestrictedCommand1' do
57 | expect { try(RestrictedCommand1) }
58 | .to raise_error(/Command account restriction/)
59 | .and output(/can only be run/).to_stderr
60 | end
61 |
62 | it 'should let us run RestrictedCommand2' do
63 | expect { try(RestrictedCommand2) }
64 | .to output(/Yep, I ran!/).to_stdout
65 | end
66 |
67 | it 'should let us run UnrestrictedCommand' do
68 | expect { try(UnrestrictedCommand) }
69 | .to output(/Yep, I ran!/).to_stdout
70 | end
71 | end
72 |
73 | context 'in the "carrot" account' do
74 | let(:account_name) { 'carrot' }
75 |
76 | it 'should not let us run RestrictedCommand1' do
77 | expect { try(RestrictedCommand1) }
78 | .to raise_error(/Command account restriction/)
79 | .and output(/can only be run/).to_stderr
80 | end
81 |
82 | it 'should not let us run RestrictedCommand2' do
83 | expect { try(RestrictedCommand2) }
84 | .to raise_error(/Command account restriction/)
85 | .and output(/can only be run/).to_stderr
86 | end
87 |
88 | it 'should let us run UnrestrictedCommand' do
89 | expect { try(UnrestrictedCommand) }
90 | .to output(/Yep, I ran!/).to_stdout
91 | end
92 | end
93 | end
94 |
--------------------------------------------------------------------------------
/lib/moonshot/shell.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'thor'
4 | require 'retriable'
5 |
6 | # Mixin providing the Thor::Shell methods and other shell execution helpers.
7 | module Moonshot::Shell
8 | CommandError = Class.new(RuntimeError)
9 |
10 | # Retry every 10 seconds for a maximum of 2 minutes.
11 | DEFAULT_RETRY_OPTIONS = {
12 | on: RuntimeError,
13 | tries: 50,
14 | multiplier: 1,
15 | base_interval: 10,
16 | max_elapsed_time: 120,
17 | rand_factor: 0
18 | }.freeze
19 |
20 | DEFAULT_TERMINAL_WIDTH = 80
21 |
22 | # Run a command, returning stdout. Stderr is suppressed unless the command
23 | # returns non-zero.
24 | def sh_out(cmd, fail = true, stdin = '')
25 | r_in, w_in = IO.pipe
26 | r_out, w_out = IO.pipe
27 | r_err, w_err = IO.pipe
28 | w_in.write(stdin)
29 | w_in.close
30 | pid = Process.spawn(cmd, in: r_in, out: w_out, err: w_err)
31 | Process.wait(pid)
32 |
33 | r_in.close
34 | w_out.close
35 | w_err.close
36 | stdout = r_out.read
37 | r_out.close
38 | stderr = r_err.read
39 | r_err.close
40 |
41 | if fail && $CHILD_STATUS.exitstatus != 0
42 | raise CommandError, "`#{cmd}` exited #{$CHILD_STATUS.exitstatus}\n" \
43 | "stdout:\n" \
44 | "#{stdout}\n" \
45 | "stderr:\n" \
46 | "#{stderr}\n"
47 | end
48 | stdout
49 | end
50 | module_function :sh_out
51 |
52 | def shell
53 | @thor_shell ||= Thor::Base.shell.new
54 | end
55 |
56 | def terminal_width
57 | Thor::Shell::Terminal.terminal_width || DEFAULT_TERMINAL_WIDTH
58 | end
59 |
60 | Thor::Shell::Basic.public_instance_methods(false).each do |meth|
61 | define_method(meth) { |*args| shell.public_send(meth, *args) }
62 | end
63 |
64 | def sh_step(cmd, **args)
65 | msg = args.delete(:msg) || cmd
66 | msg = "#{msg[0..(terminal_width - 22)]}..." if msg.length > (terminal_width - 18)
67 | ilog.start_threaded(msg) do |step|
68 | out = sh_out(cmd, **args)
69 | yield step, out if block_given?
70 | step.success
71 | end
72 | end
73 |
74 | # Retries every second upto maximum of 2 minutes with the default options.
75 | #
76 | # @param cmd [String] command to execute.
77 | # @param fail [Boolean] Raise error when the command exits with non-zero.
78 | # @param stdin [String] Input to the command.
79 | # @param opts [Hash] Options for retriable.
80 | #
81 | # @return [String] Stdout form the command.
82 | def sh_retry(cmd, fail = true, stdin = '', opts: {})
83 | Retriable.retriable(DEFAULT_RETRY_OPTIONS.merge(opts)) do
84 | out = sh_out(cmd, stdin:)
85 | yield out if block_given?
86 | out
87 | end
88 | rescue CommandError => e
89 | raise e if fail
90 | end
91 | end
92 |
--------------------------------------------------------------------------------
/lib/moonshot/artifact_repository/s3_bucket.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative '../resources_helper'
4 | require_relative '../creds_helper'
5 | require_relative '../doctor_helper'
6 |
7 | # The S3Bucket stores builds in an S3 Bucket.
8 | #
9 | # For example:
10 | #
11 | # def MyApplication < Moonshot::CLI
12 | # self.artifact_repository = S3Bucket.new('my-application-builds')
13 | # end
14 | class Moonshot::ArtifactRepository::S3Bucket
15 | include Moonshot::ResourcesHelper
16 | include Moonshot::CredsHelper
17 | include Moonshot::DoctorHelper
18 |
19 | attr_reader :bucket_name
20 |
21 | def initialize(bucket_name, prefix: '')
22 | @bucket_name = bucket_name
23 | @prefix = prefix
24 | end
25 |
26 | def store_hook(build_mechanism, version_name)
27 | unless build_mechanism.respond_to?(:output_file)
28 | raise "S3Bucket does not know how to store artifacts from #{build_mechanism.class}, no method '#output_file'."
29 | end
30 |
31 | file = build_mechanism.output_file
32 | bucket_name = @bucket_name
33 | key = filename_for_version(version_name)
34 |
35 | ilog.start_threaded "Uploading #{file} to s3://#{bucket_name}/#{key}" do |s|
36 | upload_to_s3(file, key)
37 | s.success "Uploaded s3://#{bucket_name}/#{key} successfully."
38 | end
39 | end
40 |
41 | def filename_for_version(version_name)
42 | "#{@prefix}#{version_name}.tar.gz"
43 | end
44 |
45 | private
46 |
47 | def upload_to_s3(file, key)
48 | s3_client.put_object(
49 | acl: 'bucket-owner-full-control',
50 | key:,
51 | body: File.open(file),
52 | bucket: @bucket_name,
53 | storage_class: 'STANDARD_IA'
54 | )
55 | end
56 |
57 | def doctor_check_bucket_exists
58 | s3_client.get_bucket_location(bucket: @bucket_name)
59 | success "Bucket '#{@bucket_name}' exists."
60 | rescue StandardError => e
61 | # This is warning because the role you use for deployment may not actually
62 | # be able to read builds, however the instance role assigned to the nodes
63 | # might.
64 | str = "Could not get information about bucket '#{@bucket_name}'."
65 | warning(str, e.message)
66 | end
67 |
68 | def doctor_check_bucket_writable
69 | s3_client.put_object(
70 | key: 'test-object',
71 | body: '',
72 | bucket: @bucket_name,
73 | storage_class: 'REDUCED_REDUNDANCY'
74 | )
75 | s3_client.delete_object(key: 'test-object', bucket: @bucket_name)
76 | success 'Bucket is writable, new builds can be uploaded.'
77 | rescue StandardError => e
78 | # This is a warning because you may deploy to an environment where you have
79 | # read access to builds, but could not publish a new build.
80 | warning('Could not write to bucket, you may still be able to deploy existing builds.', e.message)
81 | end
82 | end
83 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/load-balancing/elb/README.md:
--------------------------------------------------------------------------------
1 | # ELB and ASG lifecycle event scripts
2 |
3 | Often when running a web service, you'll have your instances behind a load balancer. But when
4 | deploying new code to these instances, you don't want the load balancer to continue sending customer
5 | traffic to an instance while the deployment is in progress. Lifecycle event scripts give you the
6 | ability to integrate your AWS CodeDeploy deployments with instances that are behind an Elastic Load
7 | Balancer or in an Auto Scaling group. Simply set the name (or names) of the Elastic Load Balancer
8 | your instances are a part of, set the scripts in the appropriate lifecycle events, and the scripts
9 | will take care of deregistering the instance, waiting for connection draining, and re-registering
10 | after the deployment finishes.
11 |
12 | ## Requirements
13 |
14 | The register and deregister scripts have a couple of dependencies in order to properly interact with
15 | Elastic Load Balancing and AutoScaling:
16 |
17 | 1. The [AWS CLI](http://aws.amazon.com/cli/). In order to take advantage of
18 | AutoScaling's Standby feature, the CLI must be at least version 1.3.25. If you
19 | have Python and PIP already installed, the CLI can simply be installed with `pip
20 | install awscli`. Otherwise, follow the [installation instructions](http://docs.aws.amazon.com/cli/latest/userguide/installing.html)
21 | in the CLI's user guide.
22 | 1. An instance profile with a policy that allows, at minimum, the following actions:
23 |
24 | ```
25 | elasticloadbalancing:Describe*
26 | elasticloadbalancing:DeregisterInstancesFromLoadBalancer
27 | elasticloadbalancing:RegisterInstancesWithLoadBalancer
28 | autoscaling:Describe*
29 | autoscaling:EnterStandby
30 | autoscaling:ExitStandby
31 | autoscaling:UpdateAutoScalingGroup
32 | ```
33 |
34 | Note: the AWS CodeDeploy Agent requires that an instance profile be attached to all instances that
35 | are to participate in AWS CodeDeploy deployments. For more information on creating an instance
36 | profile for AWS CodeDeploy, see the [Create an IAM Instance Profile for Your Amazon EC2 Instances]()
37 | topic in the documentation.
38 | 1. All instances are assumed to already have the AWS CodeDeploy Agent installed.
39 |
40 | ## Installing the Scripts
41 |
42 | To use these scripts in your own application:
43 |
44 | 1. Install the AWS CLI on all your instances.
45 | 1. Update the policies on the EC2 instance profile to allow the above actions.
46 | 1. Copy the `.sh` files in this directory into your application source.
47 | 1. Edit your application's `appspec.yml` to run `deregister_from_elb.sh` on the ApplicationStop event,
48 | and `register_with_elb.sh` on the ApplicationStart event.
49 | 1. Edit `common_functions.sh` to set `ELB_LIST` to contain the name(s) of the Elastic Load
50 | Balancer(s) your deployment group is a part of. Make sure the entries in ELB_LIST are separated by space.
51 | 1. Deploy!
52 |
53 |
--------------------------------------------------------------------------------
/lib/moonshot/change_set.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Moonshot
4 | class ChangeSet
5 | attr_reader :name, :stack_name
6 |
7 | def initialize(name, stack_name)
8 | @name = name
9 | @stack_name = stack_name
10 | @change_set = nil
11 | @cf_client = Aws::CloudFormation::Client.new
12 | end
13 |
14 | def confirm?
15 | raise 'Cannot confirm ChangeSet when interactive mode is disabled!' unless Moonshot.config.interactive
16 |
17 | loop do
18 | print 'Apply changes? '
19 | resp = gets.chomp.downcase
20 |
21 | return true if resp == 'yes'
22 | return false if resp == 'no'
23 |
24 | puts "Please enter 'yes' or 'no'!"
25 | end
26 | end
27 |
28 | def valid?
29 | @change_set.status == 'CREATE_COMPLETE'
30 | end
31 |
32 | def invalid_reason
33 | @change_set.status_reason
34 | end
35 |
36 | def display_changes
37 | wait_for_change_set unless @change_set
38 |
39 | @change_set.changes.map(&:resource_change).each do |c|
40 | puts "* #{c.action} #{c.logical_resource_id} (#{c.resource_type})"
41 |
42 | case c.replacement
43 | when 'True'
44 | puts ' - Will be replaced'
45 | when 'Conditional'
46 | puts ' - May be replaced (Conditional)'
47 | end
48 |
49 | c.details.each do |d|
50 | case d.change_source
51 | when 'ResourceReference', 'ParameterReference'
52 | puts " - Caused by #{d.causing_entity.blue} (#{d.change_source})"
53 | when 'DirectModification'
54 | puts " - Caused by template change (#{d.target.attribute}: #{d.target.name})"
55 | end
56 | end
57 | end
58 | end
59 |
60 | def execute
61 | wait_for_change_set unless @change_set
62 | @cf_client.execute_change_set(
63 | change_set_name: @name,
64 | stack_name: @stack_name
65 | )
66 | end
67 |
68 | def delete
69 | wait_for_change_set unless @change_set
70 | @cf_client.delete_change_set(
71 | change_set_name: @name,
72 | stack_name: @stack_name
73 | )
74 | rescue Aws::CloudFormation::Errors::InvalidChangeSetStatus
75 | sleep 1
76 | retry
77 | end
78 |
79 | def wait_for_change_set
80 | begin
81 | @cf_client.wait_until(:change_set_create_complete,
82 | stack_name: @stack_name,
83 | change_set_name: @name)
84 | rescue Aws::Waiters::Errors::FailureStateError => e
85 | if e.message != 'stopped waiting, encountered a failure state'
86 | throw e
87 | else
88 | puts "The change set didn't contain any new changes."
89 | end
90 | end
91 |
92 | @change_set = @cf_client.describe_change_set(stack_name: @stack_name,
93 | change_set_name: @name)
94 | end
95 | end
96 | end
97 |
--------------------------------------------------------------------------------
/docs/plugins/backup.md:
--------------------------------------------------------------------------------
1 | # Moonshot backup plugin
2 |
3 | Moonshot plugin for backing up config files.
4 |
5 | ## Functionality
6 |
7 | The plugin collects and deflates certain files to a single tarball,
8 | and uploads that to a a given S3 bucket. The whole process happens
9 | in memory, nothing is written to disk. The plugin currently supports single files only,
10 | including whole directories in your tarball is not possible yet.
11 |
12 | The plugin uses the Moonshot AWS config, meaning that the bucket must be
13 | present in the same account and region as your deployment.
14 |
15 | ## Basic usage
16 |
17 | When instantiating a class, you need to set the following options
18 | in a block, where the object is provided as a block argument:
19 |
20 | - `bucket`: the name of the S3 bucket you wish to upload the tarball (optional)
21 | - `buckets`: a hash map containing account aliases as keys, and target buckets as values (optional).
22 | - `files`: an array of relative path names as strings.
23 | - `backup_parameters`: boolean value for backing up all parameters into a YAML file (optional, defaults to `false`).
24 | - `backup_template`: boolean value for backing up the current CloudFormation template (optional, defaults to `false`).
25 | - `hooks`: which hooks to run the backup logic, works with all valid Moonshot hooks
26 | - `target_name`: tarball archive name (optional, defaults to `__.tar.gz`).
27 |
28 | You must provide either `bucket` or `buckets`, but **not both**.
29 |
30 | If you provide either `backup_parameters` or `backup_template` you may not provide `files` additionally.
31 |
32 | `pre_create` and `post_delete` hooks are **not** allowed to use due to certain implementation restrictions.
33 |
34 | ## Default method
35 |
36 | If you wish to back up only the current template and parameter files, you can simply
37 | use the factory method provided:
38 |
39 | ```ruby
40 | Moonshot.config do |c|
41 | # ...
42 | c.plugins << Moonshot::Plugins::Backup.to_bucket('your-bucket-name')
43 | ```
44 |
45 | ## Placeholders
46 |
47 | You can use the following placeholders both in your filenames
48 | and tarball target names (meanings are pretty self explaining):
49 |
50 | - `%{app_name}`
51 | - `%{stack_name}`
52 | - `%{timestamp}`
53 | - `%{user}`
54 |
55 | ## Example
56 |
57 | A possible use-case is backing up a CF template and/or
58 | parameter file after create or update.
59 |
60 | ```ruby
61 | c.plugins << Backup.new do |b|
62 | b.bucket = 'your-bucket-name'
63 |
64 | b.files = [
65 | 'cloud_formation/%{app_name}.json',
66 | 'cloud_formation/parameters/%{stack_name}.yml'
67 | ]
68 |
69 | b.hooks = [:post_create, :post_update]
70 | end
71 | ```
72 |
73 | ```ruby
74 | c.plugins << Backup.new do |b|
75 | b.buckets = {
76 | 'dev_account' => 'dev_bucket',
77 | 'prod_account' => 'prod_bucket'
78 | }
79 |
80 | b.backup_template = true
81 | b.backup_parameters = true
82 |
83 | b.hooks = [:post_create, :post_update]
84 | end
85 | ```
86 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | #
Moonshot
2 | _Because releasing services shouldn't be a moonshot._
3 |
4 | ## Overview
5 |
6 | Moonshot is a command line tool and library for provisioning and
7 | managing application environments using CloudFormation. It has native
8 | support for integration with S3 and CodeDeploy, as well. Other systems
9 | may be added using our pluggable system. The core components are:
10 |
11 | - A DeploymentMechanism controls releasing code. For example, Amazon
12 | CodeDeploy.
13 | - A BuildMechanism creates a release artifact. For example, a local
14 | shell script.
15 | - A ArtifactRepository stores the release artifacts. For example,
16 | Amazon S3.
17 |
18 | 
19 |
20 | ## Design Goals
21 |
22 | The goal of Moonshot is to wrap CloudFormation in a toolchain that
23 | codifies the deployment and management of a service. Our goal is that
24 | within a given service the Moonshot configuration, CloudFormation
25 | template, and supporting AWS services should be easily understood.
26 |
27 | Some of our original design goals were:
28 |
29 | - Simplicity: It shouldn't take more than a few hours to understand what your
30 | release tooling does.
31 | - Choice: As much as possible, each component should be pluggable and omittable,
32 | so teams are free to use what works best for them.
33 | - Verbosity: The output of core Moonshot code should explain in detail what
34 | changes are being made, so knowledge is shared and not abstracted.
35 |
36 | ## Installation
37 |
38 | You can install Moonshot for your local user with:
39 |
40 | $ gem install moonshot
41 |
42 | If you would prefer to manage your projects dependencies with Bundler,
43 | add the following to your Gemfile:
44 |
45 | gem 'moonshot'
46 |
47 | And then execute:
48 |
49 | $ bundle install
50 |
51 | After installation, there is still some work required. Follow
52 | the [example documentation](example.md) as described below to dig in!
53 |
54 | ## Getting started
55 |
56 | The Moonshot tool has been designed to be an extensible library for
57 | your specific use-case. We aren't trying to solve every use case, but
58 | rather give you an extensible toolkit that your project can grow with,
59 | without leaving your trapped behind rigid design philosophy.
60 | Interested in how it can be used? See our [example documentation][2].
61 | The example doc uses the files shown in the [sample directory][3] so
62 | you can figure out how to modify this for your own application.
63 |
64 | We also want to [help you contribute and answer all your questions][1]
65 | on how Moonshot is maintained.
66 |
67 | [1]: http://moonshot.readthedocs.org/en/latest/about/contribute
68 | [2]: example.md
69 | [3]: https://github.com/acquia/moonshot/tree/master/sample
70 |
71 | ## Requirements
72 |
73 | - Ruby 2.2 or higher
74 |
75 | ## Attributions
76 |
77 | Thanks to [Acquia Inc.](https://acquia.com) for sponsoring the time to work on this tool.
78 | Thanks to [Ted](https://github.com/tottey) for the funky logo.
79 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #
Moonshot [](http://moonshot.readthedocs.org/en/latest/?badge=latest)[](https://codeclimate.com/github/acquia/moonshot/coverage)[](https://codeclimate.com/github/acquia/moonshot)[](https://badge.fury.io/rb/moonshot)
2 | _Because releasing services shouldn't be a moonshot._
3 |
4 | ## Overview
5 |
6 | [We also have pretty docs, lots more to find there.](http://moonshot.readthedocs.org/en/latest/)
7 |
8 | Moonshot is a Ruby gem for provisioning environments in AWS using a CLI.
9 | The environments are centered around a single CloudFormation stack and supported
10 | by pluggable systems:
11 |
12 | - A DeploymentMechanism controls releasing code.
13 | - A BuildMechanism creates a release artifact.
14 | - A ArtifactRepository stores the release artifacts.
15 |
16 | 
17 |
18 | ## Design Goals
19 |
20 | These are core ideas to the creation of this project. Not all are met to the
21 | level we'd like (e.g. CloudFormation isn't much of a Choice currently), but we
22 | should aspire to meet them with each iteration.
23 |
24 | - Simplicity: It shouldn't take more than a few hours to understand what your
25 | release tooling does.
26 | - Choice: As much as possible, each component should be pluggable and omittable,
27 | so teams are free to use what works best for them.
28 | - Verbosity: The output of core Moonshot code should explain in detail what
29 | changes are being made, so knowledge is shared and not abstracted.
30 |
31 | ## Existing limitations
32 |
33 | - Moonshot does not support detailed error logging from Cloudformation substacks.
34 | - Moonshot does not support a non-local cloudformation file.
35 |
36 | ## Installation
37 |
38 | Install the Moonshot gem:
39 |
40 | ```shell
41 | $ gem install moonshot
42 | ```
43 |
44 | After installation, there is still some work required. Follow the [example documentation](docs/example.md) as described below to dig in!
45 |
46 | ## Getting started
47 |
48 | The Moonshot tool has been designed to be an extensible library for your
49 | specific use-case. Interested in how it can be used? See our [example
50 | documentation](http://moonshot.readthedocs.org/en/latest/example). The example
51 | doc uses the files shown in the [sample
52 | directory](https://github.com/acquia/moonshot/tree/master/sample) so you can
53 | figure out how to modify this for your own deployment strategy.
54 |
55 | We also want to [help you contribute and answer all your questions](http://moonshot.readthedocs.org/en/latest/about/contribute) on how Moonshot is maintained.
56 |
57 | ## Requirements
58 |
59 | - Ruby 2.2 or higher
60 |
61 | ## Attributions
62 |
63 | Thanks to [Acquia Inc.](https://acquia.com) for sponsoring the time to work on this tool.
64 | Thanks to [Ted](https://github.com/tottey) for the funky logo.
65 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/load-balancing/elb/register_with_elb.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License").
6 | # You may not use this file except in compliance with the License.
7 | # A copy of the License is located at
8 | #
9 | # http://aws.amazon.com/apache2.0
10 | #
11 | # or in the "license" file accompanying this file. This file is distributed
12 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
13 | # express or implied. See the License for the specific language governing
14 | # permissions and limitations under the License.
15 |
16 | . $(dirname $0)/common_functions.sh
17 |
18 | msg "Running AWS CLI with region: $(get_instance_region)"
19 |
20 | # get this instance's ID
21 | INSTANCE_ID=$(get_instance_id)
22 | if [ $? != 0 -o -z "$INSTANCE_ID" ]; then
23 | error_exit "Unable to get this instance's ID; cannot continue."
24 | fi
25 |
26 | # Get current time
27 | msg "Started $(basename $0) at $(/bin/date "+%F %T")"
28 | start_sec=$(/bin/date +%s.%N)
29 |
30 | msg "Checking if instance $INSTANCE_ID is part of an AutoScaling group"
31 | asg=$(autoscaling_group_name $INSTANCE_ID)
32 | if [ $? == 0 -a -n "$asg" ]; then
33 | msg "Found AutoScaling group for instance $INSTANCE_ID: $asg"
34 |
35 | msg "Checking that installed CLI version is at least at version required for AutoScaling Standby"
36 | check_cli_version
37 | if [ $? != 0 ]; then
38 | error_exit "CLI must be at least version ${MIN_CLI_X}.${MIN_CLI_Y}.${MIN_CLI_Z} to work with AutoScaling Standby"
39 | fi
40 |
41 | msg "Attempting to move instance out of Standby"
42 | autoscaling_exit_standby $INSTANCE_ID $asg
43 | if [ $? != 0 ]; then
44 | error_exit "Failed to move instance out of standby"
45 | else
46 | msg "Instance is no longer in Standby"
47 | exit 0
48 | fi
49 | fi
50 |
51 | msg "Instance is not part of an ASG, continuing..."
52 |
53 | msg "Checking that user set at least one load balancer"
54 | if test -z "$ELB_LIST"; then
55 | error_exit "Must have at least one load balancer to register to"
56 | fi
57 |
58 | # Loop through all LBs the user set, and attempt to register this instance to them.
59 | for elb in $ELB_LIST; do
60 | msg "Checking validity of load balancer named '$elb'"
61 | validate_elb $INSTANCE_ID $elb
62 | if [ $? != 0 ]; then
63 | msg "Error validating $elb; cannot continue with this LB"
64 | continue
65 | fi
66 |
67 | msg "Registering $INSTANCE_ID to $elb"
68 | register_instance $INSTANCE_ID $elb
69 |
70 | if [ $? != 0 ]; then
71 | error_exit "Failed to register instance $INSTANCE_ID from ELB $elb"
72 | fi
73 | done
74 |
75 | # Wait for all Registrations to finish
76 | msg "Waiting for instance to register to its load balancers"
77 | for elb in $ELB_LIST; do
78 | wait_for_state "elb" $INSTANCE_ID "InService" $elb
79 | if [ $? != 0 ]; then
80 | error_exit "Failed waiting for $INSTANCE_ID to return to $elb"
81 | fi
82 | done
83 |
84 | msg "Finished $(basename $0) at $(/bin/date "+%F %T")"
85 |
86 | end_sec=$(/bin/date +%s.%N)
87 | elapsed_seconds=$(echo "$end_sec - $start_sec" | /usr/bin/bc)
88 |
89 | msg "Elapsed time: $elapsed_seconds"
90 |
--------------------------------------------------------------------------------
/sample/bin/aws-codedeploy-samples/load-balancing/elb/deregister_from_elb.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License").
6 | # You may not use this file except in compliance with the License.
7 | # A copy of the License is located at
8 | #
9 | # http://aws.amazon.com/apache2.0
10 | #
11 | # or in the "license" file accompanying this file. This file is distributed
12 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
13 | # express or implied. See the License for the specific language governing
14 | # permissions and limitations under the License.
15 |
16 | . $(dirname $0)/common_functions.sh
17 |
18 | msg "Running AWS CLI with region: $(get_instance_region)"
19 |
20 | # get this instance's ID
21 | INSTANCE_ID=$(get_instance_id)
22 | if [ $? != 0 -o -z "$INSTANCE_ID" ]; then
23 | error_exit "Unable to get this instance's ID; cannot continue."
24 | fi
25 |
26 | # Get current time
27 | msg "Started $(basename $0) at $(/bin/date "+%F %T")"
28 | start_sec=$(/bin/date +%s.%N)
29 |
30 | msg "Checking if instance $INSTANCE_ID is part of an AutoScaling group"
31 | asg=$(autoscaling_group_name $INSTANCE_ID)
32 | if [ $? == 0 -a -n "$asg" ]; then
33 | msg "Found AutoScaling group for instance $INSTANCE_ID: $asg"
34 |
35 | msg "Checking that installed CLI version is at least at version required for AutoScaling Standby"
36 | check_cli_version
37 | if [ $? != 0 ]; then
38 | error_exit "CLI must be at least version ${MIN_CLI_X}.${MIN_CLI_Y}.${MIN_CLI_Z} to work with AutoScaling Standby"
39 | fi
40 |
41 | msg "Attempting to put instance into Standby"
42 | autoscaling_enter_standby $INSTANCE_ID $asg
43 | if [ $? != 0 ]; then
44 | error_exit "Failed to move instance into standby"
45 | else
46 | msg "Instance is in standby"
47 | exit 0
48 | fi
49 | fi
50 |
51 | msg "Instance is not part of an ASG, continuing..."
52 |
53 | msg "Checking that user set at least one load balancer"
54 | if test -z "$ELB_LIST"; then
55 | error_exit "Must have at least one load balancer to deregister from"
56 | fi
57 |
58 | # Loop through all LBs the user set, and attempt to deregister this instance from them.
59 | for elb in $ELB_LIST; do
60 | msg "Checking validity of load balancer named '$elb'"
61 | validate_elb $INSTANCE_ID $elb
62 | if [ $? != 0 ]; then
63 | msg "Error validating $elb; cannot continue with this LB"
64 | continue
65 | fi
66 |
67 | msg "Deregistering $INSTANCE_ID from $elb"
68 | deregister_instance $INSTANCE_ID $elb
69 |
70 | if [ $? != 0 ]; then
71 | error_exit "Failed to deregister instance $INSTANCE_ID from ELB $elb"
72 | fi
73 | done
74 |
75 | # Wait for all Deregistrations to finish
76 | msg "Waiting for instance to de-register from its load balancers"
77 | for elb in $ELB_LIST; do
78 | wait_for_state "elb" $INSTANCE_ID "OutOfService" $elb
79 | if [ $? != 0 ]; then
80 | error_exit "Failed waiting for $INSTANCE_ID to leave $elb"
81 | fi
82 | done
83 |
84 | msg "Finished $(basename $0) at $(/bin/date "+%F %T")"
85 |
86 | end_sec=$(/bin/date +%s.%N)
87 | elapsed_seconds=$(echo "$end_sec - $start_sec" | /usr/bin/bc)
88 |
89 | msg "Elapsed time: $elapsed_seconds"
90 |
--------------------------------------------------------------------------------
/docs/plugins.md:
--------------------------------------------------------------------------------
1 | # Plugin Support
2 |
3 | **Warning, the plugin support in Moonshot is a work-in-progress. The interface
4 | to plugins may change dramatically in future versions.**
5 |
6 |
7 | Moonshot supports adding plugins (implemented as a Ruby class) to the
8 | controller that can perform actions before and after the `create`,
9 | `update`, `delete`, `deploy`, `status`, `doctor` and `ssh` actions.
10 |
11 | ## Writing a Moonshot Plugin
12 |
13 | A Moonshot Plugin is a Ruby class that responds to one or more of the following
14 | methods:
15 |
16 | - pre_create
17 | - post_create
18 | - pre_update
19 | - post_update
20 | - pre_delete
21 | - post_delete
22 | - pre_deploy
23 | - post_deploy
24 | - pre_status
25 | - post_status
26 | - pre_doctor
27 | - post_doctor
28 | - pre_ssh
29 | - post_ssh
30 | - setup_create
31 | - setup_update
32 | - setup_status
33 | - setup_build
34 | - setup_deploy
35 | - setup_delete
36 |
37 |
38 | The method will be handed a single argument, which is an instance of the
39 | `Moonshot::Resources` class. This instance gives the plugin access to three
40 | important resources:
41 |
42 | - `Moonshot::Resources#ilog` is an instance of `InteractiveLogger`, used to
43 | display status to the user of the CLI interface.
44 | - `Moonshot::Resources#stack` is an instance of `Moonshot::Stack` which can
45 | retreive the name of the stack, stack parameters and stack outputs. This support
46 | should be expanded in the future to provide Plugins with more control over the
47 | CloudFormation stack.
48 |
49 | `setup` hooks work a bit differently. They are invoked before any other business
50 | logic is executed. This means no resource objects are actually instantiated,
51 | therefore no parameters are passed in this case.
52 |
53 | ## Manipulating CLI options with Plugins
54 |
55 | If you wish to modify the options accepted by a core Moonshot command
56 | in order to affect the pre/post hooks defined in your plugin,
57 | implement a method called `_cli_hook`. This hook will be
58 | passed an instance of OptionParser, which you can manipulate and
59 | return. For example:
60 |
61 | ```ruby
62 | class MyPlugin
63 | def pre_build(_)
64 | puts "FULL SPEED AHEAD!!!!" if @hyperdrive
65 | end
66 |
67 | def build_cli_hook(parser)
68 | parser.on('--foo', '-F', TrueClass, 'ENABLE HYPERDRIVE') do |v|
69 | @hyperdrive = v
70 | end
71 | end
72 | end
73 | ```
74 |
75 | With this plugin, the output of `moonshot build --help` reflects the
76 | new command line option:
77 |
78 | ```
79 | Usage: moonshot build VERSION
80 | -v, --[no-]verbose Show debug logging
81 | -s, --skip-ci-status Skip checks on CI jobs
82 | -n, --environment=NAME Which environment to operate on.
83 | --[no-]interactive-logger Enable or disable fancy logging
84 | -F, --foo ENABLE HYPERDRIVE
85 | ```
86 |
87 | ## Adding a plugin to Moonshot
88 |
89 | Once you have defined or included your plugin class, you can add a
90 | plugin by modifying your `Moonfile.rb` file, like so:
91 |
92 | ```ruby
93 | Moonshot.config do |c|
94 | c.app_name = 'my-app'
95 | # ...
96 | c.plugins << MyPlugin.new
97 | end
98 | ```
99 |
100 | ## Auto-loading Plugin Source
101 |
102 | The Moonshot CLI tool will auto-load plugin source in the path
103 | `moonshot/plugins/**/*.rb` relative to the `Moonfile.rb` file for your
104 | project. This can be useful for plugins that define project-specific
105 | behaviors.
106 |
--------------------------------------------------------------------------------