# frozen_string_literal: true require "rake" require "mina" require "pp" require "open3" module Groundskeeper # Formulas for managing releases and deployments. # rubocop:disable Metrics/ClassLength class Commands RAKEFILE = File.join( File.dirname(__FILE__), "..", "..", "config", "deploy.rb" ) STAGE = "stage" STAGING = "staging" PRODUCTION = "production" DEFAULT_STAGE = STAGING TAG = "tag" # rubocop:disable Metrics/MethodLength def self.build(console) repository = Repository.new project = Project.build(repository.name) git_hub = GitHub.build( username: project.source_control_username, repository_name: repository.name ) new( changelog: Changelog.build, console: console, git: Git.build, git_hub: git_hub, jira: Jira.build(project.jira_prefix), project: project, repository: repository, rubygems: Rubygems, version_file: RailsVersion.new ) end # rubocop:enable Metrics/MethodLength # rubocop:disable Metrics/MethodLength,Metrics/ParameterLists def initialize( changelog: nil, console:, git: nil, git_hub: nil, jira: nil, project: nil, repository: nil, rubygems: nil, version_file: ) @changelog = changelog @console = console @git = git @git_hub = git_hub @jira = jira @project = project @repository = repository @rubygems = rubygems @version_file = version_file @did_checkout_branch = false @did_push_to_remote = false end # rubocop:enable Metrics/MethodLength,Metrics/ParameterLists # rubocop:disable Metrics/MethodLength def info return unrecognized_version unless version_file.exists? return unless check_groundskeeper_version announce_latest_tag console.say( "version in current branch: " + version_file.current_version, :yellow ) console.say( "tag contained in: #{git.branches_containing_latest_tag}\n\n", :yellow ) end # rubocop:disable Metrics/AbcSize def release return unrecognized_version unless version_file.exists? return missing_jira_credentials unless jira.credentials? return unless check_groundskeeper_version summarize_recent_commits ask_next_version ask_new_branch update_version_file update_changelog commit_changes_and_tag ask_create_jira_version ask_push_with_tags ask_add_version_to_jira_issues open_pull_request_page end # rubocop:enable Metrics/AbcSize # rubocop:enable Metrics/MethodLength def predeploy(options) return unrecognized_version unless version_file.exists? return unless check_groundskeeper_version mina "predeploy", options end def deploy(options) return unrecognized_version unless version_file.exists? return missing_jira_credentials unless jira.credentials? return unless check_groundskeeper_version ENV["whenever"] = "1" if project.uses_whenever? mina "deploy", options console.say("waiting for deployed application to restart...", :yellow) update_deployed_issues end private # collaborators attr_reader :changelog, :console, :git, :git_hub, :jira, :project, :repository, :rubygems, :version_file # state attr_reader :next_version, :recent_commits, :did_checkout_branch, :did_push_to_remote def mina(command, options) cmd = String.new command cmd << " -s" if options[:simulate] cmd << " -v" if options[:verbose] run_mina cmd end def check_groundskeeper_version console.say("Groundskeeper version #{Groundskeeper::VERSION}\n\n", :bold) latest_version = rubygems.latest_groundskeeper_version if SemanticVersion.new(latest_version) > SemanticVersion.new(VERSION) console.say( "Groundskeeper is outdated, please install #{latest_version}", :red ) return false end true end def announce_latest_tag console.say( "latest tag on this repository: " + git.latest_tag_name_across_branches, :yellow ) end def summarize_recent_commits console.say("commits since last tag", :bold) @recent_commits = repository.changes_since_latest_tag console.say(recent_commits.join("\n"), :green) console.say("") end def ask_next_version type = console.ask( "Major, minor, or patch release?", :cyan, limited_to: %w[M m p] ) @next_version = repository.bumped_semantic_version(type) console.say("next tag will be: #{next_version}", :green) end def ask_new_branch @did_checkout_branch = console.yes?( "Checkout new branch #{new_branch_name}? [y, n]", :cyan ) git.create_and_checkout_branch(new_branch_name) if did_checkout_branch end def update_version_file version_file.update_version! next_version console.say("# updated version file", :green) end def update_changelog changelog.update_file(next_version, recent_commits) console.say("# updated changelog", :green) end def commit_changes_and_tag git.add_update git.commit(format(Repository::RELEASE_MESSAGE, next_version)) console.say("# committed changes", :green) git.add_tag(next_version) console.say("# tagged", :green) end def ask_create_jira_version create_jira_version = console.yes?("Create version #{next_version} in Jira? [y, n]", :cyan) jira.create_remote_version(next_jira_version_name) if create_jira_version end def ask_push_with_tags @did_push_to_remote = console.yes?( "Push to remote with tags? [y, n]", :cyan ) git.push_with_tags && console.say("# pushed tag") if @did_push_to_remote end def ask_add_version_to_jira_issues issue_ids = jira.included_issues(recent_commits) add_version_to_jira_issues = console.yes?( "Add #{next_jira_version_name} to #{issue_ids}? [y, n]", :cyan ) return unless add_version_to_jira_issues jira.add_version_to_remote_issues(next_jira_version_name, issue_ids) console.say("# added version to Jira issues", :green) end def next_jira_version_name "#{repository.name} #{next_version}" end def unrecognized_version console.say( "Unable to find version file, is this a Rails application?", :red ) end def missing_jira_credentials console.say( "Please configure your Jira environment variables.", :red ) end def open_pull_request_page return unless did_checkout_branch && did_push_to_remote git_hub.open_pull_request_page(new_branch_name) end def new_branch_name "v#{next_version}" end def run_mina(arguments) command = "mina #{arguments} -f #{RAKEFILE}" Open3.popen3(command) do |_stdout, stderr, _status, _thread| # rubocop:disable Lint/AssignmentInCondition while line = stderr.gets puts line end # rubocop:enable Lint/AssignmentInCondition end end def update_deployed_issues deployed_url = "https://#{project.full_dns(stage)}" deployed_version = Website.new(deployed_url).version if deployed_version == ENV[TAG] console.say("# deployment successful", :green) transition_remote_issues deployed_version else console.say("something went wrong", :red) end end def stage ENV[STAGE] == PRODUCTION ? PRODUCTION : DEFAULT_STAGE end def transition_remote_issues(version) version_name = "#{repository.name} #{version}" deployed_issues = jira.fetch_issues_by_fix_version(version_name) action = stage == PRODUCTION ? Jira::DELIVER : Jira::DEPLOY_TO_STAGING console.say( "transitioning deployed issues in Jira: " \ "#{action} #{deployed_issues.join(', ')}", :yellow ) jira.transition_remote_issues(action, deployed_issues) end end # rubocop:enable Metrics/ClassLength end