diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..fa5685d --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1 @@ +f7163e62937564239dfcd655fec48d0f342e8044 \ No newline at end of file diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml new file mode 100644 index 0000000..db6b476 --- /dev/null +++ b/.github/actions/setup/action.yml @@ -0,0 +1,21 @@ +name: Setup +description: Performs common setup steps +inputs: + ruby-version: + description: Ruby version to ues + required: true +runs: + using: composite + steps: + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ inputs.ruby-version }} + bundler: "2.5.23" + bundler-cache: true # runs 'bundle install' and caches installed gems automatically + - name: Set Git name + shell: sh + run: git config --global user.name "GitHub CI" + - name: Set Git email + shell: sh + run: git config --global user.email "github-ci@epigenesys.org.uk" diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index ebc5bc1..d7bedd2 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -9,29 +9,37 @@ name: Ruby on: push: - branches: [ master ] + branches: + - master pull_request: - branches: [ master ] jobs: test: - runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - ruby-version: ['3.0', '3.1', '3.2', '3.3', '4.0'] - + ruby-version: + - "3.1" + - "3.2" + - "3.3" + - "4.0" + steps: + - uses: actions/checkout@v2 + - name: Setup + uses: ./.github/actions/setup + with: + ruby-version: ${{ matrix.ruby-version }} + - name: Run tests + run: bundle exec rspec + rubocop: + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby-version }} - bundler: '2.5.23' - bundler-cache: true # runs 'bundle install' and caches installed gems automatically - - name: Set Git name - run: git config --global user.name "GitHub CI" - - name: Set Git email - run: git config --global user.email "github-ci@epigenesys.org.uk" - - name: Run tests - run: bundle exec rspec + - uses: actions/checkout@v2 + - name: Setup + uses: ./.github/actions/setup + with: + ruby-version: 4.0 + - name: Run rubocop + run: bundle exec rubocop + continue-on-error: true diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..278f23f --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,2 @@ +inherit_gem: + epi-rubocop: rubocop.yml diff --git a/Gemfile b/Gemfile index 1b361cc..63fd2a8 100644 --- a/Gemfile +++ b/Gemfile @@ -1,12 +1,8 @@ -source 'https://rubygems.org' +source "https://rubygems.org" # Specify your gem's dependencies in epi_deploy.gemspec gemspec group :development do - gem "rspec", '~> 3.1' - gem "aruba", '~> 1.0.0' - gem 'byebug' - - gem 'capistrano', '~> 3.6' + gem "epi-rubocop", require: false, group: :development, git: "https://github.com/epigenesys/epi-rubocop.git" end diff --git a/LICENSE.txt b/LICENSE.txt index 27296b1..4c26491 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2014 Epi Genesys Ltd +Copyright (c) 2026 Epi Genesys Ltd MIT License diff --git a/epi_deploy.gemspec b/epi_deploy.gemspec index 69ec665..e84846b 100644 --- a/epi_deploy.gemspec +++ b/epi_deploy.gemspec @@ -1,23 +1,26 @@ -# -*- encoding: utf-8 -*- -lib = File.expand_path('../lib', __FILE__) -$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) -require 'epi_deploy/version' +require_relative "lib/epi_deploy/version" Gem::Specification.new do |gem| - gem.name = "epi_deploy" - gem.version = EpiDeploy::VERSION - gem.authors = ["Anthony Nettleship", "Shuo Chen", "Chris Hunt", "James Gregory", "William Lee"] - gem.email = ["anthony.nettleship@epigenesys.org.uk", "shuo.chen@epigenesys.org.uk", "chris.hunt@epigenesys.org.uk", "james.gregory@epigenesys.org.uk", "william.lee@epigenesys.org.uk"] - gem.description = "A gem to facilitate deployment across multiple git branches and evironments" - gem.summary = "eD" - gem.homepage = "https://www.epigenesys.org.uk" + gem.name = "epi_deploy" + gem.version = EpiDeploy::VERSION + gem.authors = ["Anthony Nettleship", "Shuo Chen", "Chris Hunt", "James Gregory", "William Lee"] + gem.email = ["anthony.nettleship@epigenesys.org.uk", "shuo.chen@epigenesys.org.uk", "chris.hunt@epigenesys.org.uk", "james.gregory@epigenesys.org.uk", "william.lee@epigenesys.org.uk"] + gem.summary = "A gem to facilitate deployment across multiple Git branches and environments using Capistrano" + gem.homepage = "https://github.com/epigenesys/epi_deploy" + gem.license = "MIT" - gem.files = `git ls-files`.split($/) - gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } - gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) - gem.require_paths = ["lib"] + gem.files = Dir["bin/*", "lib/**", "LICENSE.txt", "README.md"] + gem.executables = Dir["bin/*"].map { |f| File.basename(f) } - gem.add_dependency('bundler', '>= 2.5.23') - gem.add_dependency('git', '~> 1.5') - gem.add_dependency('slop', '~> 3.6') + gem.required_ruby_version = ">= 3.1" + + gem.add_dependency "git", "~> 1.5" + gem.add_dependency "slop", "~> 3.6" + gem.add_dependency "bundler", ">= 2.5.23", "< 5" + + gem.add_development_dependency "rspec", "~> 3.1" + gem.add_development_dependency "aruba", "~> 1.0.0" + gem.add_development_dependency "byebug", "~> 11.0" + gem.add_development_dependency "capistrano", "~> 3.6" + gem.add_development_dependency "irb", "~> 1.0" end diff --git a/lib/capistrano/epi_deploy.rb b/lib/capistrano/epi_deploy.rb index 3be9d81..da0cc85 100644 --- a/lib/capistrano/epi_deploy.rb +++ b/lib/capistrano/epi_deploy.rb @@ -1 +1 @@ -Dir.glob(File.join(File.dirname(__FILE__), 'tasks', '*.rb')).each { |f| require f } \ No newline at end of file +Dir.glob(File.join(File.dirname(__FILE__), "tasks", "*.rb")).each { |f| require f } diff --git a/lib/capistrano/tasks/branches.rb b/lib/capistrano/tasks/branches.rb index 437b65a..292d8db 100644 --- a/lib/capistrano/tasks/branches.rb +++ b/lib/capistrano/tasks/branches.rb @@ -1,4 +1,4 @@ -require_relative '../../epi_deploy/config' +require_relative "../../epi_deploy/config" Dir["config/initializers/version.rb", "config/epi_deploy.rb"].each do |file| require File.join(Dir.pwd, file) @@ -7,19 +7,19 @@ if EpiDeploy.use_timestamped_deploy_tags namespace :epi_deploy do task :set_branch do - branch = if ENV['BRANCH'] - ENV['BRANCH'] - elsif Object.const_defined?('LATEST_RELEASE_TAG') + branch = if ENV["BRANCH"] + ENV["BRANCH"] + elsif Object.const_defined?("LATEST_RELEASE_TAG") LATEST_RELEASE_TAG end if branch.nil? - raise 'Cannot determine commit to deploy as BRANCH environment variable is not set and LATEST_RELEASE_TAG constant in version.rb could not be found' + raise "Cannot determine commit to deploy as BRANCH environment variable is not set and LATEST_RELEASE_TAG constant in version.rb could not be found" end set :branch, branch end end - before 'deploy:starting', 'epi_deploy:set_branch' + before "deploy:starting", "epi_deploy:set_branch" end diff --git a/lib/capistrano/tasks/multi_customers.rb b/lib/capistrano/tasks/multi_customers.rb index a2e2f22..45d5e33 100644 --- a/lib/capistrano/tasks/multi_customers.rb +++ b/lib/capistrano/tasks/multi_customers.rb @@ -4,7 +4,7 @@ customer_settings_file = "config/customers/customer_settings/#{fetch(:current_customer)}.yml" customer_settings_path = release_path.join(customer_settings_file) if test("[ -f '#{customer_settings_path}' ]") - execute :ln, '-s', customer_settings_path, release_path.join("config/customer_settings.yml") + execute :ln, "-s", customer_settings_path, release_path.join("config/customer_settings.yml") else warn "Not symlinking customer configuration as #{customer_settings_file} not found" end @@ -42,7 +42,7 @@ def iterate_over_customers Dir.glob("config/deploy/#{fetch(:stage)}.*.rb").sort.each_with_index do |file, index| # Extract the variables required - stage_and_customer = File.basename file, '.rb' + stage_and_customer = File.basename file, ".rb" customer = stage_and_customer[/\.(.+)$/, 1] # Insert a blank line between each run @@ -53,5 +53,5 @@ def iterate_over_customers end end -task deploy_all: 'deploy_all:deploy' -after 'deploy:updating', 'epi_deploy:symlink_customer_configs' +task deploy_all: "deploy_all:deploy" +after "deploy:updating", "epi_deploy:symlink_customer_configs" diff --git a/lib/epi_deploy.rb b/lib/epi_deploy.rb index 3797854..dc9c8ea 100644 --- a/lib/epi_deploy.rb +++ b/lib/epi_deploy.rb @@ -1 +1 @@ -require "epi_deploy/version" \ No newline at end of file +require "epi_deploy/version" diff --git a/lib/epi_deploy/app_version.rb b/lib/epi_deploy/app_version.rb index 6df313e..ff2b922 100644 --- a/lib/epi_deploy/app_version.rb +++ b/lib/epi_deploy/app_version.rb @@ -1,9 +1,10 @@ module EpiDeploy class AppVersion + attr_accessor :version_file_path, :version, :latest_release_tag - + def initialize(current_dir = Dir.pwd) - self.version_file_path = File.join(current_dir, 'config/initializers/version.rb') + self.version_file_path = File.join(current_dir, "config/initializers/version.rb") end def load @@ -15,12 +16,12 @@ def load end else self.version = 0 - self.latest_release_tag = '' + self.latest_release_tag = "" end end def save! - File.open version_file_path, 'w' do |f| + File.open version_file_path, "w" do |f| f.write "APP_VERSION = '#{version}'\n" f.write "LATEST_RELEASE_TAG = '#{latest_release_tag}'\n" end @@ -37,21 +38,22 @@ def self.open(current_dir = Dir.pwd) end private - def version_file_exists? - File.exist? version_file_path - end - - def extract_version_number(contents) - contents.match(/APP_VERSION\s*=\s*'(?\d+).*'/)[:version].to_i - end - def extract_latest_release_tag(contents) - if (match = contents.match(/LATEST_RELEASE_TAG\s*=\s*'(?[A-Za-z0-9_-]+)'/)) - match[:tag] - else - '' - end + def version_file_exists? + File.exist? version_file_path + end + + def extract_version_number(contents) + contents.match(/APP_VERSION\s*=\s*'(?\d+).*'/)[:version].to_i + end + + def extract_latest_release_tag(contents) + if (match = contents.match(/LATEST_RELEASE_TAG\s*=\s*'(?[A-Za-z0-9_-]+)'/)) + match[:tag] + else + "" end - + end + end -end \ No newline at end of file +end diff --git a/lib/epi_deploy/cli.rb b/lib/epi_deploy/cli.rb index 46d6b32..754bb6b 100644 --- a/lib/epi_deploy/cli.rb +++ b/lib/epi_deploy/cli.rb @@ -1,42 +1,42 @@ -require 'slop' +require "slop" -require_relative 'command' +require_relative "command" +require_relative "config" module EpiDeploy class Cli + def run! load_config Slop.parse strict: true do + banner "Usage: bundle exec epi_deploy " + + command "release" do + description "Create a new release with optional deploy" + + on :d=, :deploy=, "Deploy to specified environment(s)", argument: :optional, as: Array, delimiter: ":" - banner 'Usage: bundle exec epi_deploy ' - - command 'release' do - description 'Create a new release with optional deploy' - - on :d=, :deploy=, 'Deploy to specified environment(s)', argument: :optional, as: Array, delimiter: ':' - run do |options, args| command = EpiDeploy::Command.new(options, args) command.release end end - - command 'deploy' do - description 'Deploys an existing release' - - on :r=, :ref=, 'Git reference to deploy', argument: :optional - + + command "deploy" do + description "Deploys an existing release" + + on :r=, :ref=, "Git reference to deploy", argument: :optional + run do |options, args| command = EpiDeploy::Command.new(options, args) command.deploy end end - + run do |options, args| Kernel.abort "\x1B[31mValid commands are 'release' and 'deploy'.\x1B[0m" end - end rescue Slop::InvalidOptionError, Slop::InvalidArgumentError, RuntimeError => e Kernel.abort "\x1B[31m#{e.message}\x1B[0m" @@ -44,11 +44,12 @@ def run! private - def load_config - config_path = File.join(Dir.pwd, 'config/epi_deploy.rb') - if File.exist?(config_path) - require config_path - end + def load_config + config_path = File.join(Dir.pwd, "config/epi_deploy.rb") + if File.exist?(config_path) + require config_path end + end + end -end \ No newline at end of file +end diff --git a/lib/epi_deploy/command.rb b/lib/epi_deploy/command.rb index 0a11380..c4d53d8 100644 --- a/lib/epi_deploy/command.rb +++ b/lib/epi_deploy/command.rb @@ -1,14 +1,18 @@ -Dir[File.join(File.dirname(__FILE__), '*.rb')].each { |f| require_relative f } +require "slop" + +require_relative "release" +require_relative "deployer" +require_relative "stages_extractor" module EpiDeploy class Command - + include EpiDeploy::Helpers - + attr_accessor :options attr_accessor :args attr_accessor :release_class - + def initialize(options, args, release_class = EpiDeploy::Release) self.options = options self.args = args @@ -25,7 +29,7 @@ def release environments = self.options.to_hash[:deploy] self.deploy(environments) unless environments.nil? end - + def deploy(environments = self.args) raise Slop::InvalidArgumentError.new("No environments provided") unless environments.any? check_environments_are_valid(environments) @@ -41,52 +45,51 @@ def deploy(environments = self.args) end end end - - + private - - def prompt_for_a_release - print_notice "Select a recent release (or just press enter for latest):" - - tag_list = {} - self.release_class.new.release_tags_list.each_with_index do |release, i| - number = i + 1 - tag_list[number.to_s] = release - print_notice "#{number}: #{release}" - end - selected_release = nil - while selected_release.nil? do - selected_release = STDIN.gets[/\d/] rescue nil - if selected_release.nil? - return :latest - else - unless tag_list.key?(selected_release) - print_failure_and_abort "Invalid selection '#{selected_release}'. Try again..." - selected_release = nil - end - end - end - tag_list[selected_release] + def prompt_for_a_release + print_notice "Select a recent release (or just press enter for latest):" + + tag_list = {} + self.release_class.new.release_tags_list.each_with_index do |release, i| + number = i + 1 + tag_list[number.to_s] = release + print_notice "#{number}: #{release}" end - def determine_release_reference(options) - if options.ref? - reference = options[:ref].to_s.strip - reference = prompt_for_a_release if reference.empty? - reference + selected_release = nil + while selected_release.nil? do + selected_release = STDIN.gets[/\d/] rescue nil + if selected_release.nil? + return :latest else - :latest + unless tag_list.key?(selected_release) + print_failure_and_abort "Invalid selection '#{selected_release}'. Try again..." + selected_release = nil + end end end - def check_environments_are_valid(environments) - invalid_environments = environments.reject { |environment| stages_extractor.valid_stage?(environment) } - raise Slop::InvalidArgumentError.new("Environment '#{invalid_environments.first}' does not exist") unless invalid_environments.empty? - end - - def stages_extractor - @stages_extractor ||= StagesExtractor.new + tag_list[selected_release] + end + + def determine_release_reference(options) + if options.ref? + reference = options[:ref].to_s.strip + reference = prompt_for_a_release if reference.empty? + reference + else + :latest end - + end + def check_environments_are_valid(environments) + invalid_environments = environments.reject { |environment| stages_extractor.valid_stage?(environment) } + raise Slop::InvalidArgumentError.new("Environment '#{invalid_environments.first}' does not exist") unless invalid_environments.empty? + end + + def stages_extractor + @stages_extractor ||= StagesExtractor.new + end + end -end \ No newline at end of file +end diff --git a/lib/epi_deploy/config.rb b/lib/epi_deploy/config.rb index d31f997..70ecbf4 100644 --- a/lib/epi_deploy/config.rb +++ b/lib/epi_deploy/config.rb @@ -1,14 +1,18 @@ module EpiDeploy + @use_tags_for_deploy = false @use_timestamped_deploy_tags = false class << self + attr_reader :use_tags_for_deploy attr_accessor :use_timestamped_deploy_tags def use_tags_for_deploy=(use_tags_for_deploy) - warn '[Deprecation Warning] The use_tags_for_deploy option is now obsolete. Remove this from your configuration.' + warn "[Deprecation Warning] The use_tags_for_deploy option is now obsolete. Remove this from your configuration." @use_tags_for_deploy = use_tags_for_deploy end + end -end \ No newline at end of file + +end diff --git a/lib/epi_deploy/deployer.rb b/lib/epi_deploy/deployer.rb index 23f02a0..e591344 100644 --- a/lib/epi_deploy/deployer.rb +++ b/lib/epi_deploy/deployer.rb @@ -1,10 +1,13 @@ -require 'git' +require "git" -require_relative 'helpers' -require_relative 'stages_extractor' +require_relative "helpers" +require_relative "stages_extractor" +require_relative "git_wrapper" +require_relative "config" module EpiDeploy class Deployer + include Helpers def initialize(release) @@ -27,81 +30,82 @@ def deploy!(stages_or_environments) private - def deploy_with_timestamped_tags(stages_or_environments) - # Remove legacy environment branches in the local repo and remotely - print_notice 'Removing any legacy deployment branches' - git_wrapper.delete_branches(stages_extractor.environments) - - stages_or_environments.each do |stage_or_environment| - stages_extractor.stages_for_stage_or_environment(stage_or_environment).each do |stage| - tag_name = tag_name_for_stage(stage) - - completed = run_cap_deploy_to(stage) - if completed - git_wrapper.create_or_update_tag(tag_name, @release.commit) - print_success "Created deployment tag #{tag_name} on commit #{@release.commit}" - else - print_failure_and_abort "Deployment failed - please review output before deploying again" - end + def deploy_with_timestamped_tags(stages_or_environments) + # Remove legacy environment branches in the local repo and remotely + print_notice "Removing any legacy deployment branches" + git_wrapper.delete_branches(stages_extractor.environments) + + stages_or_environments.each do |stage_or_environment| + stages_extractor.stages_for_stage_or_environment(stage_or_environment).each do |stage| + tag_name = tag_name_for_stage(stage) + + completed = run_cap_deploy_to(stage) + if completed + git_wrapper.create_or_update_tag(tag_name, @release.commit) + print_success "Created deployment tag #{tag_name} on commit #{@release.commit}" + else + print_failure_and_abort "Deployment failed - please review output before deploying again" end end end + end + + def deploy_with_environment_branches(stages_or_environments) + updated_branches = Set.new + + stages_or_environments.each do |stage_or_environment| + begin + git_wrapper.pull + + matches = StagesExtractor.match_with(stage_or_environment) + # Force the tag/branch to the commit we want to deploy + unless updated_branches.include? matches[:stage] + git_wrapper.create_or_update_branch(matches[:stage], @release.commit) + updated_branches << matches[:stage] + end - def deploy_with_environment_branches(stages_or_environments) - updated_branches = Set.new - - stages_or_environments.each do |stage_or_environment| - begin - git_wrapper.pull - - matches = StagesExtractor.match_with(stage_or_environment) - # Force the tag/branch to the commit we want to deploy - unless updated_branches.include? matches[:stage] - git_wrapper.create_or_update_branch(matches[:stage], @release.commit) - updated_branches << matches[:stage] - end - - completed = run_cap_deploy_to(stage_or_environment) - if !completed - print_failure_and_abort "Deployment failed - please review output before deploying again" - end - rescue ::Git::GitExecuteError => e - print_failure_and_abort "A git error occurred: #{e.message}" + completed = run_cap_deploy_to(stage_or_environment) + if !completed + print_failure_and_abort "Deployment failed - please review output before deploying again" end + rescue ::Git::GitExecuteError => e + print_failure_and_abort "A git error occurred: #{e.message}" end end + end - def git_wrapper - @git_wrapper ||= GitWrapper.new - end + def git_wrapper + @git_wrapper ||= GitWrapper.new + end - def stages_extractor - @stages_extractor ||= StagesExtractor.new - end + def stages_extractor + @stages_extractor ||= StagesExtractor.new + end - def tag_name_for_stage(stage) - timestamp = Time.now.strftime('%Y_%m_%d-%H_%M_%S') - "deploy-#{stage}-#{timestamp}" - end + def tag_name_for_stage(stage) + timestamp = Time.now.strftime("%Y_%m_%d-%H_%M_%S") + "deploy-#{stage}-#{timestamp}" + end - def run_cap_deploy_to(environment) - print_notice "Deploying to #{environment}... " + def run_cap_deploy_to(environment) + print_notice "Deploying to #{environment}... " - task_to_run = if stages_extractor.multi_customer_stage?(environment) - "deploy_all" - else - "deploy" - end - cmd = "BRANCH=#{@release.commit} ED_REF=#{@release.reference} bundle exec cap #{environment} #{task_to_run}" + task_to_run = if stages_extractor.multi_customer_stage?(environment) + "deploy_all" + else + "deploy" + end + cmd = "BRANCH=#{@release.commit} ED_REF=#{@release.reference} bundle exec cap #{environment} #{task_to_run}" - success = Kernel.system cmd - print_notice '' + success = Kernel.system cmd + print_notice "" - exit_code = $?.to_i - if !success || exit_code != 0 - print_error "Capistrano exited with exit code #{exit_code}" - end - success + exit_code = $?.to_i + if !success || exit_code != 0 + print_error "Capistrano exited with exit code #{exit_code}" end + success + end + end end diff --git a/lib/epi_deploy/git_wrapper.rb b/lib/epi_deploy/git_wrapper.rb index 77f9509..32c943e 100644 --- a/lib/epi_deploy/git_wrapper.rb +++ b/lib/epi_deploy/git_wrapper.rb @@ -1,11 +1,12 @@ -require 'git' -require_relative './helpers' +require "git" +require_relative "./helpers" module EpiDeploy class GitWrapper + include EpiDeploy::Helpers def on_primary_branch? - ["main", "master"].include? current_branch + ["main", "master"].include? current_branch end def pending_changes? @@ -16,7 +17,7 @@ def pending_changes? end def pull - git.pull('origin', current_branch) + git.pull("origin", current_branch) end def commit(message) @@ -25,7 +26,7 @@ def commit(message) def push(branch, **options) options = { force: false, tags: true }.merge(options) - git.push 'origin', branch, **options + git.push "origin", branch, **options end def add(files = nil) @@ -38,13 +39,13 @@ def short_commit_hash def create_or_update_tag(name, commit = nil, push: true) if push - git.push('origin', "refs/tags/#{name}", delete: true) + git.push("origin", "refs/tags/#{name}", delete: true) end git.add_tag(name, commit, annotate: true, f: true, message: name) if push - git.push('origin', "refs/tags/#{name}") + git.push("origin", "refs/tags/#{name}") end end @@ -60,7 +61,7 @@ def delete_branches(branches) end def tag_list - @tag_list ||= `git for-each-ref --sort=taggerdate --format '%(tag)' refs/tags`.gsub("'", '').split.reverse + @tag_list ||= `git for-each-ref --sort=taggerdate --format '%(tag)' refs/tags`.gsub("'", "").split.reverse end def current_branch @@ -99,5 +100,6 @@ def run_custom_command(command) def commit_hash_for(ref) `git rev-list -n1 #{ref}`.strip end + end end diff --git a/lib/epi_deploy/release.rb b/lib/epi_deploy/release.rb index cd76d4b..ab869f1 100644 --- a/lib/epi_deploy/release.rb +++ b/lib/epi_deploy/release.rb @@ -1,23 +1,26 @@ -require_relative './helpers' -require 'git' +require "git" + +require_relative "helpers" +require_relative "git_wrapper" +require_relative "app_version" module EpiDeploy class Release include EpiDeploy::Helpers - MONTHS = %w(jan feb mar apr may jun jul aug sep oct nov dec) + MONTHS = %w[jan feb mar apr may jun jul aug sep oct nov dec] attr_accessor :reference, :tag, :commit def create! - return print_failure_and_abort 'You can only create a release on the main or master branch. Please switch to main or master and try again.' unless git_wrapper.on_primary_branch? - return print_failure_and_abort 'You have pending changes, please commit or stash them and try again.' if git_wrapper.pending_changes? + return print_failure_and_abort "You can only create a release on the main or master branch. Please switch to main or master and try again." unless git_wrapper.on_primary_branch? + return print_failure_and_abort "You have pending changes, please commit or stash them and try again." if git_wrapper.pending_changes? begin git_wrapper.pull - if git_wrapper.most_recent_commit.message.start_with? 'Bumped to version' + if git_wrapper.most_recent_commit.message.start_with? "Bumped to version" false else new_version = app_version.bump @@ -62,24 +65,24 @@ def self.find(reference) private - def app_version(app_version_class = EpiDeploy::AppVersion) - @app_version ||= app_version_class.open - end + def app_version(app_version_class = EpiDeploy::AppVersion) + @app_version ||= app_version_class.open + end - # Use Time.zone if we have it (i.e. Rails), otherwise use Time - def date_and_time_for_tag(time_class = (Time.respond_to?(:zone) ? Time.zone : Time)) - time = time_class.now - time.strftime "%Y#{MONTHS[time.month - 1]}%d-%H%M" - end + # Use Time.zone if we have it (i.e. Rails), otherwise use Time + def date_and_time_for_tag(time_class = (Time.respond_to?(:zone) ? Time.zone : Time)) + time = time_class.now + time.strftime "%Y#{MONTHS[time.month - 1]}%d-%H%M" + end - def get_commit(git_reference) - if git_reference == :latest - print_failure_and_abort("There is no latest release. Create one, or specify a reference with --ref") if self.release_tags_list.empty? - git_reference = release_tags_list.first - end - - git_wrapper.git_object_for(git_reference) + def get_commit(git_reference) + if git_reference == :latest + print_failure_and_abort("There is no latest release. Create one, or specify a reference with --ref") if self.release_tags_list.empty? + git_reference = release_tags_list.first end + git_wrapper.git_object_for(git_reference) + end + end end diff --git a/lib/epi_deploy/stages_extractor.rb b/lib/epi_deploy/stages_extractor.rb index 9afef4e..a0ab02b 100644 --- a/lib/epi_deploy/stages_extractor.rb +++ b/lib/epi_deploy/stages_extractor.rb @@ -1,7 +1,8 @@ -require 'set' +require "set" module EpiDeploy class StagesExtractor + STAGE_REGEX = /\A(?[\w\-]+)(?:\.(?\w+))?\z/ DEPLOY_FILE_REGEX = /\A(?[\w\-]+)(?:\.(?[\w\-]+))?\.rb\z/ @@ -18,7 +19,7 @@ def initialize end @environment_to_stages[matches[:stage]] ||= Set.new - @environment_to_stages[matches[:stage]] << matches[1..2].compact.join('.') + @environment_to_stages[matches[:stage]] << matches[1..2].compact.join(".") end # Remove stage with same name as environment if it's a multi-customer stage @@ -32,11 +33,11 @@ def initialize self.all_stages = Set.new(@environment_to_stages.values.map(&:to_a).flatten) end - + def multi_customer_stage?(stage) multi_customer_stages.include?(stage) end - + def valid_stage?(stage) all_stages.include?(stage) || multi_customer_stage?(stage) end @@ -62,11 +63,13 @@ def self.match_with(stage_or_environment) end private - def stage_config_files - @stage_config_files ||= begin - glob_pattern = File.join(Dir.pwd, 'config', 'deploy', '*.rb') - Dir[glob_pattern].map { |path| File.basename(path) } - end + + def stage_config_files + @stage_config_files ||= begin + glob_pattern = File.join(Dir.pwd, "config", "deploy", "*.rb") + Dir[glob_pattern].map { |path| File.basename(path) } end + end + end -end \ No newline at end of file +end diff --git a/lib/epi_deploy/version.rb b/lib/epi_deploy/version.rb index e3d8c0b..9c5ccee 100644 --- a/lib/epi_deploy/version.rb +++ b/lib/epi_deploy/version.rb @@ -1,3 +1,5 @@ module EpiDeploy + VERSION = "2.4.0" + end diff --git a/spec/features/deploy_spec.rb b/spec/features/deploy_spec.rb index f39e9fd..cebed50 100644 --- a/spec/features/deploy_spec.rb +++ b/spec/features/deploy_spec.rb @@ -1,13 +1,7 @@ require 'spec_helper' require 'support/aruba_helper' -describe "Deploy", type: :aruba do - - before do - setup_aruba_and_git - run_command_and_stop 'bundle install --quiet' - end - +describe "Deploy", :bundle, type: :aruba do it "errors if environment doesn't exist" do run_ed 'deploy invalidenvironment' @@ -31,5 +25,4 @@ expect(all_output).to include('Deploying to production...') expect(last_command_started).to have_exit_status(0) end - end diff --git a/spec/features/release_spec.rb b/spec/features/release_spec.rb index b6ffa62..175e6b7 100644 --- a/spec/features/release_spec.rb +++ b/spec/features/release_spec.rb @@ -1,21 +1,18 @@ require 'spec_helper' require 'support/aruba_helper' -describe "Release" do - +describe "Release", type: :aruba do it "creates a new release" do - setup_aruba_and_git run_command_and_stop 'bundle install --quiet' run_ed "release" expect(last_command_started).to have_exit_status(0) expect(all_output).to include("Release 1 created with tag #{Time.now.year}") - expect(all_output).to_not include 'Capistrano deploying to production' + expect(all_output).not_to include 'Capistrano deploying to production' end it "deploys the release if the flag is supplied" do - setup_aruba_and_git run_command_and_stop 'git tag -a example_tag -m "For testing"' # Create a pretend release run_command_and_stop 'git push' run_command_and_stop 'bundle install --quiet' @@ -28,7 +25,6 @@ end it 'does not deploy a second environment if the first environment deployment fails' do - setup_aruba_and_git run_command_and_stop 'git tag -a example_tag -m "For testing"' # Create a pretend release run_command_and_stop 'git push --tags' run_command_and_stop 'bundle install --quiet' @@ -38,8 +34,7 @@ expect(last_command_started).to have_exit_status(1) expect(all_output).to include('Deploying to demo...') expect(all_output).to include('Deployment failed - please review output before deploying again') - expect(all_output).to_not include('Deploying to production...') - expect(all_output).to_not include 'Capistrano deploying to production' + expect(all_output).not_to include('Deploying to production...') + expect(all_output).not_to include 'Capistrano deploying to production' end - end diff --git a/spec/fixtures/config/deploy/production.rb b/spec/fixtures/config/deploy/production.rb deleted file mode 100644 index e69de29..0000000 diff --git a/Capfile b/spec/fixtures/test_app/Capfile similarity index 63% rename from Capfile rename to spec/fixtures/test_app/Capfile index 1c7acdc..0501d93 100644 --- a/Capfile +++ b/spec/fixtures/test_app/Capfile @@ -1,2 +1,3 @@ require 'capistrano/setup' require 'capistrano/deploy' +require 'capistrano/epi_deploy' diff --git a/spec/fixtures/test_app/Gemfile b/spec/fixtures/test_app/Gemfile new file mode 100644 index 0000000..959cbd2 --- /dev/null +++ b/spec/fixtures/test_app/Gemfile @@ -0,0 +1,4 @@ +source "https://rubygems.org" + +gem "capistrano", "~> 3.6" +gem "epi_deploy", path: "../.." diff --git a/config/deploy.rb b/spec/fixtures/test_app/config/deploy.rb similarity index 100% rename from config/deploy.rb rename to spec/fixtures/test_app/config/deploy.rb diff --git a/config/deploy/demo.rb b/spec/fixtures/test_app/config/deploy/demo.rb similarity index 69% rename from config/deploy/demo.rb rename to spec/fixtures/test_app/config/deploy/demo.rb index d14a07c..d2cc82d 100644 --- a/config/deploy/demo.rb +++ b/spec/fixtures/test_app/config/deploy/demo.rb @@ -1,2 +1,2 @@ # Placeholder capistrano env deploy file for testing purposes -raise 'Deployment failure' +raise "Deployment failure" diff --git a/spec/fixtures/config/deploy/production.epigenesys.rb b/spec/fixtures/test_app/config/deploy/production.epigenesys.rb similarity index 100% rename from spec/fixtures/config/deploy/production.epigenesys.rb rename to spec/fixtures/test_app/config/deploy/production.epigenesys.rb diff --git a/spec/fixtures/config/deploy/production.genesys.rb b/spec/fixtures/test_app/config/deploy/production.genesys.rb similarity index 100% rename from spec/fixtures/config/deploy/production.genesys.rb rename to spec/fixtures/test_app/config/deploy/production.genesys.rb diff --git a/config/deploy/production.rb b/spec/fixtures/test_app/config/deploy/production.rb similarity index 59% rename from config/deploy/production.rb rename to spec/fixtures/test_app/config/deploy/production.rb index 18bd83a..a028559 100644 --- a/config/deploy/production.rb +++ b/spec/fixtures/test_app/config/deploy/production.rb @@ -1,2 +1,2 @@ # Placeholder capistrano env deploy file for testing purposes -puts "Capistrano deploying to production" \ No newline at end of file +puts "Capistrano deploying to production" diff --git a/spec/fixtures/config/deploy/demo.rb b/spec/fixtures/test_app/config/initializers/.gitkeep similarity index 100% rename from spec/fixtures/config/deploy/demo.rb rename to spec/fixtures/test_app/config/initializers/.gitkeep diff --git a/spec/lib/epi_deploy/app_version_spec.rb b/spec/lib/epi_deploy/app_version_spec.rb index fa7a3c6..5ec7d90 100644 --- a/spec/lib/epi_deploy/app_version_spec.rb +++ b/spec/lib/epi_deploy/app_version_spec.rb @@ -5,12 +5,12 @@ require 'epi_deploy/app_version' RSpec.describe EpiDeploy::AppVersion do + subject { described_class.new(current_dir) } + let(:current_dir) { File.expand_path(File.join('../../../../tmp/test_directory'), __FILE__) } let(:initializers_dir) { File.join(current_dir, 'config/initializers') } let(:version_file_path) { File.join(initializers_dir, 'version.rb') } - subject { described_class.new(current_dir) } - before do FileUtils.mkdir_p(initializers_dir) end diff --git a/spec/lib/epi_deploy/command_spec.rb b/spec/lib/epi_deploy/command_spec.rb index 02f35d5..66dfed5 100644 --- a/spec/lib/epi_deploy/command_spec.rb +++ b/spec/lib/epi_deploy/command_spec.rb @@ -1,10 +1,13 @@ require 'spec_helper' +require 'support/aruba_helper' + require 'epi_deploy/command' require 'epi_deploy/deployer' + require 'slop' class MockOptions - + def initialize(options = {}) @options = options end @@ -13,18 +16,21 @@ def ref? end def [](key); end def to_hash; @options; end + end class MockRelease + def create!; true; end def deploy!(environments); end def version; 5; end def tag; 'nice-taggy'; end def self.find(ref); new; end - + def tag_list [] end + end describe "Command" do @@ -32,39 +38,39 @@ def tag_list allow_any_instance_of(EpiDeploy::Helpers).to receive_messages(print_notice: nil, print_success: nil, print_failure_and_abort: nil) end - let(:options) { MockOptions.new } - let(:args) { [] } - + let(:options) { double(to_hash: {}) } + let(:args) { [] } + describe "release" do subject { EpiDeploy::Command.new options, args, MockRelease } specify "the user is notified of success" do expect(subject).to receive_messages(print_success: "Release v5 created with tag nice-taggy") + subject.release end - + describe "optional --deploy flag" do - before do release = MockRelease.new allow(release).to receive_messages(create!: true) allow(MockRelease).to receive_messages(new: release) end - + it "deploys to the specified environments if options specified" do - subject.options = MockOptions.new deploy: %w(production) - expect(subject).to receive(:deploy).with(%w(production)) + subject.options = MockOptions.new deploy: %w[production] + expect(subject).to receive(:deploy).with(%w[production]) subject.release end - + it "does not deploy if option not specified" do - expect(subject).to_not receive(:deploy) + expect(subject).not_to receive(:deploy) subject.release end end end - - describe "deploy" do + + describe "deploy", type: :aruba do subject { EpiDeploy::Command.new options, args, MockRelease } before do @@ -73,43 +79,40 @@ def tag_list end around do |example| - Dir.chdir(File.join(File.dirname(__FILE__), '../..', 'fixtures')) do + in_current_directory do example.call end end - + describe "required arguments" do let(:release) { MockRelease.new } let(:deployer) { double(:deployer) } it "errors if no deploy environment is provided" do - expect{ subject.deploy }.to raise_error(Slop::InvalidArgumentError, "No environments provided") + expect { subject.deploy }.to raise_error(Slop::InvalidArgumentError, "No environments provided") end - - it "it deploys the specified targets when they are all valid" do + + it "deploys the specified targets when they are all valid" do allow(subject).to receive_messages(determine_release_reference: :latest) allow(MockRelease).to receive_messages(find: release) allow(EpiDeploy::Deployer).to receive(:new).and_return(deployer) - expect(deployer).to receive(:deploy!).with(%w(production)) + expect(deployer).to receive(:deploy!).with(%w[production]) - subject.args = %w(production) + subject.args = %w[production] subject.deploy end - + it "only accepts environments that exist" do - subject.args = %w(cheese) - expect{subject.deploy}.to raise_error(Slop::InvalidArgumentError, "Environment 'cheese' does not exist") + subject.args = %w[cheese] + expect { subject.deploy }.to raise_error(Slop::InvalidArgumentError, "Environment 'cheese' does not exist") end end - + describe "optional --ref flag" do subject { EpiDeploy::Command.new options, ['production'], MockRelease } - let(:options) do - options = Hash.new - options - end + let(:options) { double } before do allow(options).to receive_messages(ref?: true) @@ -120,25 +123,25 @@ def tag_list expect(subject.release_class).to receive(:find).with(:latest).and_return(MockRelease.new) subject.deploy end - + specify "if flag supplied with no argument then list of releases displayed with choice" do - options[:ref] = nil + allow(options).to receive(:[]).with(:ref).and_return(nil) expect(subject).to receive(:prompt_for_a_release) subject.deploy end - + it "can be supplied with a git reference" do - options[:ref] = 'an_exisiting_ref' + allow(options).to receive(:[]).with(:ref).and_return("an_exisiting_ref") expect(subject.release_class).to receive(:find).with('an_exisiting_ref').and_return(MockRelease.new) subject.deploy end - + it "errors if the reference not exist" do - options[:ref] = 'invalid_ref' + allow(options).to receive(:[]).with(:ref).and_return("invalid_ref") allow(MockRelease).to receive_messages(find: nil) expect(subject).to receive(:print_failure_and_abort).with("You did not enter a valid Git reference. Please try again.") subject.deploy end end end -end \ No newline at end of file +end diff --git a/spec/lib/epi_deploy/deployer_spec.rb b/spec/lib/epi_deploy/deployer_spec.rb index bdb47e8..a2c32ab 100644 --- a/spec/lib/epi_deploy/deployer_spec.rb +++ b/spec/lib/epi_deploy/deployer_spec.rb @@ -1,10 +1,12 @@ +require 'spec_helper' +require 'support/aruba_helper' + require 'time' require 'epi_deploy/deployer' -require 'spec_helper' - class MockGit + def initialize(on_primary_branch: true, pending_changes: false) @on_primary_branch = on_primary_branch @pending_changes = pending_changes @@ -21,6 +23,7 @@ def current_branch; 'main'; end def create_or_update_tag(name, commit); end def create_or_update_branch(name, commit); end def delete_branches(branches); end + end def deployment_stage_with_timestamp(stage) @@ -31,25 +34,26 @@ def deployment_stage_with_timestamp(stage) end RSpec.describe EpiDeploy::Deployer do + subject { described_class.new(release) } + let(:release) { double('release') } let(:system_exit) { Exception.new('test exception') } let(:git_wrapper) { MockGit.new } - subject { described_class.new(release) } before do allow(release).to receive_messages(reference: 'test', commit: 'caa2c06f96cb0e52cdc6059014bc69bd94573d7a592b8c380bca5348e1f6806e0e9ad9bd12d7a78b') allow(subject).to receive_messages(git_wrapper: git_wrapper) end - describe "#deploy!" do + describe "#deploy!", type: :aruba do before do allow_any_instance_of(EpiDeploy::Helpers).to receive_messages(print_error: nil, print_notice: nil, print_success: nil) allow_any_instance_of(EpiDeploy::Helpers).to receive(:print_failure_and_abort) { raise system_exit } end around do |example| - Dir.chdir(File.join(File.dirname(__FILE__), '../..', 'fixtures')) do - example.run + in_current_directory do + example.call end end @@ -64,8 +68,8 @@ def deployment_stage_with_timestamp(stage) expect(Kernel).to receive(:system).with("BRANCH=#{release.commit} ED_REF=test bundle exec cap production.genesys deploy").and_return(true) expect do - subject.deploy! %w(demo production) - end.to_not raise_error + subject.deploy! %w[demo production] + end.not_to raise_error end context 'if deployment to all stages is successful' do @@ -86,11 +90,11 @@ def deployment_stage_with_timestamp(stage) it 'only adds the tag to the deployment stages have succeeded' do expect(Kernel).to receive(:system).with("BRANCH=#{release.commit} ED_REF=test bundle exec cap production.epigenesys deploy").and_return(true) expect(Kernel).to receive(:system).with("BRANCH=#{release.commit} ED_REF=test bundle exec cap production.genesys deploy").and_return(false) - expect(Kernel).to_not receive(:system).with("BRANCH=#{release.commit} ED_REF=test bundle exec cap demo deploy") + expect(Kernel).not_to receive(:system).with("BRANCH=#{release.commit} ED_REF=test bundle exec cap demo deploy") expect(git_wrapper).to receive(:create_or_update_tag).with(deployment_stage_with_timestamp('production.epigenesys'), release.commit) - expect(git_wrapper).to_not receive(:create_or_update_tag).with(deployment_stage_with_timestamp('production.genesys'), release.commit) - expect(git_wrapper).to_not receive(:create_or_update_tag).with(deployment_stage_with_timestamp('demo'), release.commit) + expect(git_wrapper).not_to receive(:create_or_update_tag).with(deployment_stage_with_timestamp('production.genesys'), release.commit) + expect(git_wrapper).not_to receive(:create_or_update_tag).with(deployment_stage_with_timestamp('demo'), release.commit) expect { subject.deploy! ['production.epigenesys', 'production.genesys', 'demo'] }.to raise_error system_exit end @@ -115,16 +119,16 @@ def deployment_stage_with_timestamp(stage) expect(Kernel).to receive(:system).with("BRANCH=#{release.commit} ED_REF=test bundle exec cap demo deploy").and_return(true) expect do - subject.deploy! %w(demo) - end.to_not raise_error + subject.deploy! %w[demo] + end.not_to raise_error end it 'runs the capistrano deploy_all task for multi-customer environments' do expect(Kernel).to receive(:system).with("BRANCH=#{release.commit} ED_REF=test bundle exec cap production deploy_all").and_return(true) expect do - subject.deploy! %w(production) - end.to_not raise_error + subject.deploy! %w[production] + end.not_to raise_error end it 'creates a branch for each deployment environment' do @@ -135,14 +139,14 @@ def deployment_stage_with_timestamp(stage) expect do subject.deploy! ['production.epigenesys', 'production.genesys', 'demo'] - end.to_not raise_error + end.not_to raise_error end it 'creates a branch for a deployment stage each if it does not succeed but not for subsequent environments' do allow(Kernel).to receive(:system).and_return(false) expect(git_wrapper).to receive(:create_or_update_branch).with('demo', release.commit) - expect(git_wrapper).to_not receive(:create_or_update_branch).with('production', any_args) + expect(git_wrapper).not_to receive(:create_or_update_branch).with('production', any_args) expect do subject.deploy! ['demo', 'production'] diff --git a/spec/lib/epi_deploy/git_wrapper_spec.rb b/spec/lib/epi_deploy/git_wrapper_spec.rb index 9c42049..586cb0a 100644 --- a/spec/lib/epi_deploy/git_wrapper_spec.rb +++ b/spec/lib/epi_deploy/git_wrapper_spec.rb @@ -3,11 +3,11 @@ require 'epi_deploy/git_wrapper' describe EpiDeploy::GitWrapper do - let(:mocked_git) { double(:git, current_branch: current_branch, add_tag: true, push: true) } let(:commit) { 'caa2c06f96cb0e52cdc6059014bc69bd94573d7a592b8c380bca5348e1f6806e0e9ad9bd12d7a78b' } let(:current_branch) { 'main' } - before(:each) do + + before do allow(subject).to receive(:git).and_return(mocked_git) end @@ -38,7 +38,7 @@ expect(subject).to be_on_primary_branch end end - + context 'When the current branch is master' do let(:current_branch) { 'master' } @@ -51,10 +51,9 @@ let(:current_branch) { 'feature/cool-feature-branch' } specify 'Then it returns false' do - expect(subject).to_not be_on_primary_branch + expect(subject).not_to be_on_primary_branch end end - end describe '#current_branch' do @@ -102,7 +101,7 @@ context 'if not all the branches exist as local branches' do let(:production_branch) { double('production branch') } let(:demo_branch) { double('demo branch') } - let(:local_branches) { [production_branch ] } + let(:local_branches) { [production_branch] } specify 'it deletes each branch from the remote' do allow(production_branch).to receive(:delete) @@ -115,7 +114,7 @@ specify 'it deletes only the branches that exist locally' do allow(subject).to receive(:run_custom_command) expect(production_branch).to receive(:delete) - expect(demo_branch).to_not receive(:delete) + expect(demo_branch).not_to receive(:delete) subject.delete_branches(branches) end diff --git a/spec/lib/epi_deploy/release_spec.rb b/spec/lib/epi_deploy/release_spec.rb index 49488ee..48199e6 100644 --- a/spec/lib/epi_deploy/release_spec.rb +++ b/spec/lib/epi_deploy/release_spec.rb @@ -5,6 +5,7 @@ require 'epi_deploy/release' class MockGit + def initialize(on_primary_branch: true, pending_changes: false) @on_primary_branch = on_primary_branch @pending_changes = pending_changes @@ -20,12 +21,14 @@ def pull; end def current_branch; 'main'; end def create_or_update_tag(stage, commit); end def delete_branches(branches); end + def most_recent_commit; end + end describe EpiDeploy::Release do - let(:git_wrapper) { MockGit.new } let(:app_version) { double(bump: 42, version_file_path: '', :latest_release_tag= => nil, save!: true) } + before do allow(subject).to receive_messages(reference: 'test', git_wrapper: git_wrapper, commit: 'caa2c06f96cb0e52cdc6059014bc69bd94573d7a592b8c380bca5348e1f6806e0e9ad9bd12d7a78b', app_version: app_version) allow(git_wrapper).to receive(:most_recent_commit).and_return(double('commit', message: 'Some non-release commit')) @@ -49,30 +52,27 @@ def delete_branches(branches); end end it "performs a git pull to ensure code is the latest" do - allow(subject).to receive_messages(bump_version: nil) expect(git_wrapper).to receive(:pull) - expect(subject.create!).to eq true + expect(subject.create!).to be true end it "stops with a warning message when a git pull fails (eg. merge errors)" do - allow(subject).to receive_messages(bump_version: nil) expect(git_wrapper).to receive(:pull) - expect(subject.create!).to eq true + expect(subject.create!).to be true end it "bumps the version number" do expect(app_version).to receive(:bump) - expect(subject.create!).to eq true + expect(subject.create!).to be true end it "commits the new version number" do - allow(subject).to receive_messages bump_version: 42 expect(git_wrapper).to receive(:commit).with('Bumped to version 42 [skip ci]') - expect(subject.create!).to eq true + expect(subject.create!).to be true end context 'given that the date and time is 2024-12-1 16:15:00' do @@ -84,29 +84,27 @@ def delete_branches(branches); end it 'sets the latest release tag in the version the newly-created tag' do expect(app_version).to receive(:latest_release_tag=).with('2014dec01-1615-abc1234-v42') - expect(subject.create!).to eq true + expect(subject.create!).to be true end it "creates a tag in the format YYYYmonDD-HHMM-CommitRef-version for the new commit" do - allow(subject).to receive_messages bump_version: 42 expect(git_wrapper).to receive(:create_or_update_tag).with('2014dec01-1615-abc1234-v42', push: false) - expect(subject.create!).to eq true + expect(subject.create!).to be true end end it "pushes the new version to primary branch to reduce the chance of version number collisions" do - allow(subject).to receive_messages bump_version: 42 expect(git_wrapper).to receive(:push).with('main', tags: true) - expect(subject.create!).to eq true + expect(subject.create!).to be true end it 'does not create a new release if the most recent commit is a release commit' do allow(git_wrapper).to receive(:most_recent_commit).and_return(double('commit', message: 'Bumped to version 12 [skip ci]')) - expect(git_wrapper).to_not receive(:create_or_update_tag) + expect(git_wrapper).not_to receive(:create_or_update_tag) - expect(subject.create!).to eq false + expect(subject.create!).to be false end end end diff --git a/spec/lib/epi_deploy/stages_extractor_spec.rb b/spec/lib/epi_deploy/stages_extractor_spec.rb index c3a59cd..07ec8af 100644 --- a/spec/lib/epi_deploy/stages_extractor_spec.rb +++ b/spec/lib/epi_deploy/stages_extractor_spec.rb @@ -1,38 +1,38 @@ require 'spec_helper' -require 'epi_deploy/stages_extractor' +require 'support/aruba_helper' +require 'epi_deploy/stages_extractor' -describe EpiDeploy::StagesExtractor do - - subject do - Dir.chdir(File.join(File.dirname(__FILE__), '../..', 'fixtures')) do - described_class.new +describe EpiDeploy::StagesExtractor, type: :aruba do + around do |example| + in_current_directory do + example.call end end - + describe '#multi_customer_stage?' do it 'returns true if the stage is for multiple customers' do expect(subject.multi_customer_stage?('production')).to be true end - + it 'returns false if the stage is not for multiple customers' do expect(subject.multi_customer_stage?('demo')).to be false end end - + describe '#valid_stage?' do specify "a multi-customer stage is valid" do expect(subject.valid_stage?('production')).to be true end - + specify "a single-customer stage is valid" do expect(subject.valid_stage?('demo')).to be true end - + specify "a specific customer stage is valid" do expect(subject.valid_stage?('production.epigenesys')).to be true end - + specify "a stage without a config file is not valid" do expect(subject.valid_stage?('qa')).to be false end @@ -40,33 +40,33 @@ describe '#environments' do specify 'it returns a list of environments' do - expect(subject.environments).to match_array ['production', 'demo'] + expect(subject.environments).to contain_exactly('production', 'demo') end end describe '#all_stages' do specify 'it returns of all deployment stages' do - expect(subject.all_stages).to match_array ['production.epigenesys', 'production.genesys', 'demo'] + expect(subject.all_stages).to contain_exactly('production.epigenesys', 'production.genesys', 'demo') end end describe '#multi_customer_stages' do specify 'it returns only stages that have multiple customers for the same environment' do - expect(subject.multi_customer_stages).to match_array ['production'] + expect(subject.multi_customer_stages).to contain_exactly('production') end end describe '#stages_for_stage_or_environment' do specify 'it returns the deployable stages for a multi-customer environment' do - expect(subject.stages_for_stage_or_environment('production')).to match_array ['production.epigenesys', 'production.genesys'] + expect(subject.stages_for_stage_or_environment('production')).to contain_exactly('production.epigenesys', 'production.genesys') end specify 'it returns the deployment stage itself for a single customer environment' do - expect(subject.stages_for_stage_or_environment('demo')).to match_array ['demo'] + expect(subject.stages_for_stage_or_environment('demo')).to contain_exactly('demo') end specify 'it returns the deployment stage for a fully qualified stage in a multi-customer environment' do - expect(subject.stages_for_stage_or_environment('production.epigenesys')).to match_array ['production.epigenesys'] + expect(subject.stages_for_stage_or_environment('production.epigenesys')).to contain_exactly('production.epigenesys') end end -end \ No newline at end of file +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f83e281..96c1e8d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,7 +1,86 @@ -$: << File.expand_path('../../lib', __FILE__) +require "byebug" -require 'byebug' +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end -def run_ed(commands) - run_command_and_stop "#{File.join(File.dirname(__FILE__), '../bin/epi_deploy')} #{commands}", fail_on_error: false + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "tmp/examples.txt" + + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end + + config.backtrace_exclusion_patterns << /gems/ end diff --git a/spec/support/aruba_helper.rb b/spec/support/aruba_helper.rb index 459c08a..eedecf2 100644 --- a/spec/support/aruba_helper.rb +++ b/spec/support/aruba_helper.rb @@ -1,34 +1,35 @@ require 'git' -require 'aruba/api' - -RSpec.configure do |config| - config.include Aruba::Api -end - -def local_repo - File.expand_path("../../../#{Aruba.config.working_directory}", __FILE__) -end +require 'aruba/rspec' def setup_aruba_and_git - @dirs = [local_repo] - - setup_aruba - `rm -rf #{local_repo}` - `mkdir -p #{local_repo}` - `cp #{File.expand_path('../../../Gemfile*', __FILE__)} #{local_repo}` - - `mkdir -p #{local_repo}/config/initializers` - `mkdir -p #{local_repo}/config/deploy` - - `echo > #{local_repo}/config/initializers/.gitkeep` - `cp -r #{File.expand_path('../../../config/deploy/*.rb', __FILE__)} #{local_repo}/config/deploy/` + local_repo = Aruba.config.home_directory + copy "%/test_app/config", "%/test_app/Gemfile", "%/test_app/Capfile", "." g = Git.init(local_repo) + g.config("push.autoSetupRemote", "true") + g.config("user.name", "epiDeploy Test Account") + g.config("user.email", "epi_deploy@example.test") g.add g.commit('initial commit') # Set the remote repo to the local, works the same as an actual remote g.add_remote('origin', local_repo) + g.push("origin", "HEAD") +end + +def run_ed(commands) + run_command_and_stop "bundle exec epi_deploy #{commands}", fail_on_error: false +end + +RSpec.configure do |config| + config.include Aruba::Api + + config.around(type: :aruba) do |example| + setup_aruba_and_git + if example.metadata[:bundle] + run_command_and_stop 'bundle install --quiet' + end - `cd #{local_repo} && git push --quiet -u origin $(git symbolic-ref --short HEAD) &> /dev/null` + example.run + end end