diff --git a/lib/moonshot/artifact_repository/s3_bucket.rb b/lib/moonshot/artifact_repository/s3_bucket.rb index 448dfbce..5f8e3c99 100644 --- a/lib/moonshot/artifact_repository/s3_bucket.rb +++ b/lib/moonshot/artifact_repository/s3_bucket.rb @@ -48,6 +48,7 @@ def upload_to_s3(file, key) ) end + add_doctor_check :doctor_check_bucket_exists def doctor_check_bucket_exists s3_client.get_bucket_location(bucket: @bucket_name) success "Bucket '#{@bucket_name}' exists." @@ -59,6 +60,7 @@ def doctor_check_bucket_exists warning(str, e.message) end + add_doctor_check :doctor_check_bucket_writable def doctor_check_bucket_writable s3_client.put_object( key: 'test-object', diff --git a/lib/moonshot/artifact_repository/s3_bucket_via_github_releases.rb b/lib/moonshot/artifact_repository/s3_bucket_via_github_releases.rb index 5434f6da..71031fe3 100644 --- a/lib/moonshot/artifact_repository/s3_bucket_via_github_releases.rb +++ b/lib/moonshot/artifact_repository/s3_bucket_via_github_releases.rb @@ -10,6 +10,7 @@ module Moonshot::ArtifactRepository class S3BucketViaGithubReleases < S3Bucket include Moonshot::BuildMechanism include Moonshot::Shell + include Moonshot::DoctorHelper # @override # If release version, transfer from GitHub to S3. @@ -77,6 +78,7 @@ def github_to_s3(version, s3_name) end end + add_doctor_check :doctor_check_hub_release_download def doctor_check_hub_release_download sh_out('hub release download --help') rescue diff --git a/lib/moonshot/build_mechanism/github_release.rb b/lib/moonshot/build_mechanism/github_release.rb index 81d9cedb..ff23efb8 100644 --- a/lib/moonshot/build_mechanism/github_release.rb +++ b/lib/moonshot/build_mechanism/github_release.rb @@ -21,9 +21,9 @@ def initialize(build_mechanism) @build_mechanism = build_mechanism end - def doctor_hook + def doctor_hook(options = {}) super - @build_mechanism.doctor_hook + @build_mechanism.doctor_hook(options) end def resources=(r) @@ -174,6 +174,7 @@ def releases_url `hub browse -u -- releases`.chomp end + add_doctor_check :doctor_check_upstream def doctor_check_upstream sh_out('git remote | grep ^upstream$') rescue => e @@ -182,12 +183,22 @@ def doctor_check_upstream success 'git remote `upstream` exists.' end + add_doctor_check :doctor_check_hub_installed, is_local: true + def doctor_check_hub_installed + sh_out('hub version') + rescue => e + critical "`hub` is not installed.\n#{e.message}" + else + success '`hub` installed.' + end + + add_doctor_check :doctor_check_hub_auth def doctor_check_hub_auth sh_out('hub ci-status 0.0.0') rescue => e - critical "`hub` failed, install hub and authorize it.\n#{e.message}" + critical "`hub` not installed or not authorized.\n#{e.message}" else - success '`hub` installed and authorized.' + success '`hub` authorized.' end end end diff --git a/lib/moonshot/build_mechanism/script.rb b/lib/moonshot/build_mechanism/script.rb index db3df3c1..b038366d 100644 --- a/lib/moonshot/build_mechanism/script.rb +++ b/lib/moonshot/build_mechanism/script.rb @@ -74,6 +74,7 @@ def run_script(step, env: {}) # rubocop:disable AbcSize end end + add_doctor_check :doctor_check_script_exists, is_config: true def doctor_check_script_exists if File.exist?(@script) success "Script '#{@script}' exists." diff --git a/lib/moonshot/build_mechanism/travis_deploy.rb b/lib/moonshot/build_mechanism/travis_deploy.rb index 393fc35c..c09e828e 100644 --- a/lib/moonshot/build_mechanism/travis_deploy.rb +++ b/lib/moonshot/build_mechanism/travis_deploy.rb @@ -132,12 +132,22 @@ def check_build(version) end end + add_doctor_check :doctor_check_travis_installed, is_local: true + def doctor_check_travis_installed + sh_out('bundle exec travis version') + rescue => e + critical "`travis` not installed.\n#{e.message}" + else + success '`travis` installed.' + end + + add_doctor_check :doctor_check_travis_auth def doctor_check_travis_auth sh_out("bundle exec travis raw #{@endpoint} repos/#{@slug}") rescue => e - critical "`travis` not available or not authorized.\n#{e.message}" + critical "`travis` not installed or not authorized.\n#{e.message}" else - success '`travis` installed and authorized.' + success '`travis` authorized.' end end end diff --git a/lib/moonshot/build_mechanism/version_proxy.rb b/lib/moonshot/build_mechanism/version_proxy.rb index fe5fdbbb..bbfd2e2b 100644 --- a/lib/moonshot/build_mechanism/version_proxy.rb +++ b/lib/moonshot/build_mechanism/version_proxy.rb @@ -14,9 +14,9 @@ def initialize(release:, dev:) @dev = dev end - def doctor_hook - @release.doctor_hook - @dev.doctor_hook + def doctor_hook(options = {}) + @release.doctor_hook(options) + @dev.doctor_hook(options) end def resources=(r) diff --git a/lib/moonshot/cli.rb b/lib/moonshot/cli.rb index 5a512c83..4964fd5e 100644 --- a/lib/moonshot/cli.rb +++ b/lib/moonshot/cli.rb @@ -206,8 +206,10 @@ def delete end desc :doctor, 'Run configuration checks against current environment.' + option :local, aliases: 'l', type: :boolean, default: false + option :config, aliases: 'c', type: :boolean, default: false def doctor - success = controller.doctor + success = controller.doctor(options) raise Thor::Error, 'One or more checks failed.' unless success end diff --git a/lib/moonshot/controller.rb b/lib/moonshot/controller.rb index a8f2d840..06019d23 100644 --- a/lib/moonshot/controller.rb +++ b/lib/moonshot/controller.rb @@ -67,13 +67,13 @@ def delete run_plugins(:post_delete) end - def doctor + def doctor(options) # @todo use #run_hook when Stack becomes an InfrastructureProvider success = true - success &&= stack.doctor_hook - success &&= run_hook(:build, :doctor) - success &&= run_hook(:repo, :doctor) - success &&= run_hook(:deploy, :doctor) + success &&= stack.doctor_hook(options) + success &&= run_hook(:build, :doctor, options) + success &&= run_hook(:repo, :doctor, options) + success &&= run_hook(:deploy, :doctor, options) results = run_plugins(:doctor) success = false if results.value?(false) diff --git a/lib/moonshot/deployment_mechanism/code_deploy.rb b/lib/moonshot/deployment_mechanism/code_deploy.rb index d2dd0ee5..ec507ac3 100644 --- a/lib/moonshot/deployment_mechanism/code_deploy.rb +++ b/lib/moonshot/deployment_mechanism/code_deploy.rb @@ -312,6 +312,7 @@ def s3_revision_for(artifact_repo, version_name) } end + add_doctor_check :doctor_check_code_deploy_role def doctor_check_code_deploy_role iam_client.get_role(role_name: @codedeploy_role).role success("#{@codedeploy_role} exists.") @@ -325,6 +326,7 @@ def doctor_check_code_deploy_role critical("Could not find #{@codedeploy_role}, ", help) end + add_doctor_check :doctor_check_auto_scaling_resource_defined, is_config: true def doctor_check_auto_scaling_resource_defined @asg_logical_ids.each do |asg_logical_id| if stack.template.resource_names.include?(asg_logical_id) diff --git a/lib/moonshot/doctor_helper.rb b/lib/moonshot/doctor_helper.rb index f185235f..fe86b6d6 100644 --- a/lib/moonshot/doctor_helper.rb +++ b/lib/moonshot/doctor_helper.rb @@ -8,19 +8,44 @@ module Moonshot # A series of methods for adding "doctor" checks to a mechanism. # module DoctorHelper - def doctor_hook - run_all_checks + def self.included(klass) + class << klass + attr_accessor :doctor_checks + end + klass.doctor_checks = {} + klass.extend ClassMethods + end + + def doctor_hook(options = {}) + checks = self.class.doctor_checks + checks.delete_if { |_k, v| v[:is_local] == false } if options[:local] + checks.delete_if { |_k, v| v[:is_config] == false } if options[:config] + run_checks(checks) + end + + # Contains class methods + module ClassMethods + def add_doctor_check(method, flags = {}) + default_flags = { + is_local: false, + is_config: false + } + doctor_checks[method] = default_flags.merge(flags) + end end private - def run_all_checks + def run_checks(checks) + return true if checks.empty? success = true + puts puts self.class.name.split('::').last - private_methods.each do |meth| + + checks.each do |meth, _| begin - send(meth) if meth =~ /^doctor_check_/ + send(meth) rescue DoctorCritical # Stop running checks in this Mechanism. success = false diff --git a/lib/moonshot/stack.rb b/lib/moonshot/stack.rb index f5c1e497..7396ab6c 100644 --- a/lib/moonshot/stack.rb +++ b/lib/moonshot/stack.rb @@ -376,6 +376,7 @@ def format_event(event) str end + add_doctor_check :doctor_check_template_exists, is_config: true def doctor_check_template_exists if File.exist?(template_file) success "CloudFormation template found at '#{template_file}'." @@ -384,6 +385,7 @@ def doctor_check_template_exists end end + add_doctor_check :doctor_check_template_exists, is_config: true def doctor_check_template_against_aws cf_client.validate_template(template_body: template.body) success('CloudFormation template is valid.') diff --git a/spec/moonshot/build_mechanism/github_release_spec.rb b/spec/moonshot/build_mechanism/github_release_spec.rb index 322b2e1d..f13f97c2 100644 --- a/spec/moonshot/build_mechanism/github_release_spec.rb +++ b/spec/moonshot/build_mechanism/github_release_spec.rb @@ -26,51 +26,71 @@ module Moonshot # rubocop:disable ModuleLength allow(subject).to receive(:puts) allow(subject).to receive(:print) expect(subject).to receive(:doctor_check_hub_auth) + expect(subject).to receive(:doctor_check_hub_installed) expect(subject).to receive(:doctor_check_upstream) subject.doctor_hook end + end - describe '#doctor_check_upstream' do - around do |example| - Dir.mktmpdir do |path| - Dir.chdir(path) do - `git init` - example.run - end + describe '#doctor_check_upstream' do + around do |example| + Dir.mktmpdir do |path| + Dir.chdir(path) do + `git init` + example.run end end + end - it 'should fail without upstream.' do - expect(subject).to receive(:critical) - .with(/git remote `upstream` not found/) - subject.send(:doctor_check_upstream) - end + it 'should fail without upstream.' do + expect(subject).to receive(:critical) + .with(/git remote `upstream` not found/) + subject.send(:doctor_check_upstream) + end - it 'should succeed with upstream remote.' do - `git remote add upstream https://example.com/my/repo.git` - expect(subject).to receive(:success) - .with('git remote `upstream` exists.') - subject.send(:doctor_check_upstream) - end + it 'should succeed with upstream remote.' do + `git remote add upstream https://example.com/my/repo.git` + expect(subject).to receive(:success) + .with('git remote `upstream` exists.') + subject.send(:doctor_check_upstream) end + end - describe '#doctor_check_hub_auth' do - it 'should succeed with 0 exit status.' do - expect(subject).to receive(:sh_out) - .with('hub ci-status 0.0.0') - expect(subject).to receive(:success) - .with('`hub` installed and authorized.') - subject.send(:doctor_check_hub_auth) - end + describe '#doctor_check_hub_installed' do + it 'should succeed with 0 exit status.' do + expect(subject).to receive(:sh_out) + .with('hub version') + expect(subject).to receive(:success) + .with('`hub` installed.') + subject.send(:doctor_check_hub_installed) + end - it 'should critical with non-zero exit status.' do - expect(subject).to receive(:sh_out) - .with('hub ci-status 0.0.0') - .and_raise(RuntimeError, 'oops') - expect(subject).to receive(:critical) - .with("`hub` failed, install hub and authorize it.\noops") - subject.send(:doctor_check_hub_auth) - end + it 'should critical with non-zero exit status.' do + expect(subject).to receive(:sh_out) + .with('hub version') + .and_raise(RuntimeError, 'oops') + expect(subject).to receive(:critical) + .with("`hub` is not installed.\noops") + subject.send(:doctor_check_hub_installed) + end + end + + describe '#doctor_check_hub_auth' do + it 'should succeed with 0 exit status.' do + expect(subject).to receive(:sh_out) + .with('hub ci-status 0.0.0') + expect(subject).to receive(:success) + .with('`hub` authorized.') + subject.send(:doctor_check_hub_auth) + end + + it 'should critical with non-zero exit status.' do + expect(subject).to receive(:sh_out) + .with('hub ci-status 0.0.0') + .and_raise(RuntimeError, 'oops') + expect(subject).to receive(:critical) + .with("`hub` not installed or not authorized.\noops") + subject.send(:doctor_check_hub_auth) end end diff --git a/spec/moonshot/build_mechanism/travis_deploy_spec.rb b/spec/moonshot/build_mechanism/travis_deploy_spec.rb index 9a614414..8f80cf61 100644 --- a/spec/moonshot/build_mechanism/travis_deploy_spec.rb +++ b/spec/moonshot/build_mechanism/travis_deploy_spec.rb @@ -19,19 +19,38 @@ module Moonshot # rubocop:disable Metrics/ModuleLength it 'should call our hooks' do allow(subject).to receive(:puts) expect(subject).to receive(:puts).with('we did it') - expect(subject).to receive(:print).with(' ✓ '.green) + expect(subject).to receive(:print).with(' ✓ '.green).twice expect(subject).to receive(:doctor_check_travis_auth) do subject.send(:success, 'we did it') end subject.doctor_hook end + describe '#doctor_check_travis_installed' do + it 'should pass if travis exits 0' do + expect(subject).to receive(:sh_out) + .with('bundle exec travis version') + expect(subject).to receive(:success) + .with('`travis` installed.') + subject.send(:doctor_check_travis_installed) + end + + it 'should pass fail travis exits 1' do + expect(subject).to receive(:sh_out) + .with('bundle exec travis version') + .and_raise(RuntimeError, 'stuffs broke man') + expect(subject).to receive(:critical) + .with("`travis` not installed.\nstuffs broke man") + subject.send(:doctor_check_travis_installed) + end + end + describe '#doctor_check_travis_auth' do it 'should pass if travis exits 0' do expect(subject).to receive(:sh_out) .with('bundle exec travis raw --org repos/myorg/myrepo') expect(subject).to receive(:success) - .with('`travis` installed and authorized.') + .with('`travis` authorized.') subject.send(:doctor_check_travis_auth) end @@ -40,7 +59,7 @@ module Moonshot # rubocop:disable Metrics/ModuleLength .with('bundle exec travis raw --org repos/myorg/myrepo') .and_raise(RuntimeError, 'stuffs broke man') expect(subject).to receive(:critical) - .with("`travis` not available or not authorized.\nstuffs broke man") + .with("`travis` not installed or not authorized.\nstuffs broke man") subject.send(:doctor_check_travis_auth) end end