From d5640b2897dd781cdb4ca212d29502f83d13473b Mon Sep 17 00:00:00 2001 From: Kyle Smith Date: Sun, 9 Oct 2016 20:11:45 -0400 Subject: [PATCH] Moonshot 1.0.0 - multiple features. (#139) Moonshot 1.0.0 - multiple features. --- .rubocop.yml | 2 + README.md | 21 +- bin/moonshot | 10 + docs/about/contribute.md | 36 ++- docs/example.md | 39 ++-- docs/index.md | 27 ++- docs/mechanisms/artifact-repository.md | 18 +- docs/mechanisms/build.md | 43 ++-- docs/mechanisms/deployment.md | 39 ++-- docs/plugins.md | 23 +- docs/plugins/asg_rollout.md | 18 +- docs/plugins/backup.md | 16 +- docs/user-guide/cli.md | 89 +++---- docs/user-guide/include_in_your_project.md | 39 ++-- docs/user-guide/stack_parameter_strategies.md | 11 +- lib/moonshot.rb | 31 ++- .../build_mechanism/github_release.rb | 2 +- lib/moonshot/build_mechanism/script.rb | 2 +- lib/moonshot/cli.rb | 219 ------------------ lib/moonshot/command.rb | 64 +++++ lib/moonshot/command_line.rb | 121 ++++++++++ lib/moonshot/commands/build.rb | 12 + lib/moonshot/commands/console.rb | 19 ++ lib/moonshot/commands/create.rb | 31 +++ lib/moonshot/commands/delete.rb | 12 + lib/moonshot/commands/deploy.rb | 12 + lib/moonshot/commands/doctor.rb | 12 + lib/moonshot/commands/list.rb | 12 + lib/moonshot/commands/push.rb | 12 + lib/moonshot/commands/ssh.rb | 12 + lib/moonshot/commands/status.rb | 12 + lib/moonshot/commands/update.rb | 32 +++ lib/moonshot/config.rb | 0 lib/moonshot/controller.rb | 12 +- lib/moonshot/controller_config.rb | 3 +- .../deployment_mechanism/code_deploy.rb | 8 +- lib/moonshot/resources.rb | 5 +- lib/moonshot/resources_helper.rb | 4 +- lib/moonshot/ssh_command.rb | 31 +++ lib/moonshot/ssh_config.rb | 2 +- lib/moonshot/stack.rb | 19 +- lib/moonshot/stack_lister.rb | 5 +- lib/moonshot/stack_template.rb | 3 +- lib/moonshot/task.rb | 10 + lib/moonshot/tools/asg_rollout.rb | 2 +- .../tools/asg_rollout/instance_health.rb | 2 +- lib/moonshot/tools/asg_rollout_config.rb | 2 +- moonshot.gemspec | 11 +- sample/Moonfile | 7 + sample/bin/environment | 14 -- .../build_mechanism/github_release_spec.rb | 1 - .../build_mechanism/travis_deploy_spec.rb | 1 - spec/moonshot/cli_spec.rb | 70 ------ spec/moonshot/plugins_spec.rb | 22 -- spec/moonshot/shell_spec.rb | 2 - spec/moonshot/ssh_spec.rb | 11 +- spec/moonshot/stack_spec.rb | 4 +- 57 files changed, 720 insertions(+), 579 deletions(-) create mode 100755 bin/moonshot delete mode 100644 lib/moonshot/cli.rb create mode 100644 lib/moonshot/command.rb create mode 100644 lib/moonshot/command_line.rb create mode 100644 lib/moonshot/commands/build.rb create mode 100644 lib/moonshot/commands/console.rb create mode 100644 lib/moonshot/commands/create.rb create mode 100644 lib/moonshot/commands/delete.rb create mode 100644 lib/moonshot/commands/deploy.rb create mode 100644 lib/moonshot/commands/doctor.rb create mode 100644 lib/moonshot/commands/list.rb create mode 100644 lib/moonshot/commands/push.rb create mode 100644 lib/moonshot/commands/ssh.rb create mode 100644 lib/moonshot/commands/status.rb create mode 100644 lib/moonshot/commands/update.rb create mode 100644 lib/moonshot/config.rb create mode 100644 lib/moonshot/ssh_command.rb create mode 100644 lib/moonshot/task.rb create mode 100644 sample/Moonfile delete mode 100755 sample/bin/environment delete mode 100644 spec/moonshot/cli_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index 17d47527..8e798276 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -16,3 +16,5 @@ Style/ClassAndModuleChildren: Enabled: false Metrics/ClassLength: Max: 130 +Style/Documentation: + Enabled: false diff --git a/README.md b/README.md index 27bb5e52..7c0ce551 100644 --- a/README.md +++ b/README.md @@ -35,23 +35,22 @@ should aspire to meet them with each iteration. ## Installation -Add this line to your application's Gemfile: +Install the Moonshot gem: - gem 'moonshot' - -And then execute: - - $ bundle install - -Or install it yourself as: - - $ gem install moonshot +```shell +$ gem install moonshot +``` After installation, there is still some work required. Follow the [example documentation](docs/example.md) as described below to dig in! ## Getting started -The Moonshot tool has been designed to be an extensible library for your specific use-case. Interested in how it can be used? See our [example documentation](http://moonshot.readthedocs.org/en/latest/example). The example doc uses the files shown in the [sample directory](https://github.com/acquia/moonshot/tree/master/sample) so you can figure out how to modify this for your own deployment strategy. +The Moonshot tool has been designed to be an extensible library for your +specific use-case. Interested in how it can be used? See our [example +documentation](http://moonshot.readthedocs.org/en/latest/example). The example +doc uses the files shown in the [sample +directory](https://github.com/acquia/moonshot/tree/master/sample) so you can +figure out how to modify this for your own deployment strategy. 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. diff --git a/bin/moonshot b/bin/moonshot new file mode 100755 index 00000000..c57b9e6d --- /dev/null +++ b/bin/moonshot @@ -0,0 +1,10 @@ +#!/usr/bin/env ruby +require 'moonshot' + +# This is the main entry point for the `moonshot` command-line tool. +begin + Moonshot::CommandLine.run! +rescue => e + warn "#{e} (at #{e.backtrace.first})" + exit 1 +end diff --git a/docs/about/contribute.md b/docs/about/contribute.md index 0564592c..4272dadf 100644 --- a/docs/about/contribute.md +++ b/docs/about/contribute.md @@ -1,7 +1,9 @@ # Contributing to Moonshot An introduction to contributing to the Moonshot project. -The Moonshot project welcomes, and depends, on contributions from developers and users in the open source community. Contributions can be made in a number of ways, a few examples are: +The Moonshot project welcomes, and depends, on contributions from +developers and users in the open source community. Contributions can +be made in a number of ways, a few examples are: - Code patches via pull requests - Documentation improvements @@ -10,15 +12,33 @@ The Moonshot project welcomes, and depends, on contributions from developers and ## How to Contribute -So, you want to help? Awesome! In order to guide this the best way we can and to make sure we can help you in either getting a bug fixed or improve our documentation. we are asking you to please include as much detail as you can. Let us know your platform and Moonshot version. If the problem is visual please add a screenshot and if you get an error please include the full error and traceback. +So, you want to help? Awesome! In order to guide this the best way we +can and to make sure we can help you in either getting a bug fixed or +improve our documentation. we are asking you to please include as much +detail as you can. Let us know your platform and Moonshot version. If +the problem is visual please add a screenshot and if you get an error +please include the full error and traceback. ## Submitting Pull Requests -Once you are happy with your changes or you are ready for some feedback, push it to your fork and send a pull request. For a change to be accepted it will most likely need to have tests and documentation if it is a new feature. +Once you are happy with your changes or you are ready for some +feedback, push it to your fork and send a pull request. For a change +to be accepted it will most likely need to have tests and +documentation if it is a new feature. ## Expectations -Even though this is an Open project, it does not mean that we have 24/7 support for it. The best way to make sure that we accept your bugfix or add in a feature you want is still to make a pull request and link it in the issue. Include some reasons how to reproduce or even better, make sure the bug is tested and passes highy increases your chances for the change to get committed. - -So, you've done all that? It very likely that it could get committed really quickly but since some of the contributers to this project could be on a holiday or we are in no way responsible in actually merging it in soon. But have no fear! Since you filed a pull request from your fork, you are able to use a version of moonshot yourselves. And we promise you, we will do our utter best. - -And again, thanks! We're looking forward working with you. \ No newline at end of file +Even though this is an Open project, it does not mean that we have +24/7 support for it. The best way to make sure that we accept your +bugfix or add in a feature you want is still to make a pull request +and link it in the issue. Include some reasons how to reproduce or +even better, make sure the bug is tested and passes highy increases +your chances for the change to get committed. + +So, you've done all that? It very likely that it could get committed +really quickly but since some of the contributers to this project +could be on a holiday or we are in no way responsible in actually +merging it in soon. But have no fear! Since you filed a pull request +from your fork, you are able to use a version of moonshot +yourselves. And we promise you, we will do our utter best. + +And again, thanks! We're looking forward working with you. diff --git a/docs/example.md b/docs/example.md index 2af46169..b808d043 100644 --- a/docs/example.md +++ b/docs/example.md @@ -9,14 +9,14 @@ After setup, you will be able to repeatedly deploy a PHP app to one or more isolated environments on AWS by running the following command: ```shell -bundle exec bin/environment deploy-code +$ moonshot push ``` You will also be able to update the OS and supporting software by pulling the latest changes in this repository and updating the stack with following command: ```shell -bundle exec bin/environment update +$ moonshot update ``` Lastly, you get a disposable, light-weight application that can be used to learn @@ -30,7 +30,7 @@ matter. Create a role called CodeDeployRole with the AWSCodeDeployRole policy ```bash -aws iam create-role --role-name CodeDeployRole --assume-role-policy-document '{ +$ aws iam create-role --role-name CodeDeployRole --assume-role-policy-document '{ "Version": "2012-10-17", "Statement": [ { @@ -45,7 +45,7 @@ aws iam create-role --role-name CodeDeployRole --assume-role-policy-document '{ } ] }' -aws iam attach-role-policy --role-name CodeDeployRole --policy-arn arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole +$ aws iam attach-role-policy --role-name CodeDeployRole --policy-arn arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole ``` If you wish to do this manually, follow the @@ -55,10 +55,10 @@ what Moonshot expects. ### Install Moonshot and it's dependencies. -This step assumes that you have [Bundler](http://bundler.io/) and a modern version of Ruby installed on your system. See Moonshot's [requirements](index.md#requirements) +Moonshot is released as a Ruby gem, and required Ruby 2.1+. ```shell -bundle install +$ gem install moonshot ``` ### Create an S3 bucket and update the sample tools. @@ -68,7 +68,7 @@ First, create your own bucket to put your artifacts in: $ aws s3api create-bucket --bucket moonshot-sample-your-name ``` -Then update `bin/environment` to refer to that bucket in the `S3Bucket` configuration. +Then update `Moonfile.rb` to refer to that bucket in the `S3Bucket` configuration. ### Create a configuration for your stack. @@ -83,61 +83,62 @@ $ cp cloud_formation/parameters/moonshot-sample-app.yml cloud_formation/paramete ## Usage of the CLI Run the following commands to create your environment and deploy code to it. -Note that you will have to set the `AWS_REGION` environment variable prior to running these commands. If it's not set, it will use the default AWS region which at the time of this writing is us-east-1. +Note that you will have to set the `AWS_REGION` environment variable prior to +running these commands. If it's not set, it will use the default AWS region +which at the time of this writing is us-east-1. A detailed explanation of [all the CLI commands can be found in the User Guide](user-guide/cli.md) You can now deploy your software to a new stack with: ```shell -$ ./bin/environment create +$ moonshot create ``` By default, you'll get a development environment named `moonshot-sample-app`. If you want to provision test or production named environment, use: ```shell -$ ./bin/environment create -n my-service-staging -$ ./bin/environment create -n my-service-production +$ moonshot create -n my-service-staging +$ moonshot create -n my-service-production ``` By default, create launches the stack and deploys code. If you want to only create the stack and not deploy code, use: ```shell -$ ./bin/environment create --no-deploy +$ moonshot create --no-deploy ``` If you make changes to your application and want to release a development build to your stack, run: ```shell -$ ./bin/environment deploy-code +$ moonshot push ``` To build a "named build" for releasing through test and production environments, use: ```shell -$ ./bin/environment build-version v0.1.0 -$ ./bin/environment deploy-version v0.1.0 -n +$ moonshot build v0.1.0 +$ moonshot deploy v0.1.0 -n ``` To see the outputs of the stack you just spun up: ```shell -$ ./bin/environment build-version v0.1.0 -$ ./bin/environment deploy-version v0.1.0 -n +$ moonshot status ``` Tear down your stack by running the following command: ```shell -bundle exec bin/environment delete +$ moonshot delete ``` SSH into the first instance in your stack by running the following command: ```shell -bundle exec bin/environment ssh +$ moonshot ssh ``` diff --git a/docs/index.md b/docs/index.md index 3e89b2e4..335d25a2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -33,7 +33,12 @@ should aspire to meet them with each iteration. ## Installation -Add this line to your application's Gemfile: +You can install Moonshot for your local user with: + + $ gem install moonshot + +If you would prefer to manage your projects dependencies with Bundler, +add the following to your Gemfile: gem 'moonshot' @@ -41,17 +46,23 @@ And then execute: $ bundle install -Or install it yourself as: - - $ gem install moonshot - -After installation, there is still some work required. Follow the [example documentation](example.md) as described below to dig in! +After installation, there is still some work required. Follow +the [example documentation](example.md) as described below to dig in! ## Getting started -The Moonshot tool has been designed to be an extensible library for your specific use-case. Interested in how it can be used? See our [example documentation](example.md). The example doc uses the files shown in the [sample directory](https://github.com/acquia/moonshot/tree/master/sample) so you can figure out how to modify this for your own deployment strategy. +The Moonshot tool has been designed to be an extensible library for +your specific use-case. Interested in how it can be used? See +our [example documentation](example.md). The example doc uses the +files shown in the +[sample directory](https://github.com/acquia/moonshot/tree/master/sample) so +you can figure out how to modify this for your own deployment +strategy. + +We also want to [help you contribute and answer all your questions][1] +on how Moonshot is maintained. -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. +[1]: http://moonshot.readthedocs.org/en/latest/about/contribute ## Requirements diff --git a/docs/mechanisms/artifact-repository.md b/docs/mechanisms/artifact-repository.md index 81cddcb9..68fba392 100644 --- a/docs/mechanisms/artifact-repository.md +++ b/docs/mechanisms/artifact-repository.md @@ -3,23 +3,25 @@ ## S3Bucket The store action will upload the file using the S3 PutObject API call. -The local environment must be configured with appropriate credentials. +The local environment must be configured with appropriate +credentials. Running `doctor` will verify those credentials for you. To create a new S3Bucket ArtifactRepository: ```ruby -class MyApplication < Moonshot::CLI - self.artifact_repository = S3Bucket.new('my-bucket-name') +Moonshot.config do |c| + c.artifact_repository = S3Bucket.new('my-bucket-name') end ``` ## S3BucketViaGithubReleases -S3 Bucket repository backed by GitHub releases. -If a SemVer package isn't found in S3, it is downloaded from GitHub releases to avoid not being able to release in case there is trouble with AWS S3. +S3 Bucket repository backed by GitHub releases. If a SemVer package +isn't found in S3, it is downloaded from GitHub releases to avoid not +being able to release in case there is trouble with AWS S3. To create a new S3BucketViaGithubReleases ArtifactRepository: ```ruby -class MyApplication < Moonshot::CLI - self.artifact_repository = S3BucketViaGithubReleases.new('my-bucket-name') +Moonshot.config do |c| + c.artifact_repository = S3BucketViaGithubReleases.new('my-bucket-name') end -``` \ No newline at end of file +``` diff --git a/docs/mechanisms/build.md b/docs/mechanisms/build.md index eed97834..283cb88e 100644 --- a/docs/mechanisms/build.md +++ b/docs/mechanisms/build.md @@ -8,54 +8,45 @@ expectations. The script will run with some environment variables: - `VERSION`: The named version string passed to `build-version`. - `OUTPUT_FILE`: The file that the script is expected to produce. -If the file is not created by the build script, deployment will fail. Otherwise, -the output file will be uploaded using the ArtifactRepository. +If the file is not created by the build script, deployment will +fail. Otherwise, the output file will be uploaded using the +ArtifactRepository. Sample Usage ```ruby -#!/usr/bin/env ruby - -require 'moonshot' - -# Set up Moonshot tooling for our environment. -class MoonshotSampleApp < Moonshot::CLI - self.build_mechanism = Script.new('bin/build.sh') +Moonshot.config do |c| + c.build_mechanism = Script.new('bin/build.sh') ... ``` ## GithubRelease -A build mechanism that creates a tag and GitHub release. Could be used to delegate other building steps after GitHub release is created. +A build mechanism that creates a tag and GitHub release. Could be used +to delegate other building steps after GitHub release is created. Sample Usage ```ruby -#!/usr/bin/env ruby - -require 'moonshot' - -# Set up Moonshot tooling for our environment. -class MoonshotSampleApp < Moonshot::CLI - wait_for_travis_mechanism = TravisDeploy.new("acquia/moonshot", true) - self.build_mechanism = GithubRelease.new(wait_for_travis_mechanism) +Moonshot.config do |c| + wait_for_travis_mechanism = TravisDeploy.new("acquia/moonshot", true) + c.build_mechanism = GithubRelease.new(wait_for_travis_mechanism) ... ``` ## TravisDeploy -The Travis Build Mechanism waits for Travis-CI to finish building a job matching the VERSION (see above) and the output of the travis job has to be 'BUILD=1'. Can be used to make sure that the travis job for the repository for that version actually finished before the deployment step can be executed. +The Travis Build Mechanism waits for Travis-CI to finish building a +job matching the VERSION (see above) and the output of the travis job +has to be 'BUILD=1'. Can be used to make sure that the travis job for +the repository for that version actually finished before the +deployment step can be executed. Sample Usage ```ruby -#!/usr/bin/env ruby - -require 'moonshot' - -# Set up Moonshot tooling for our environment. -class MoonshotSampleApp < Moonshot::CLI +Moonshot.config do |c| # First argument is the repository as known by travis. # Second argument is wether or not you are using travis pro. - self.build_mechanism = TravisDeploy.new("acquia/moonshot", pro: true) + c.build_mechanism = TravisDeploy.new("acquia/moonshot", pro: true) ... ``` diff --git a/docs/mechanisms/deployment.md b/docs/mechanisms/deployment.md index 3ad22d55..03490fc9 100644 --- a/docs/mechanisms/deployment.md +++ b/docs/mechanisms/deployment.md @@ -2,7 +2,14 @@ ## CodeDeploy -The CodeDeploy DeploymentMechanism will create a CodeDeploy Application and Deployment Group matching the application name. The created Deployment Group will point at the logical resource id provided to the constructor (e.g. `CodeDeploy.new(asg: 'MyAutoScalingGroup')`). During the `deploy-code` action, the ArtifactRepository is checked for compatibility with CodeDeploy. Currently only the S3Bucket is supported, though CodeDeploy itself supports deploying from a git source. +The CodeDeploy DeploymentMechanism will create a CodeDeploy +Application and Deployment Group matching the application name. The +created Deployment Group will point at the logical resource id +provided to the constructor (e.g. `CodeDeploy.new(asg: +'MyAutoScalingGroup')`). During the `deploy-code` action, the +ArtifactRepository is checked for compatibility with +CodeDeploy. Currently only the S3Bucket is supported, though +CodeDeploy itself supports deploying from a git source. Assumptions made by the CodeDeploy mechanism: @@ -11,35 +18,39 @@ Assumptions made by the CodeDeploy mechanism: Sample Usage ```ruby -#!/usr/bin/env ruby - -require 'moonshot' - -# Set up Moonshot tooling for our environment. -class MoonshotSampleApp < Moonshot::CLI - self.deployment_mechanism = CodeDeploy.new(asg: 'AutoScalingGroup', role: 'CodeDeployRole', app_name: 'my_app_name', config_name: 'CodeDeployDefault.OneAtATime') +Moonshot.config do |c| + c.deployment_mechanism = CodeDeploy.new( + asg: 'AutoScalingGroup', + role: 'CodeDeployRole', + app_name: 'my_app_name', + config_name: 'CodeDeployDefault.OneAtATime') ... ``` Parameters ### asg | string,array -The logical name of one or more Auto Scaling Groups to create and manage a Deployment -Group for in CodeDeploy. +The logical name of one or more Auto Scaling Groups to create and +manage a Deployment Group for in CodeDeploy. ### role | string -IAM role with AWSCodeDeployRole policy. CodeDeployRole is considered as default role if its not specified. +IAM role with AWSCodeDeployRole policy. CodeDeployRole is considered +as default role if its not specified. ### app_name | string,nil -The name of the CodeDeploy Application and Deployment Group. By default, this is the same as the stack name, and probably what you want. If you have multiple deployments in a single Stack, they must have unique names. +The name of the CodeDeploy Application and Deployment Group. By +default, this is the same as the stack name, and probably what you +want. If you have multiple deployments in a single Stack, they must +have unique names. ### config_name | string -The name of the Deplloyment Configuration. CodeDeployDefault.OneAtATime is the default if its not specified. +The name of the Deplloyment +Configuration. CodeDeployDefault.OneAtATime is the default if its not +specified. For more information about CodeDeploy, see the [AWS Documentation][1]. - [1]: http://docs.aws.amazon.com/codedeploy/latest/userguide/welcome.html diff --git a/docs/plugins.md b/docs/plugins.md index 15231d4c..a9df9cae 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -3,9 +3,10 @@ **Warning, the plugin support in Moonshot is a work-in-progress. The interface to plugins may change dramatically in future versions.** -Moonshot supports adding plugins (implemented as a Ruby class) to the controller -that can perform actions before and after the `create`, `update`, `delete`, `deploy-code`, -`status` and `doctor` actions. + +Moonshot supports adding plugins (implemented as a Ruby class) to the +controller that can perform actions before and after the `create`, +`update`, `delete`, `deploy`, `status`, `doctor` and `ssh` actions. ## Writing a Moonshot Plugin @@ -40,12 +41,20 @@ CloudFormation stack. ## Adding a plugin to a CLI tool. -Once you have defined or included your plugin class, you can add a plugin like so: +Once you have defined or included your plugin class, you can add a +plugin by modifying your `Moonfile.rb` file, like so: ```ruby -class MyApp < Moonshot::CLI - self.application_name = 'my-app' +Moonshot.config do |c| + c.app_name = 'my-app' # ... - plugin MyPlugin.new + c.plugins << MyPlugin.new end ``` + +## Auto-loading Plugin Source + +The Moonshot CLI tool will auto-load plugin source in the path +`moonshot/plugins/**/*.rb` relative to the `Moonfile.rb` file for your +project. This can be useful for plugins that define project-specific +behaviors. diff --git a/docs/plugins/asg_rollout.md b/docs/plugins/asg_rollout.md index fba4dddc..9506b9bf 100644 --- a/docs/plugins/asg_rollout.md +++ b/docs/plugins/asg_rollout.md @@ -9,19 +9,15 @@ support). ## Example -The ASGRollout class is intended to be used within a method in your -project's derived class of the Moonshot::CLI class. +The ASGRollout class is intended to be used within a [CLI extension][1] +within your project. Here's an example of what it might look like: ```ruby -#!/usr/bin/env ruby +class Rollout < Moonshot::Command + self.usage = "rollout" + self.description = "Update all instances to the latest LaunchConfiguration" -require 'moonshot' - -class MyService < Moonshot::CLI - # .. normal configuration .. - - desc :asg_rollout, 'Update instances in the Auto Scaling Group' - def asg_rollout + def execute ar = Moonshot::Tools::ASGRollout.new do |config| config.controller = controller config.logical_id = 'APIAutoScalingGroup' @@ -35,8 +31,6 @@ class MyService < Moonshot::CLI ar.run! end end - -MyService.start ``` ## Configuration diff --git a/docs/plugins/backup.md b/docs/plugins/backup.md index d94f4914..17231792 100644 --- a/docs/plugins/backup.md +++ b/docs/plugins/backup.md @@ -6,7 +6,7 @@ Moonshot plugin for backing up config files. The plugin collects and deflates certain files to a single tarball, and uploads that to a a given S3 bucket. The whole process happens -in memory, nothing is written to disk. The plugin currently supports single files only, +in memory, nothing is written to disk. The plugin currently supports single files only, including whole directories in your tarball is not possible yet. The plugin uses the Moonshot AWS config, meaning that the bucket must be @@ -31,7 +31,9 @@ If you wish to back up only the current template and parameter files, you can si use the factory method provided: ```ruby -plugin(Moonshot::Plugins::Backup.to_bucket('your-bucket-name')) +Moonshot.config do |c| + # ... + c.plugins << Moonshot::Plugins::Backup.to_bucket('your-bucket-name') ``` ## Placeholders @@ -50,8 +52,7 @@ A possible use-case is backing up a CF template and/or parameter file after create or update. ```ruby -plugin( - Backup.new do |b| + c.plugins << Backup.new do |b| b.bucket = 'your-bucket-name' b.files = [ @@ -61,12 +62,10 @@ plugin( b.hooks = [:post_create, :post_update] end -) ``` ```ruby -plugin( - Backup.new do |b| + c.plugins << Backup.new do |b| b.buckets = { 'dev_account' => 'dev_bucket', 'prod_account' => 'prod_bucket' @@ -79,5 +78,4 @@ plugin( b.hooks = [:post_create, :post_update] end -) -``` \ No newline at end of file +``` diff --git a/docs/user-guide/cli.md b/docs/user-guide/cli.md index 8516a7cb..34ecdea4 100644 --- a/docs/user-guide/cli.md +++ b/docs/user-guide/cli.md @@ -16,7 +16,7 @@ List stacks for this application. Example: ```shell -./bin/environment list +moonshot list ``` Output: @@ -43,7 +43,7 @@ Create a new environment. Example: ```shell -./bin/environment create --name my-service-staging +moonshot create --name my-service-staging ``` Output: @@ -55,14 +55,14 @@ Output: [ ✓ ] [ 0m 0s ] AvailabilityZone1: us-east-1a [ ✓ ] [ 0m 0s ] AvailabilityZone2: us-east-1d [ ✓ ] [ 0m 0s ] DesiredCapacity: 1 -[ ✓ ] [ 0m 1s ] Created CloudFormation Stack my-service-staging. -[ ✓ ] [ 4m 49s ] CloudFormation Stack my-service-staging successfully created. -[ ✓ ] [ 0m 0s ] Created CodeDeploy Application my-service-staging. -[ ✓ ] [ 0m 1s ] Created CodeDeploy Deployment Group my-service-staging. -[ ✓ ] [ 0m 1s ] AutoScaling Group up to capacity! +[ ✓ ] [ 0m 1s ] Created CloudFormation Stack my-service-staging. +[ ✓ ] [ 4m 49s ] CloudFormation Stack my-service-staging successfully created. +[ ✓ ] [ 0m 0s ] Created CodeDeploy Application my-service-staging. +[ ✓ ] [ 0m 1s ] Created CodeDeploy Deployment Group my-service-staging. +[ ✓ ] [ 0m 1s ] AutoScaling Group up to capacity! [ ✓ ] [ 0m 0s ] Build script bin/build.sh exited successfully! -[ ✓ ] [ 0m 1s ] Uploaded s3://my-service-staging/my-service-staging-1457657945.tar.gz successfully. -[ ✓ ] [ 0m 49s ] Deployment d-UNF7JW2KE completed successfully! +[ ✓ ] [ 0m 1s ] Uploaded s3://my-service-staging/my-service-staging-1457657945.tar.gz successfully. +[ ✓ ] [ 0m 49s ] Deployment d-UNF7JW2KE completed successfully! ``` @@ -85,17 +85,17 @@ Options: Example: ```shell -./bin/environment update --name my-service-staging +moonshot update --name my-service-staging ``` Output: ```shell [ ✓ ] [ 0m 1s ] Initiated update for CloudFormation Stack my-service-staging. -[ ✓ ] [ 6m 11s ] CloudFormation Stack my-service-staging successfully updated. +[ ✓ ] [ 6m 11s ] CloudFormation Stack my-service-staging successfully updated. [ ✓ ] [ 0m 0s ] CodeDeploy Application my-service-staging already exists. [ ✓ ] [ 0m 0s ] CodeDeploy CodeDeploy Deployment Group my-service-staging already exists. -[ ✓ ] [ 0m 1s ] AutoScaling Group up to capacity! +[ ✓ ] [ 0m 1s ] AutoScaling Group up to capacity! ``` ## Status @@ -111,53 +111,53 @@ Get the status of an existing environment. Example: ```shell -./bin/environment status --name my-service-staging +moonshot status --name my-service-staging ``` Output: ```shell ┌─ CodeDeploy Application: my-service-staging -│ +│ │ Application and Deployment Group are configured correctly. -│ +│ └── CloudFormation Stack my-service-staging exists. ┌─ Stack Parameters -│ +│ │ ArtifactBucket: my-service-bucket (overridden) │ AvailabilityZone1: us-east-1a (overridden) │ AvailabilityZone2: us-east-1d (overridden) │ DesiredCapacity: 1 (overridden) -│ +│ ├─ Stack Outputs -│ +│ │ URL: http://sample-LoadBala-VA232FB9FWFZ-1573168493.us-east-1.elb.amazonaws.com -│ +│ ├─ ASG: AutoScalingGroup -│ +│ │ Name: my-service-staging-AutoScalingGroup-104IA9X5MF7GH │ Using ELB health checks, with a 600s health check grace period. │ Desired Capacity is 1 (Min: 1, Max: 5). │ Has 1 Load Balancer(s): sample-LoadBala-VA232FB9FWFZ -│ +│ ├── Instances -│ +│ │ i-5607c6cd 52.90.68.26 InService Healthy 0d 0h 8m 11s (launch config up to date) -│ +│ ├── Recent Activity -│ +│ │ 2016-03-11 01:07:59 UTC Terminating EC2 instance: i-73fe99f7 Successful 100% │ 2016-03-11 01:03:26 UTC Launching a new EC2 instance: i-5607c6cd Successful 100% │ 2016-03-11 00:58:03 UTC Launching a new EC2 instance: i-73fe99f7 Successful 100% -│ +│ └── ``` -## Deploy Code +## Push -Create a build from the working directory, and deploy it. +Create a development build from the working directory, and deploy it. |Description|Long Form|Short Form|Type|Example|Default| |---|---|---|---|---|---| @@ -168,18 +168,18 @@ Create a build from the working directory, and deploy it. Example: ```shell -./bin/environment deploy-code --name my-service-staging +moonshot deploy-code --name my-service-staging ``` Output: ```shell [ ✓ ] [ 0m 1s ] Build script bin/build.sh exited successfully! -[ ✓ ] [ 0m 1s ] Uploaded s3://my-service-staging/my-service-staging-1457658789.tar.gz successfully. -[ ✓ ] [ 1m 28s ] Deployment d-PFMNSB5KE completed successfully! +[ ✓ ] [ 0m 1s ] Uploaded s3://my-service-staging/my-service-staging-1457658789.tar.gz successfully. +[ ✓ ] [ 1m 28s ] Deployment d-PFMNSB5KE completed successfully! ``` -## Build Version +## Build Build a tarball of the software, ready for deployment. Requires a version name parameter. @@ -193,19 +193,19 @@ Requires a version name parameter. Example: ```shell -./bin/environment build-version 1.0.0 --name my-service-staging +moonshot build 1.0.0 ``` Output: ```shell [ ✓ ] [ 0m 0s ] Build script bin/build.sh exited successfully! -[ ✓ ] [ 0m 1s ] Uploaded s3://my-service-staging/1.0.0.tar.gz successfully. +[ ✓ ] [ 0m 1s ] Uploaded s3://my-service-staging/1.0.0.tar.gz successfully. ``` -## Deploy Version +## Deploy -Deploy a versioned release to both Elastic Beanstalk environments in an environment. +Deploy a versioned release created with the `build` command. Requires a version name parameter. |Description|Long Form|Short Form|Type|Example|Default| @@ -217,13 +217,13 @@ Requires a version name parameter. Example: ```shell -./bin/environment deploy-version 1.0.0 --name my-service-staging +moonshot deploy 1.0.0 ``` Output: ```shell -[ ✓ ] [ 1m 0s ] Deployment d-M4FY304KE completed successfully! +[ ✓ ] [ 1m 0s ] Deployment d-M4FY304KE completed successfully! ``` ## Delete @@ -240,20 +240,23 @@ Delete an existing environment. Example: ```shell -./bin/environment delete --name my-service-staging +moonshot delete --name my-service-staging ``` Output: ```shell [ ✓ ] [ 0m 1s ] Initiated deletion of CloudFormation Stack my-service-staging. -[ ✓ ] [ 11m 50s ] CloudFormation Stack my-service-staging successfully deleted. +[ ✓ ] [ 11m 50s ] CloudFormation Stack my-service-staging successfully deleted. [ ✓ ] [ 0m 0s ] Deleted CodeDeploy Application 'my-service-staging'. ``` ## Doctor -Run configuration checks against current environment. Throws an error if one or more checks failed. -For example, if you are using a deployment_mechanism that is using S3, it will check if the bucket actually exists and that you have access to. Each mechanism is able to add checks themselves that will be recognized and run. +Run configuration checks against current environment. Throws an error +if one or more checks failed. For example, if you are using a +deployment_mechanism that is using S3, it will check if the bucket +actually exists and that you have access to. Each mechanism is able to +add checks themselves that will be recognized and run. |Description|Long Form|Short Form|Type|Example|Default| |---|---|---|---|---|---| @@ -264,7 +267,7 @@ For example, if you are using a deployment_mechanism that is using S3, it will c Example: ```shell -./bin/environment doctor --name my-service-staging +moonshot doctor ``` Output: @@ -304,7 +307,7 @@ SSH into the first or specified instance on the stack. Example: ```shell -./bin/environment ssh --name my-service-staging -i ~/.ssh/whatever -c "cat /etc/redhat-release" +moonshot ssh --name my-service-staging -i ~/.ssh/whatever -c "cat /etc/redhat-release" ``` Output: diff --git a/docs/user-guide/include_in_your_project.md b/docs/user-guide/include_in_your_project.md index 694a0401..7f01ba01 100644 --- a/docs/user-guide/include_in_your_project.md +++ b/docs/user-guide/include_in_your_project.md @@ -1,24 +1,19 @@ # Including Moonshot-based tooling in your project -## Create a Ruby wrapper +## Create a Moonfile.rb -Now, let's create a ruby wrapper that implements the Moonshot tool and makes your CLI. -The base class is a subclass of Thor, so you can extend it using the [full Thor extensibility](http://whatisthor.com/). Here's a basic example using all the defaults: +(TODO: Implement `moonshot init`) -```ruby -#!/usr/bin/env ruby - -require 'moonshot' +In the root of your project, create a file called `Moonfile.rb` with +the following contents: -# Set up Moonshot tooling for our environment. -class MyService < Moonshot::CLI - self.application_name = 'my-service' - self.artifact_repository = S3Bucket.new('my-service-builds') - self.build_mechanism = Script.new('build/script.sh') - self.deployment_mechanism = CodeDeploy.new(asg: 'AutoScalingGroup') +```ruby +Moonshot.config do |c| + c.application_name = 'my-service' + c.artifact_repository = S3Bucket.new('my-service-builds') + c.build_mechanism = Script.new('build/script.sh') + c.deployment_mechanism = CodeDeploy.new(asg: 'AutoScalingGroup') end - -MyService.start ``` This example assumes: @@ -29,34 +24,34 @@ This example assumes: If all that is true, you can now deploy your software to a new stack with: ``` -$ ./bin/environment create +$ moonshot create ``` By default, you'll get a development environment named `my-service-dev-giraffe`, where `giraffe` is your username. If you want to provision test or production named environment, use: ``` -$ ./bin/environment create -n my-service-staging -$ ./bin/environment create -n my-service-production +$ moonshot create -n my-service-staging +$ moonshot create -n my-service-production ``` By default, create launches the stack and deploys code. If you want to only create the stack and not deploy code, use: ``` -$ ./bin/environment create --no-deploy +$ moonshot create --no-deploy ``` If you make changes to your application and want to release a development build to your stack, run: ``` -$ ./bin/environment deploy-code +$ moonshot push ``` To build a "named build" for releasing through test and production environments, use: ``` -$ ./bin/environment build-version v0.1.0 -$ ./bin/environment deploy-version v0.1.0 -n +$ moonshot build v0.1.0 +$ moonshot deploy v0.1.0 -n ``` We recommend using a CI system like Jenkins to perform those activities, for diff --git a/docs/user-guide/stack_parameter_strategies.md b/docs/user-guide/stack_parameter_strategies.md index 99cd44cf..7de1ca2e 100644 --- a/docs/user-guide/stack_parameter_strategies.md +++ b/docs/user-guide/stack_parameter_strategies.md @@ -71,8 +71,9 @@ hashes of the following format: } ``` -You either supply a value for `parameter_value` or set `use_previous_value` to `true`, which leaves the parameter -as it currently is. +You either supply a value for `parameter_value` or set +`use_previous_value` to `true`, which leaves the parameter as it +currently is. Example: @@ -90,7 +91,5 @@ class CustomStrategy end ``` -In order to use your custom strategy class, set a new instance of your class -to `ControllerConfig`'s `parameter_strategy` attribute. - - +In order to use your custom strategy class, set a new instance of your +class to `ControllerConfig`'s `parameter_strategy` attribute. diff --git a/lib/moonshot.rb b/lib/moonshot.rb index fb10bde0..7525305d 100644 --- a/lib/moonshot.rb +++ b/lib/moonshot.rb @@ -2,15 +2,21 @@ require 'aws-sdk' require 'logger' require 'thor' +require 'interactive-logger' module Moonshot - module ArtifactRepository # rubocop:disable Documentation + def self.config + @config ||= Moonshot::ControllerConfig.new + block_given? ? yield(@config) : @config end - module BuildMechanism # rubocop:disable Documentation + + module ArtifactRepository + end + module BuildMechanism end - module DeploymentMechanism # rubocop:disable Documentation + module DeploymentMechanism end - module Plugins # rubocop:disable Documentation + module Plugins end end @@ -23,13 +29,28 @@ module Plugins # rubocop:disable Documentation # Core 'interactive_logger_proxy', + 'command_line', + 'command', + 'ssh_command', + 'commands/build', + 'commands/console', + 'commands/create', + 'commands/delete', + 'commands/deploy', + 'commands/doctor', + 'commands/list', + 'commands/push', + 'commands/ssh', + 'commands/status', + 'commands/update', 'controller', 'controller_config', - 'cli', 'stack', 'stack_config', 'stack_lister', 'stack_events_poller', + 'merge_strategy', + 'default_strategy', # Built-in mechanisms 'artifact_repository/s3_bucket', diff --git a/lib/moonshot/build_mechanism/github_release.rb b/lib/moonshot/build_mechanism/github_release.rb index 81d9cedb..0a1920d7 100644 --- a/lib/moonshot/build_mechanism/github_release.rb +++ b/lib/moonshot/build_mechanism/github_release.rb @@ -72,7 +72,7 @@ def confirm_or_fail(version) say("#{@changes}\n\n") q = "Do you want to tag and release this commit as #{version}? [y/n]" - raise Thor::Error, 'Release declined.' unless yes?(q) + raise 'Release declined.' unless yes?(q) end def git_tag(tag, sha, annotation) diff --git a/lib/moonshot/build_mechanism/script.rb b/lib/moonshot/build_mechanism/script.rb index a1417a19..75eef3a0 100644 --- a/lib/moonshot/build_mechanism/script.rb +++ b/lib/moonshot/build_mechanism/script.rb @@ -41,7 +41,7 @@ def build_hook(version) def post_build_hook(_version) unless File.exist?(@output_file) # rubocop:disable GuardClause - raise Thor::Error, 'Build command did not produce output file!' + raise 'Build command did not produce output file!' end end diff --git a/lib/moonshot/cli.rb b/lib/moonshot/cli.rb deleted file mode 100644 index b552c83c..00000000 --- a/lib/moonshot/cli.rb +++ /dev/null @@ -1,219 +0,0 @@ -require 'interactive-logger' -require_relative 'default_strategy' -require_relative 'merge_strategy' - -# Base class for Moonshot-powered project tooling. -module Moonshot - # The main entry point for Moonshot, this class should be extended by - # project tooling. - class CLI < Thor # rubocop:disable ClassLength - class_option(:name, aliases: 'n', default: nil, type: :string) - class_option(:interactive_logger, type: :boolean, default: true) - class_option(:verbose, aliases: 'v', type: :boolean) - - class << self - attr_accessor :application_name - attr_accessor :artifact_repository - attr_accessor :auto_prefix_stack - attr_accessor :build_mechanism - attr_accessor :deployment_mechanism - attr_accessor :default_parent_stack - attr_accessor :default_parameter_strategy - attr_reader :plugins - - def plugin(plugin) - @plugins ||= [] - @plugins << plugin - end - - def parent(value) - @default_parent_stack = value - end - - def parameter_strategy(strategy) - @default_parameter_strategy = strategy - end - - def check_class_configuration - raise Thor::Error, 'No application_name is set!' unless application_name - end - - def exit_on_failure? - true - end - - def inherited(base) - base.include(Moonshot::ArtifactRepository) - base.include(Moonshot::BuildMechanism) - base.include(Moonshot::DeploymentMechanism) - end - - def ssh_options! - option :user, default: ENV['MOONSHOT_SSH_USER'] || ENV['USER'], type: :string, - aliases: '-l', - desc: 'Specifies the user to log in as on the remote machine.' - option :identity_file, default: ENV['MOONSHOT_SSH_KEY_FILE'], type: :string, - aliases: '-i', - desc: 'Selects a file from which the identity (private key) for '\ - 'public key authentication is read.' - option :instance, default: nil, type: :string, aliases: '-s', - desc: 'Connect to specified instance ID instead of the first one.' - option :command, default: nil, type: :string, aliases: '-c', - desc: 'Execute the specified command instead of opening a shell.' - option :auto_scaling_group, default: nil, type: :string, aliases: '-g', - desc: 'The logical ID of the auto scaling group to SSH into. '\ - 'Mandatory if the stack has multiple ASGs, '\ - 'ignored if you have only one.' - end - end - - def initialize(*args) - super - @log = Logger.new(STDOUT) - @log.formatter = proc do |s, d, _, msg| - "[#{self.class.name} #{s} #{d.strftime('%T')}] #{msg}\n" - end - @log.level = options[:verbose] ? Logger::DEBUG : Logger::INFO - - self.class.check_class_configuration - end - - no_tasks do - # Build a Moonshot::Controller from the CLI options. - def controller # rubocop:disable AbcSize, CyclomaticComplexity, PerceivedComplexity - Moonshot::Controller.new do |config| - config.app_name = self.class.application_name - config.artifact_repository = self.class.artifact_repository - config.auto_prefix_stack = self.class.auto_prefix_stack - config.build_mechanism = self.class.build_mechanism - config.deployment_mechanism = self.class.deployment_mechanism - config.environment_name = options[:name] - config.logger = @log - - # Degrade to a more compatible logger if the terminal seems outdated, - # or at the users request. - if !$stdout.isatty || !options[:interactive_logger] - config.interactive_logger = InteractiveLoggerProxy.new(@log) - end - - config.show_all_stack_events = true if options[:show_all_events] - config.plugins = self.class.plugins if self.class.plugins - - if options[:parent] - config.parent_stacks << options[:parent] - elsif self.class.default_parent_stack - config.parent_stacks << self.class.default_parent_stack - end - - parameter_strategy = options[:parameter_strategy] || self.class.default_parameter_strategy - config.parameter_strategy = parameter_strategy_factory(parameter_strategy) \ - unless parameter_strategy.nil? - - config.ssh_config.ssh_user = options[:user] - config.ssh_config.ssh_identity_file = options[:identity_file] - config.ssh_instance = options[:instance] - config.ssh_command = options[:command] - config.ssh_auto_scaling_group_name = options[:auto_scaling_group] - end - rescue => e - raise Thor::Error, e.message - end - - def parameter_strategy_factory(value) - case value.to_sym - when :default - Moonshot::ParameterStrategy::DefaultStrategy.new - when :merge - Moonshot::ParameterStrategy::MergeStrategy.new - else - raise Thor::Error, "Unknown parameter strategy: #{value}" - end - end - - def ssh_command(command) - arguments = ARGV.drop(1) - arguments += ['--command', command] - invoke :ssh, [], arguments - end - end - - desc :list, 'List stacks for this application.' - def list - controller.list - end - - desc :create, 'Create a new environment.' - option( - :parent, - type: :string, - aliases: '-p', - desc: "Parent stack to import parameters from. (Default: #{default_parent_stack || 'None'})") - option :deploy, default: true, type: :boolean, aliases: '-d', - desc: 'Choose if code should be deployed after stack is created' - option :version, default: nil, type: :string, - desc: 'Version to deploy. Only valid if deploy flag is set.' - option :show_all_events, desc: 'Show all stack events during update. (Default: errors only)' - def create - controller.create - - if options[:deploy] - if options[:version].nil? - controller.deploy_code - else - controller.deploy_version(options[:version]) - end - end - end - - desc :update, 'Update the CloudFormation stack within an environment.' - option( - :parameter_strategy, - type: :string, - desc: 'Override default parameter strategy.') - option( - :show_all_events, - type: :boolean, - desc: 'Show all stack events during update. (Default: errors only)') - def update - controller.update - end - - desc :status, 'Get the status of an existing environment.' - def status - controller.status - end - - desc 'deploy-code', 'Create a build from the working directory, and deploy it.' # rubocop:disable LineLength - def deploy_code - controller.deploy_code - end - - desc 'build-version VERSION', 'Build a tarball of the software, ready for deployment.' # rubocop:disable LineLength - def build_version(version_name) - controller.build_version(version_name) - end - - desc 'deploy-version VERSION_NAME', 'Deploy a versioned release to both EB environments in an environment.' # rubocop:disable LineLength - def deploy_version(version_name) - controller.deploy_version(version_name) - end - - desc :delete, 'Delete an existing environment.' - option :show_all_events, desc: 'Show all stack events during update. (Default: errors only)' - def delete - controller.delete - end - - desc :doctor, 'Run configuration checks against current environment.' - def doctor - success = controller.doctor - raise Thor::Error, 'One or more checks failed.' unless success - end - - desc :ssh, 'SSH into the first or specified instance on the stack.' - ssh_options! - def ssh - controller.ssh - end - end -end diff --git a/lib/moonshot/command.rb b/lib/moonshot/command.rb new file mode 100644 index 00000000..2570a465 --- /dev/null +++ b/lib/moonshot/command.rb @@ -0,0 +1,64 @@ +require 'thor' + +module Moonshot + # A Command that is automatically registered with the Moonshot::CommandLine + class Command + module ClassMethods + # TODO: Can we auto-generate usage for commands with no positional arguments, at least? + attr_accessor :usage, :description + end + + def self.inherited(base) + Moonshot::CommandLine.register(base) + base.extend(ClassMethods) + end + + def parser + @use_interactive_logger = true + + OptionParser.new do |o| + o.banner = "Usage: moonshot #{self.class.usage}" + + o.on('-v', '--[no-]verbose', 'Show debug logging') do |v| + Moonshot.config.interactive_logger.debug = true if v + end + + o.on('-nNAME', '--environment=NAME', 'Which environment to operate on.') do |v| + Moonshot.config.environment_name = v + end + + o.on('--[no-]interactive-logger', TrueClass, 'Enable or disable fancy logging') do |v| + @use_interactive_logger = v + end + + o.on('--[no-]show-all-events', FalseClass, 'Show all stack events during update') do |v| + Moonshot.config.show_all_stack_events = v + end + + o.on('-pPARENT_STACK', '--parent=PARENT_STACK', + 'Parent stack to import parameters from') do |v| + Moonshot.config.parent_stacks = [v] + end + end + end + + private + + # Build a Moonshot::Controller from the CLI options. + def controller + controller = Moonshot::Controller.new + + # Apply CLI options to configuration defined by Moonfile. + controller.config = Moonshot.config + + # Degrade to a more compatible logger if the terminal seems outdated, + # or at the users request. + if !$stdout.isatty || !@use_interactive_logger + log = Logger.new(STDOUT) + controller.config.interactive_logger = InteractiveLoggerProxy.new(log) + end + + controller + end + end +end diff --git a/lib/moonshot/command_line.rb b/lib/moonshot/command_line.rb new file mode 100644 index 00000000..61638b64 --- /dev/null +++ b/lib/moonshot/command_line.rb @@ -0,0 +1,121 @@ +require 'thor' + +module Moonshot + # This class implements the command-line `moonshot` tool. + class CommandLine + def self.run! # rubocop:disable AbcSize, CyclomaticComplexity, MethodLength, PerceivedComplexity + # Find the Moonfile in this project. + orig_dir = Dir.pwd + + loop do + break if File.exist?('Moonfile.rb') + raise 'Could not find Moonfile.rb!' if Dir.pwd == '/' + + Dir.chdir('..') + end + + moonfile_dir = Dir.pwd + Dir.chdir(orig_dir) + + # Load any plugins and CLI extensions relative to the Moonfile + if File.directory?(File.join(moonfile_dir, 'moonshot')) + load_plugins(moonfile_dir) + load_cli_extensions(moonfile_dir) + end + + Object.include(Moonshot::ArtifactRepository) + Object.include(Moonshot::BuildMechanism) + Object.include(Moonshot::DeploymentMechanism) + load(File.join(moonfile_dir, 'Moonfile.rb')) + + load_commands + + # Determine what command is being run, which should be the first argument. + command = ARGV.shift + if %w(--help -h help).include?(command) || command.nil? + usage + return + end + + # Dispatch to that command, by executing it's parser, then + # comparing ARGV to the execute methods arity. + unless @commands.key?(command) + usage + raise "Command not found '#{command}'" + end + + handler = @commands[command].new + handler.parser.parse! + + unless ARGV.size == handler.method(:execute).arity + warn handler.parser.help + raise "Invalid command line for '#{command}'." + end + + handler.execute(*ARGV) + end + + def self.register(klass) + @classes ||= [] + @classes << klass + end + + def self.registered_commands + @classes || [] + end + + def self.load_plugins(moonfile_dir) + plugins_path = File.join(moonfile_dir, 'moonshot', 'plugins', '**', '*.rb') + Dir.glob(plugins_path).each do |file| + load(file) + end + end + + def self.load_cli_extensions(moonfile_dir) + cli_extensions_path = File.join(moonfile_dir, 'moonshot', 'cli_extensions', '**', '*.rb') + Dir.glob(cli_extensions_path).each do |file| + load(file) + end + end + + def self.usage + warn 'Usage: moonshot [command]' + warn + warn 'Valid commands include:' + fields = [] + @commands.each do |c, k| + fields << [c, k.description] + end + + max_len = fields.map(&:first).map(&:size).max + + fields.each do |f| + line = format(" %-#{max_len}s # %s", *f) + warn line + end + end + + def self.load_commands + @commands = {} + + # Include all Moonshot::Command and Moonshot::SSHCommand + # derived classes as subcommands, with the description of their + # default task. + registered_commands.each do |klass| + next unless klass.instance_methods.include?(:execute) + + command_name = commandify(klass) + @commands[command_name] = klass + end + end + + def self.commandify(klass) + word = klass.to_s.split('::').last + word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze) + word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze) + word.tr!('_'.freeze, '-'.freeze) + word.downcase! + word + end + end +end diff --git a/lib/moonshot/commands/build.rb b/lib/moonshot/commands/build.rb new file mode 100644 index 00000000..b165ed26 --- /dev/null +++ b/lib/moonshot/commands/build.rb @@ -0,0 +1,12 @@ +module Moonshot + module Commands + class Build < Moonshot::Command + self.usage = 'build VERSION' + self.description = 'Build a release artifact, ready for deployment' + + def execute(version_name) + controller.build_version(version_name) + end + end + end +end diff --git a/lib/moonshot/commands/console.rb b/lib/moonshot/commands/console.rb new file mode 100644 index 00000000..62978eea --- /dev/null +++ b/lib/moonshot/commands/console.rb @@ -0,0 +1,19 @@ +module Moonshot + module Commands + class Console < Moonshot::Command + self.usage = 'console [options]' + self.description = 'Launch a interactive Ruby console with configured access to AWS' + + def execute + controller + + ec2 = Aws::EC2::Client.new + iam = Aws::IAM::Client.new + autoscaling = Aws::AutoScaling::Client.new + cf = Aws::CloudFormation::Client.new + + Pry.start binding, backtrace: nil + end + end + end +end diff --git a/lib/moonshot/commands/create.rb b/lib/moonshot/commands/create.rb new file mode 100644 index 00000000..916bdb93 --- /dev/null +++ b/lib/moonshot/commands/create.rb @@ -0,0 +1,31 @@ +module Moonshot + module Commands + class Create < Moonshot::Command + self.usage = 'create [options]' + self.description = 'Create a new environment' + + def parser + @deploy = true + + parser = super + parser.on('-d', '--[no-]deploy', TrueClass, 'Choose if code should be deployed immediately after the stack is created') do |v| # rubocop:disable LineLength + @deploy = v + end + + parser.on('--version VERSION_NAME', 'Version for initial deployment. If unset, a new development build is created from the local directory') do |v| # rubocop:disable LineLength + @version = v + end + end + + def execute + controller.create + + if @deploy && @version.nil? + controller.deploy_code + elsif @deploy + controller.deploy_version(@version) + end + end + end + end +end diff --git a/lib/moonshot/commands/delete.rb b/lib/moonshot/commands/delete.rb new file mode 100644 index 00000000..62b5565c --- /dev/null +++ b/lib/moonshot/commands/delete.rb @@ -0,0 +1,12 @@ +module Moonshot + module Commands + class Delete < Moonshot::Command + self.usage = 'delete [options]' + self.description = 'Delete an existing environment' + + def execute + controller.delete + end + end + end +end diff --git a/lib/moonshot/commands/deploy.rb b/lib/moonshot/commands/deploy.rb new file mode 100644 index 00000000..2a944fda --- /dev/null +++ b/lib/moonshot/commands/deploy.rb @@ -0,0 +1,12 @@ +module Moonshot + module Commands + class Deploy < Moonshot::Command + self.usage = 'deploy VERSION' + self.description = 'Deploy a versioned release to the environment' + + def execute(version_name) + controller.deploy_version(version_name) + end + end + end +end diff --git a/lib/moonshot/commands/doctor.rb b/lib/moonshot/commands/doctor.rb new file mode 100644 index 00000000..01925294 --- /dev/null +++ b/lib/moonshot/commands/doctor.rb @@ -0,0 +1,12 @@ +module Moonshot + module Commands + class Doctor < Moonshot::Command + self.usage = 'doctor [options]' + self.description = 'Run configuration checks against the local environment' + + def execute + controller.doctor || raise('One or more checks failed.') + end + end + end +end diff --git a/lib/moonshot/commands/list.rb b/lib/moonshot/commands/list.rb new file mode 100644 index 00000000..e9b0edf7 --- /dev/null +++ b/lib/moonshot/commands/list.rb @@ -0,0 +1,12 @@ +module Moonshot + module Commands + class List < Moonshot::Command + self.usage = 'list [options]' + self.description = 'List stacks for this application' + + def execute + controller.list + end + end + end +end diff --git a/lib/moonshot/commands/push.rb b/lib/moonshot/commands/push.rb new file mode 100644 index 00000000..261d38ec --- /dev/null +++ b/lib/moonshot/commands/push.rb @@ -0,0 +1,12 @@ +module Moonshot + module Commands + class Push < Moonshot::Command + self.usage = 'push [options]' + self.description = 'Build and deploy a development artifact from the working directory' + + def execute + controller.deploy_code + end + end + end +end diff --git a/lib/moonshot/commands/ssh.rb b/lib/moonshot/commands/ssh.rb new file mode 100644 index 00000000..672c0a36 --- /dev/null +++ b/lib/moonshot/commands/ssh.rb @@ -0,0 +1,12 @@ +module Moonshot + module Commands + class Ssh < Moonshot::SSHCommand + self.usage = 'ssh [options]' + self.description = 'SSH into the first (or specified) instance on the stack' + + def execute + controller.ssh + end + end + end +end diff --git a/lib/moonshot/commands/status.rb b/lib/moonshot/commands/status.rb new file mode 100644 index 00000000..b35d8246 --- /dev/null +++ b/lib/moonshot/commands/status.rb @@ -0,0 +1,12 @@ +module Moonshot + module Commands + class Status < Moonshot::Command + self.usage = 'status [options]' + self.description = 'Show the status of an existing environment' + + def execute + controller.status + end + end + end +end diff --git a/lib/moonshot/commands/update.rb b/lib/moonshot/commands/update.rb new file mode 100644 index 00000000..93c7abb1 --- /dev/null +++ b/lib/moonshot/commands/update.rb @@ -0,0 +1,32 @@ +module Moonshot + module Commands + class Update < Moonshot::Command + self.usage = 'update [options]' + self.description = 'Update the CloudFormation stack within an environment.' + + def parser + parser = super + parser.on('--parameter-strategy default,merge', 'Override default parameter strategy') do |v| # rubocop:disable LineLength + Moonshot.config.parameter_strategy = parameter_strategy_factory(v) + end + end + + def execute + controller.update + end + + private + + def parameter_strategy_factory(value) + case value.to_sym + when :default + Moonshot::ParameterStrategy::DefaultStrategy.new + when :merge + Moonshot::ParameterStrategy::MergeStrategy.new + else + raise "Unknown parameter strategy: #{value}" + end + end + end + end +end diff --git a/lib/moonshot/config.rb b/lib/moonshot/config.rb new file mode 100644 index 00000000..e69de29b diff --git a/lib/moonshot/controller.rb b/lib/moonshot/controller.rb index 35341241..641783cf 100644 --- a/lib/moonshot/controller.rb +++ b/lib/moonshot/controller.rb @@ -4,7 +4,7 @@ module Moonshot # The Controller coordinates and performs all Moonshot actions. class Controller # rubocop:disable ClassLength - attr_reader :config + attr_accessor :config def initialize @config = ControllerConfig.new @@ -12,8 +12,7 @@ def initialize end def list - Moonshot::StackLister.new( - @config.app_name, log: @config.logger).list + Moonshot::StackLister.new(@config.app_name).list end def create @@ -90,14 +89,13 @@ def ssh cb = SSHCommandBuilder.new(@config.ssh_config, @config.ssh_instance) result = cb.build(@config.ssh_command) - puts "Opening SSH connection to #{@config.ssh_instance} (#{result.ip})..." + warn "Opening SSH connection to #{@config.ssh_instance} (#{result.ip})..." exec(result.cmd) end def stack @stack ||= Stack.new(stack_name, app_name: @config.app_name, - log: @config.logger, ilog: @config.interactive_logger) do |config| config.parent_stacks = @config.parent_stacks config.show_all_events = @config.show_all_stack_events @@ -131,15 +129,13 @@ def stack_name def resources @resources ||= - Resources.new(stack: stack, log: @config.logger, - ilog: @config.interactive_logger) + Resources.new(stack: stack, ilog: @config.interactive_logger) end def run_hook(type, name, *args) mech = get_mechanism(type) name = name.to_s << '_hook' - @config.logger.debug("Calling hook=#{name} on mech=#{mech.class}") return unless mech && mech.respond_to?(name) mech.resources = resources diff --git a/lib/moonshot/controller_config.rb b/lib/moonshot/controller_config.rb index 79eb8c0b..0304af24 100644 --- a/lib/moonshot/controller_config.rb +++ b/lib/moonshot/controller_config.rb @@ -1,5 +1,6 @@ require_relative 'default_strategy' require_relative 'ssh_config' +require_relative 'task' module Moonshot # Holds configuration for Moonshot::Controller @@ -11,7 +12,6 @@ class ControllerConfig attr_accessor :deployment_mechanism attr_accessor :environment_name attr_accessor :interactive_logger - attr_accessor :logger attr_accessor :parent_stacks attr_accessor :plugins attr_accessor :show_all_stack_events @@ -24,7 +24,6 @@ class ControllerConfig def initialize @auto_prefix_stack = true @interactive_logger = InteractiveLogger.new - @logger = Logger.new(STDOUT) @parent_stacks = [] @plugins = [] @show_all_stack_events = false diff --git a/lib/moonshot/deployment_mechanism/code_deploy.rb b/lib/moonshot/deployment_mechanism/code_deploy.rb index 78f97fd8..e6d7dcd6 100644 --- a/lib/moonshot/deployment_mechanism/code_deploy.rb +++ b/lib/moonshot/deployment_mechanism/code_deploy.rb @@ -159,13 +159,13 @@ def load_auto_scaling_groups @asg_logical_ids.each do |asg_logical_id| asg_name = stack.physical_id_for(asg_logical_id) unless asg_name - raise Thor::Error, "Could not find #{asg_logical_id} resource in Stack." + raise "Could not find #{asg_logical_id} resource in Stack." end groups = as_client.describe_auto_scaling_groups( auto_scaling_group_names: [asg_name]) if groups.auto_scaling_groups.empty? - raise Thor::Error, "Could not find ASG #{asg_name}." + raise "Could not find ASG #{asg_name}." end autoscaling_groups.push(groups.auto_scaling_groups.first) @@ -219,7 +219,7 @@ def deployment_group_ok? def role iam_client.get_role(role_name: @codedeploy_role).role rescue Aws::IAM::Errors::NoSuchEntity - raise Thor::Error, "Did not find an IAM Role: #{@codedeploy_role}" + raise "Did not find an IAM Role: #{@codedeploy_role}" end def delete_deployment_group @@ -298,7 +298,7 @@ def handle_deployment_failure(deployment_id) end end - raise Thor::Error, 'Deployment was unsuccessful!' + raise 'Deployment was unsuccessful!' end def revision_for_artifact_repo(artifact_repo, version_name) diff --git a/lib/moonshot/resources.rb b/lib/moonshot/resources.rb index c3b36f29..da9cb606 100644 --- a/lib/moonshot/resources.rb +++ b/lib/moonshot/resources.rb @@ -2,10 +2,9 @@ module Moonshot # Resources is a dependency container that holds references to instances # provided to a Mechanism (build, deploy, etc.). class Resources - attr_reader :log, :stack, :ilog + attr_reader :stack, :ilog - def initialize(log:, stack:, ilog:) - @log = log + def initialize(stack:, ilog:) @stack = stack @ilog = ilog end diff --git a/lib/moonshot/resources_helper.rb b/lib/moonshot/resources_helper.rb index 4438cc06..f6d48409 100644 --- a/lib/moonshot/resources_helper.rb +++ b/lib/moonshot/resources_helper.rb @@ -6,9 +6,9 @@ module ResourcesHelper private + # TODO: Deprecate this interface. def log - raise 'Resources not provided to Mechanism!' unless @resources - @resources.log + @log ||= Logger.new(STDOUT) end def stack diff --git a/lib/moonshot/ssh_command.rb b/lib/moonshot/ssh_command.rb new file mode 100644 index 00000000..075e44ce --- /dev/null +++ b/lib/moonshot/ssh_command.rb @@ -0,0 +1,31 @@ +require 'thor' + +module Moonshot + # A SSHCommand that is automatically registered with the + # Moonshot::CommandLine, including options for SSH access. + class SSHCommand < Moonshot::Command + def parser + parser = super + parser.on('-l', '--user USER', 'User to log into remote machine as') do |v| + Moonshot.config.ssh_config.ssh_user = v + end + + parser.on('-i', '--identity-file FILE', 'SSH Private Key to authenticate with') do |v| + Moonshot.config.ssh_config.ssh_identity_file = v + end + + parser.on('-s', '--instance INSTANCE_ID', 'Specific AWS EC2 ID to connect through') do |v| + Moonshot.config.ssh_instance = v + end + + parser.on('-c', '--command COMMAND', 'Command to execute on the remote host') do |v| + Moonshot.config.ssh_command = v + end + + parser.on('-g', '--auto-scaling-group ASG_NAME', + 'The logical ID of the ASG to SSH into, required for multiple stacks') do |v| + Moonshot.config.ssh_auto_scaling_group_name = v + end + end + end +end diff --git a/lib/moonshot/ssh_config.rb b/lib/moonshot/ssh_config.rb index 0c000e41..d409be2e 100644 --- a/lib/moonshot/ssh_config.rb +++ b/lib/moonshot/ssh_config.rb @@ -1,5 +1,5 @@ module Moonshot - class SSHConfig # rubocop:disable Documentation + class SSHConfig attr_accessor :ssh_identity_file attr_accessor :ssh_user end diff --git a/lib/moonshot/stack.rb b/lib/moonshot/stack.rb index f4912bd8..0d70a823 100644 --- a/lib/moonshot/stack.rb +++ b/lib/moonshot/stack.rb @@ -22,10 +22,9 @@ class Stack # rubocop:disable ClassLength attr_reader :name # TODO: Refactor more of these parameters into the config object. - def initialize(name, app_name:, log:, ilog:, config: StackConfig.new) + def initialize(name, app_name:, ilog:, config: StackConfig.new) @name = name @app_name = app_name - @log = log @ilog = ilog @config = config yield @config if block_given? @@ -49,7 +48,7 @@ def create end def update - raise Thor::Error, "No stack found #{@name.blue}!" unless stack_exists? + raise "No stack found #{@name.blue}!" unless stack_exists? should_wait = true @ilog.start "Updating #{stack_name}." do |s| @@ -62,7 +61,7 @@ def update end success = should_wait ? wait_for_stack_state(:stack_update_complete, 'updated') : true - raise Thor::Error, 'Failed to update the CloudFormation Stack.' unless success + raise 'Failed to update the CloudFormation Stack.' unless success success end @@ -218,8 +217,8 @@ def raw_template_file_name end def load_template_file - json_template = JsonStackTemplate.new(json_template_path, log: @log) - yaml_template = YamlStackTemplate.new(yaml_template_path, log: @log) + json_template = JsonStackTemplate.new(json_template_path) + yaml_template = YamlStackTemplate.new(yaml_template_path) case when json_template.exist? json_template @@ -268,11 +267,11 @@ def parent_stack_outputs # @return [Aws::CloudFormation::Types::Stack] def get_stack(name) stacks = cf_client.describe_stacks(stack_name: name).stacks - raise Thor::Error, "Could not describe stack: #{name}" if stacks.empty? + raise "Could not describe stack: #{name}" if stacks.empty? stacks.first rescue Aws::CloudFormation::Errors::ValidationError - raise Thor::Error, "Could not describe stack: #{name}" + raise "Could not describe stack: #{name}" end def create_stack @@ -286,7 +285,7 @@ def create_stack ] ) rescue Aws::CloudFormation::Errors::AccessDenied - raise Thor::Error, 'You are not authorized to perform create_stack calls.' + raise 'You are not authorized to perform create_stack calls.' end # @return [Boolean] @@ -304,7 +303,7 @@ def update_stack ) true rescue Aws::CloudFormation::Errors::ValidationError => e - raise Thor::Error, e.message unless + raise e.message unless e.message == 'No updates are to be performed.' false end diff --git a/lib/moonshot/stack_lister.rb b/lib/moonshot/stack_lister.rb index a8b99e5c..a8d66d72 100644 --- a/lib/moonshot/stack_lister.rb +++ b/lib/moonshot/stack_lister.rb @@ -3,16 +3,15 @@ module Moonshot class StackLister include CredsHelper - def initialize(app_name, log:) + def initialize(app_name) @app_name = app_name - @log = log end def list all_stacks = cf_client.describe_stacks.stacks app_stacks = all_stacks.reject { |s| s.stack_name !~ /^#{@app_name}/ } - app_stacks.each do |stack| + app_stacks.sort_by(&:stack_name).each do |stack| puts stack.stack_name end end diff --git a/lib/moonshot/stack_template.rb b/lib/moonshot/stack_template.rb index 374657e6..364d8e46 100644 --- a/lib/moonshot/stack_template.rb +++ b/lib/moonshot/stack_template.rb @@ -8,8 +8,7 @@ def required? end end - def initialize(filename, log:) - @log = log + def initialize(filename) @filename = filename end diff --git a/lib/moonshot/task.rb b/lib/moonshot/task.rb new file mode 100644 index 00000000..68ec97db --- /dev/null +++ b/lib/moonshot/task.rb @@ -0,0 +1,10 @@ +module Moonshot + class Task + attr_reader :name, :desc, :block + def initialize(name, desc, &block) + @name = name.to_sym + @desc = desc + @block = block + end + end +end diff --git a/lib/moonshot/tools/asg_rollout.rb b/lib/moonshot/tools/asg_rollout.rb index a6784f75..c4b806c0 100644 --- a/lib/moonshot/tools/asg_rollout.rb +++ b/lib/moonshot/tools/asg_rollout.rb @@ -4,7 +4,7 @@ module Moonshot module Tools - class ASGRollout # rubocop:disable Documentation + class ASGRollout attr_accessor :config def initialize(controller:, logical_id:) diff --git a/lib/moonshot/tools/asg_rollout/instance_health.rb b/lib/moonshot/tools/asg_rollout/instance_health.rb index 83f74f1c..23cad106 100644 --- a/lib/moonshot/tools/asg_rollout/instance_health.rb +++ b/lib/moonshot/tools/asg_rollout/instance_health.rb @@ -1,7 +1,7 @@ module Moonshot module Tools class ASGRollout - class InstanceHealth # rubocop:disable Documentation + class InstanceHealth attr_reader :asg_status, :elb_status VALID_ASG_IN_SERVICE_STATES = ['InService'].freeze diff --git a/lib/moonshot/tools/asg_rollout_config.rb b/lib/moonshot/tools/asg_rollout_config.rb index 6af349c6..84177034 100644 --- a/lib/moonshot/tools/asg_rollout_config.rb +++ b/lib/moonshot/tools/asg_rollout_config.rb @@ -1,6 +1,6 @@ module Moonshot module Tools - class ASGRolloutConfig # rubocop:disable Documentation + class ASGRolloutConfig attr_reader :pre_detach, :terminate_when, :terminate_when_timeout, :terminate attr_accessor :terminate_when_delay, :instance_health_delay diff --git a/moonshot.gemspec b/moonshot.gemspec index 20d3877c..a4e18804 100644 --- a/moonshot.gemspec +++ b/moonshot.gemspec @@ -1,14 +1,16 @@ Gem::Specification.new do |s| s.name = 'moonshot' - s.version = '0.7.7' + s.version = '1.0.0' s.licenses = ['Apache-2.0'] - s.summary = 'A library for launching services into AWS' - s.description = 'A library for launching services into AWS.' + s.summary = 'A library and CLI tool for launching services into AWS' + s.description = 'A library and CLI tool for launching services into AWS.' s.authors = [ 'Cloud Engineering ' ] s.email = 'engineering@acquia.com' - s.files = Dir['lib/**/*.rb'] + s.files = Dir['lib/**/*.rb'] + Dir['bin/*'] + s.bindir = 'bin' + s.executables = ['moonshot'] s.homepage = 'https://github.com/acquia/moonshot' s.add_dependency('aws-sdk', '~> 2.0', '>= 2.2.0') @@ -24,6 +26,7 @@ Gem::Specification.new do |s| s.add_dependency('semantic') s.add_dependency('travis') s.add_dependency('vandamme') + s.add_dependency('pry') s.add_development_dependency('rspec') s.add_development_dependency('simplecov') diff --git a/sample/Moonfile b/sample/Moonfile new file mode 100644 index 00000000..9522a4d0 --- /dev/null +++ b/sample/Moonfile @@ -0,0 +1,7 @@ +Moonshot.config do |m| + m.app_name = 'moonshot-sample-app' + m.artifact_repository = S3Bucket.new('moonshot-sample-bucket2') + m.build_mechanism = Script.new('bin/build.sh') + m.deployment_mechanism = CodeDeploy.new(asg: 'AutoScalingGroup') +end + diff --git a/sample/bin/environment b/sample/bin/environment deleted file mode 100755 index 76f2f641..00000000 --- a/sample/bin/environment +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env ruby - -require 'bundler/setup' -require 'moonshot' - -# Set up Moonshot tooling for our environment. -class MoonshotSampleApp < Moonshot::CLI - self.application_name = 'moonshot-sample-app' - self.artifact_repository = S3Bucket.new('moonshot-sample-bucket2') - self.build_mechanism = Script.new('bin/build.sh') - self.deployment_mechanism = CodeDeploy.new(asg: 'AutoScalingGroup') -end - -MoonshotSampleApp.start diff --git a/spec/moonshot/build_mechanism/github_release_spec.rb b/spec/moonshot/build_mechanism/github_release_spec.rb index 322b2e1d..0a4f3bc8 100644 --- a/spec/moonshot/build_mechanism/github_release_spec.rb +++ b/spec/moonshot/build_mechanism/github_release_spec.rb @@ -8,7 +8,6 @@ module Moonshot # rubocop:disable ModuleLength let(:resources) do Resources.new( ilog: instance_double(InteractiveLogger).as_null_object, - log: instance_double(Logger).as_null_object, stack: instance_double(Stack).as_null_object ) end diff --git a/spec/moonshot/build_mechanism/travis_deploy_spec.rb b/spec/moonshot/build_mechanism/travis_deploy_spec.rb index e32ee228..e51d8bcf 100644 --- a/spec/moonshot/build_mechanism/travis_deploy_spec.rb +++ b/spec/moonshot/build_mechanism/travis_deploy_spec.rb @@ -4,7 +4,6 @@ module Moonshot # rubocop:disable Metrics/ModuleLength let(:resources) do Resources.new( ilog: double(InteractiveLogger).as_null_object, - log: double(Logger).as_null_object, stack: double(Stack).as_null_object ) end diff --git a/spec/moonshot/cli_spec.rb b/spec/moonshot/cli_spec.rb deleted file mode 100644 index f1c7cb2a..00000000 --- a/spec/moonshot/cli_spec.rb +++ /dev/null @@ -1,70 +0,0 @@ -require 'moonshot' - -class AppWithAutoPrefix < Moonshot::CLI - self.application_name = 'my-app' -end - -class AppWithoutAutoPrefix < Moonshot::CLI - self.auto_prefix_stack = false - self.application_name = 'my-other-app' -end - -class AppToTest < Moonshot::CLI - self.application_name = 'my-app' -end - -describe AppWithAutoPrefix do - let(:stack) { instance_double(Moonshot::Stack) } - before(:each) do - expect(stack).to receive(:status) - stub_const('ENV', 'USER' => 'rspec') - end - - it 'should prepend the app name when not specified' do - expect(Moonshot::Stack).to receive(:new) - .with('my-app-stack1', an_instance_of(Hash)).and_return(stack) - described_class.start('status -n stack1'.split) - end - - it 'should not prepend the app name with already specified' do - expect(Moonshot::Stack).to receive(:new) - .with('my-app-stack1', an_instance_of(Hash)).and_return(stack) - described_class.start('status -n my-app-stack1'.split) - end -end - -describe AppWithoutAutoPrefix do - let(:stack) { instance_double(Moonshot::Stack) } - before(:each) do - expect(stack).to receive(:status) - stub_const('ENV', 'USER' => 'rspec') - end - - it 'should not prepend the app name when not specified' do - expect(Moonshot::Stack).to receive(:new) - .with('stack1', an_instance_of(Hash)).and_return(stack) - described_class.start('status -n stack1'.split) - end - it 'should not prepend the app name when already specified' do - expect(Moonshot::Stack).to receive(:new) - .with('my-other-app-stack1', an_instance_of(Hash)).and_return(stack) - described_class.start('status -n my-other-app-stack1'.split) - end -end - -describe AppToTest do - before(:each) do - stub_const('ENV', 'USER' => 'rspec') - end - - it 'created a new controller object' do - controller = instance_double(Moonshot::Controller) - expect(Moonshot::Controller).to receive(:new).and_return(controller) - described_class.new.controller - end - - it 'can access stack by inheriting Moonshot::CLI' do - expect(Moonshot::Stack).to receive(:new) - described_class.new.controller.stack - end -end diff --git a/spec/moonshot/plugins_spec.rb b/spec/moonshot/plugins_spec.rb index 3557e7d8..a05ce7d3 100644 --- a/spec/moonshot/plugins_spec.rb +++ b/spec/moonshot/plugins_spec.rb @@ -6,27 +6,6 @@ def post_create end end -describe 'the Moonshot::CLI interface to plugins' do - let(:controller) { instance_double('Moonshot::Controller') } - - subject do - Class.new(Moonshot::CLI) do - self.application_name = 'my-app' - plugin MockPlugin.new - plugin MockPlugin.new - end - end - - it 'sets the plugins provided to Moonshot::Controller' do - config = Moonshot::ControllerConfig.new - expect(Moonshot::Controller).to receive(:new).and_yield(config).and_return(controller) - expect(controller).to receive(:status) - - subject.start(['status']) - expect(config.plugins.size).to eq(2) - end -end - describe 'Plugins support' do let(:plugin1) { MockPlugin.new } let(:plugin2) { MockPlugin.new } @@ -37,7 +16,6 @@ def post_create Moonshot::Controller.new do |config| config.app_name = 'my-app' config.plugins = [plugin1, plugin2] - config.logger = Logger.new(nil) end end diff --git a/spec/moonshot/shell_spec.rb b/spec/moonshot/shell_spec.rb index c59bc946..fb8b1942 100644 --- a/spec/moonshot/shell_spec.rb +++ b/spec/moonshot/shell_spec.rb @@ -6,10 +6,8 @@ module Moonshot include described_class let(:resources) do - log = instance_double(Logger).as_null_object Resources.new( ilog: InteractiveLoggerProxy.new(log), - log: log, stack: double(Stack).as_null_object ) end diff --git a/spec/moonshot/ssh_spec.rb b/spec/moonshot/ssh_spec.rb index 611fbc15..2e8cf681 100644 --- a/spec/moonshot/ssh_spec.rb +++ b/spec/moonshot/ssh_spec.rb @@ -18,11 +18,11 @@ expect_any_instance_of(Moonshot::SSHCommandBuilder).to receive(:instance_ip).exactly(2) .and_return('123.123.123.123') - expect(STDOUT).to receive(:puts) - .with('Opening SSH connection to i-04683a82f2dddcc04 (123.123.123.123)...') expect(subject).to receive(:exec) .with('ssh -t -i /Users/joeuser/.ssh/thegoods.key -l joeuser 123.123.123.123 cat\ /etc/passwd') # rubocop:disable LineLength - subject.ssh + expect { subject.ssh } + .to output("Opening SSH connection to i-04683a82f2dddcc04 (123.123.123.123)...\n") + .to_stderr end end @@ -36,11 +36,10 @@ it 'should execute an ssh command with proper parameters' do expect_any_instance_of(Moonshot::SSHCommandBuilder).to receive(:instance_ip).exactly(2) .and_return('123.123.123.123') - expect(STDOUT).to receive(:puts) - .with('Opening SSH connection to i-012012012012012 (123.123.123.123)...') expect(subject).to receive(:exec) .with('ssh -t -i /Users/joeuser/.ssh/thegoods.key -l joeuser 123.123.123.123 cat\ /etc/passwd') # rubocop:disable LineLength - subject.ssh + expect { subject.ssh } + .to output("Opening SSH connection to i-012012012012012 (123.123.123.123)...\n").to_stderr end end end diff --git a/spec/moonshot/stack_spec.rb b/spec/moonshot/stack_spec.rb index 0385df02..a6509f51 100644 --- a/spec/moonshot/stack_spec.rb +++ b/spec/moonshot/stack_spec.rb @@ -1,13 +1,13 @@ describe Moonshot::Stack do include_context 'with a working moonshot application' - let(:ilog) { Moonshot::InteractiveLoggerProxy.new(log) } let(:log) { instance_double('Logger').as_null_object } + let(:ilog) { Moonshot::InteractiveLoggerProxy.new(log) } let(:parent_stacks) { [] } let(:cf_client) { instance_double(Aws::CloudFormation::Client) } subject do - described_class.new('test', app_name: 'rspec-app', log: log, ilog: ilog) do |c| + described_class.new('test', app_name: 'rspec-app', ilog: ilog) do |c| c.parent_stacks = parent_stacks end end