afterlife 1.7.4 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +23 -2
- data/.ruby-version +1 -1
- data/Gemfile +3 -0
- data/afterlife.gemspec +2 -1
- data/config.ru +2 -3
- data/lib/afterlife/bump/semver.rb +2 -2
- data/lib/afterlife/cdn/cli.rb +1 -0
- data/lib/afterlife/cli.rb +4 -0
- data/lib/afterlife/clickup/cli.rb +15 -2
- data/lib/afterlife/clickup/commit_input_splitter.rb +64 -0
- data/lib/afterlife/clickup/get_ids_from_text.rb +1 -1
- data/lib/afterlife/clickup/next_task_status_decider.rb +46 -0
- data/lib/afterlife/clickup/status_precedence_checker.rb +2 -0
- data/lib/afterlife/clickup/task_revert_detector.rb +30 -0
- data/lib/afterlife/clickup/update_task_status.rb +71 -20
- data/lib/afterlife/config/cli.rb +1 -0
- data/lib/afterlife/config/provider.rb +2 -1
- data/lib/afterlife/deploy/cli.rb +1 -0
- data/lib/afterlife/deploy/docker_deployment.rb +103 -0
- data/lib/afterlife/deploy/kubernetes_deployment.rb +4 -72
- data/lib/afterlife/deploy.rb +1 -0
- data/lib/afterlife/notify.rb +2 -2
- data/lib/afterlife/propagate/cli.rb +45 -0
- data/lib/afterlife/propagate/propagate_qa_to_sandbox.rb +148 -0
- data/lib/afterlife/propagate.rb +6 -0
- data/lib/afterlife/release/change_version.rb +1 -1
- data/lib/afterlife/release/cli.rb +1 -1
- data/lib/afterlife/release/create_helper.rb +3 -2
- data/lib/afterlife/release/pre_helper.rb +5 -4
- data/lib/afterlife/repo/config.rb +5 -5
- data/lib/afterlife/version.rb +1 -1
- metadata +24 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ded787195d0197794e9ce63aab421fd0b9f4168208bab96b1cdab4e34f22d955
|
|
4
|
+
data.tar.gz: f9305bba34772dbe134eedad518d67ad371e85a1e8b6f28b25785f54f8737e58
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: decbc189d4fc9e422ab524fdd80adc4234d919542350db532bc04a69a48cdef40220fde5331fb94fa77c1427a59df5d4328f7d454e50d8a8b20d336c239e976b
|
|
7
|
+
data.tar.gz: 394711b68721f95e338b558c51d86581537b6a0ddf7f4dcd31646420cf0a9cebcdfbff60ef2395f4b13acaae1e44856aac5b57b0d690402f46c51b8053a94d81
|
data/.rubocop.yml
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
plugins:
|
|
2
2
|
- rubocop-rspec
|
|
3
3
|
- rubocop-rake
|
|
4
4
|
|
|
5
5
|
AllCops:
|
|
6
6
|
NewCops: enable
|
|
7
|
-
TargetRubyVersion:
|
|
7
|
+
TargetRubyVersion: 3.3
|
|
8
8
|
|
|
9
9
|
Lint/ConstantDefinitionInBlock:
|
|
10
10
|
Enabled: false
|
|
@@ -44,3 +44,24 @@ Layout/AccessModifierIndentation:
|
|
|
44
44
|
|
|
45
45
|
Style/SignalException:
|
|
46
46
|
EnforcedStyle: semantic
|
|
47
|
+
|
|
48
|
+
Metrics/ClassLength:
|
|
49
|
+
Enabled: false
|
|
50
|
+
|
|
51
|
+
Metrics/CyclomaticComplexity:
|
|
52
|
+
Enabled: false
|
|
53
|
+
|
|
54
|
+
Metrics/MethodLength:
|
|
55
|
+
Enabled: false
|
|
56
|
+
|
|
57
|
+
Metrics/ParameterLists:
|
|
58
|
+
Enabled: false
|
|
59
|
+
|
|
60
|
+
Metrics/PerceivedComplexity:
|
|
61
|
+
Enabled: false
|
|
62
|
+
|
|
63
|
+
RSpec/MultipleExpectations:
|
|
64
|
+
Enabled: false
|
|
65
|
+
|
|
66
|
+
RSpec/MultipleMemoizedHelpers:
|
|
67
|
+
Enabled: false
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.3.5
|
data/Gemfile
CHANGED
data/afterlife.gemspec
CHANGED
|
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
|
10
10
|
|
|
11
11
|
spec.summary = 'Devops utils'
|
|
12
12
|
spec.description = 'Afterlife helps you setup your development environment and deploy code easily'
|
|
13
|
-
spec.required_ruby_version = '>=
|
|
13
|
+
spec.required_ruby_version = '>= 3.3'
|
|
14
14
|
|
|
15
15
|
spec.metadata['source_code_uri'] = 'https://bitbucket.org/volabit/afterlife'
|
|
16
16
|
spec.metadata['changelog_uri'] = 'https://bitbucket.org/volabit/afterlife/src/master/CHANGELOG.md'
|
|
@@ -29,6 +29,7 @@ Gem::Specification.new do |spec|
|
|
|
29
29
|
spec.require_paths = ['lib']
|
|
30
30
|
|
|
31
31
|
spec.add_dependency 'faraday'
|
|
32
|
+
spec.add_dependency 'ostruct'
|
|
32
33
|
spec.add_dependency 'racc' # rubocop dependency, cannot run `bundle exec rubocop` without it
|
|
33
34
|
spec.add_dependency 'thor'
|
|
34
35
|
spec.add_dependency 'zeitwerk'
|
data/config.ru
CHANGED
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
require 'logger'
|
|
4
4
|
require 'rack/directory'
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
logger_file.sync = true
|
|
6
|
+
logger = Logger.new(Afterlife::Cdn.log_path)
|
|
8
7
|
|
|
9
8
|
class AssetHeaders
|
|
10
9
|
def initialize(app)
|
|
@@ -19,7 +18,7 @@ class AssetHeaders
|
|
|
19
18
|
end
|
|
20
19
|
|
|
21
20
|
use AssetHeaders
|
|
22
|
-
use Rack::CommonLogger,
|
|
21
|
+
use Rack::CommonLogger, logger
|
|
23
22
|
run do |env|
|
|
24
23
|
Rack::Directory.new(Afterlife::Cdn.local_path).call(env)
|
|
25
24
|
end
|
|
@@ -6,9 +6,9 @@ module Afterlife
|
|
|
6
6
|
module_function
|
|
7
7
|
|
|
8
8
|
DEFAULT_PRERELEASE_NAME = 'rc'
|
|
9
|
-
VERSION_REGEX = /(\d+\.\d+\.\d+(?:-(?:.+))?)
|
|
9
|
+
VERSION_REGEX = /(\d+\.\d+\.\d+(?:-(?:.+))?)/
|
|
10
10
|
|
|
11
|
-
def calculate_next(part, pre = nil)
|
|
11
|
+
def calculate_next(part, pre = nil)
|
|
12
12
|
x, y, z = release
|
|
13
13
|
case part.to_sym
|
|
14
14
|
when :major
|
data/lib/afterlife/cdn/cli.rb
CHANGED
data/lib/afterlife/cli.rb
CHANGED
|
@@ -5,6 +5,7 @@ require 'thor'
|
|
|
5
5
|
module Afterlife
|
|
6
6
|
class Cli < Thor
|
|
7
7
|
include BaseCli
|
|
8
|
+
|
|
8
9
|
def self.exit_on_failure?
|
|
9
10
|
true
|
|
10
11
|
end
|
|
@@ -50,6 +51,9 @@ module Afterlife
|
|
|
50
51
|
desc 'clickup', 'ClickUp integration commands'
|
|
51
52
|
subcommand 'clickup', Clickup::Cli
|
|
52
53
|
|
|
54
|
+
desc 'propagate', 'Propagate commands'
|
|
55
|
+
subcommand 'propagate', Propagate::Cli
|
|
56
|
+
|
|
53
57
|
map %w[-v --version] => :version
|
|
54
58
|
desc 'version', 'Prints afterlife version'
|
|
55
59
|
def version
|
|
@@ -37,10 +37,23 @@ module Afterlife
|
|
|
37
37
|
desc: 'Force update status of tasks even if they would not be updated due to status precedence'
|
|
38
38
|
option :dry_run, type: :boolean, default: false, aliases: '-d',
|
|
39
39
|
desc: 'Do not actually update the status of the tasks, just print what would be done'
|
|
40
|
-
|
|
40
|
+
option :cherry_pick, type: :boolean, default: false, aliases: '-c',
|
|
41
|
+
desc: 'Include cherry pick status in the output'
|
|
42
|
+
option :verbose, type: :boolean, default: true, aliases: '-v',
|
|
43
|
+
desc: 'Print skipped tasks'
|
|
44
|
+
option :commits, type: :string,
|
|
45
|
+
desc: 'Commit messages used to detect reverted tasks'
|
|
46
|
+
def update_status(target_status, *task_ids) # rubocop:disable Metrics/AbcSize
|
|
41
47
|
task_ids = $stdin.read.strip.split("\n") if task_ids.empty?
|
|
42
48
|
|
|
43
|
-
UpdateTaskStatus.call(
|
|
49
|
+
UpdateTaskStatus.call(
|
|
50
|
+
task_ids, target_status,
|
|
51
|
+
force: options[:force],
|
|
52
|
+
dry_run: options[:dry_run],
|
|
53
|
+
cherry_pick: options[:cherry_pick],
|
|
54
|
+
verbose: options[:verbose],
|
|
55
|
+
commits: options[:commits],
|
|
56
|
+
)
|
|
44
57
|
rescue StandardError => e
|
|
45
58
|
fatal!(e.message)
|
|
46
59
|
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Afterlife
|
|
4
|
+
module Clickup
|
|
5
|
+
class CommitInputSplitter
|
|
6
|
+
SUBJECT_PATTERN = /\A(?:revert\b|\w+(?:\(.+\))?!?:\s|.+#(?:CU[-_])?[a-z0-9]{8,})/i
|
|
7
|
+
|
|
8
|
+
def self.call(input)
|
|
9
|
+
new(input).call
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def initialize(input)
|
|
13
|
+
@input = input.to_s.strip
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call
|
|
17
|
+
return [] if input.empty?
|
|
18
|
+
|
|
19
|
+
return split_github_input if input.include?('\\n')
|
|
20
|
+
|
|
21
|
+
split_real_newline_input
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
attr_reader :input
|
|
27
|
+
|
|
28
|
+
def split_github_input
|
|
29
|
+
input.lines(chomp: true).filter_map do |line|
|
|
30
|
+
line = line.strip
|
|
31
|
+
line.gsub('\\n', "\n") unless line.empty?
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def split_real_newline_input
|
|
36
|
+
commits = []
|
|
37
|
+
current = []
|
|
38
|
+
|
|
39
|
+
input.lines(chomp: true).each do |line|
|
|
40
|
+
if subject?(line) && current.any?
|
|
41
|
+
commits << normalize(current)
|
|
42
|
+
current = []
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
current << line
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
commits << normalize(current) if current.any?
|
|
49
|
+
commits.reject(&:empty?)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def subject?(line)
|
|
53
|
+
return false if line.empty? || line.start_with?('*', '-', ' ')
|
|
54
|
+
return false if line.match?(/\AThis reverts commit\b/i)
|
|
55
|
+
|
|
56
|
+
line.match?(SUBJECT_PATTERN)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def normalize(lines)
|
|
60
|
+
lines.join("\n").strip
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Afterlife
|
|
4
|
+
module Clickup
|
|
5
|
+
class NextTaskStatusDecider
|
|
6
|
+
REVERT_SOURCE_STATUS = 'ready for qa'
|
|
7
|
+
REVERT_TARGET_STATUS = 'needs rework'
|
|
8
|
+
|
|
9
|
+
def self.call(**args)
|
|
10
|
+
new(**args).call
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize(clickup_task:, target_status:, force: false, reverted: false)
|
|
14
|
+
@clickup_task = clickup_task
|
|
15
|
+
@target_status = target_status
|
|
16
|
+
@force = force
|
|
17
|
+
@reverted = reverted
|
|
18
|
+
@precedence_checker = StatusPrecedenceChecker.new
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def call
|
|
22
|
+
return if current_status.nil?
|
|
23
|
+
|
|
24
|
+
return reverted_status if reverted
|
|
25
|
+
|
|
26
|
+
skip_update? ? nil : target_status
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
attr_reader :clickup_task, :target_status, :force, :reverted, :precedence_checker
|
|
32
|
+
|
|
33
|
+
def current_status
|
|
34
|
+
@current_status ||= clickup_task&.dig('status', 'status')&.downcase
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def reverted_status
|
|
38
|
+
REVERT_TARGET_STATUS if current_status == REVERT_SOURCE_STATUS
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def skip_update?
|
|
42
|
+
!force && precedence_checker.should_skip_update?(current_status, target_status)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Afterlife
|
|
4
|
+
module Clickup
|
|
5
|
+
class TaskRevertDetector
|
|
6
|
+
def initialize(commits)
|
|
7
|
+
@commits = CommitInputSplitter.call(commits)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def reverted?(task_id)
|
|
11
|
+
reverted = false
|
|
12
|
+
|
|
13
|
+
@commits.each do |commit|
|
|
14
|
+
next unless commit.match?(/#?(?:CU[-_])?#{Regexp.escape(task_id)}/i)
|
|
15
|
+
|
|
16
|
+
reverted = revert_count(commit, task_id).odd?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
reverted
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def revert_count(line, task_id)
|
|
25
|
+
before_task_id = line.split(/#?(?:CU[-_])?#{Regexp.escape(task_id)}/i, 2).first
|
|
26
|
+
before_task_id.scan(/\brevert\b/i).count
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -3,50 +3,101 @@
|
|
|
3
3
|
module Afterlife
|
|
4
4
|
module Clickup
|
|
5
5
|
class UpdateTaskStatus
|
|
6
|
-
attr_reader :
|
|
6
|
+
attr_reader :api, :force, :dry_run, :cherry_pick, :verbose, :revert_detector, :slack_output_path
|
|
7
7
|
|
|
8
|
-
def self.call(
|
|
9
|
-
|
|
8
|
+
def self.call(
|
|
9
|
+
task_ids, target_status, force: false, dry_run: false, cherry_pick: false, verbose: true, commits: nil
|
|
10
|
+
)
|
|
11
|
+
new(force:, dry_run:, cherry_pick:, verbose:, commits:).call(task_ids, target_status)
|
|
10
12
|
end
|
|
11
13
|
|
|
12
|
-
def initialize(force: false, dry_run: false)
|
|
14
|
+
def initialize(force: false, dry_run: false, cherry_pick: false, verbose: true, commits: nil)
|
|
13
15
|
@force = force
|
|
14
16
|
@dry_run = dry_run
|
|
17
|
+
@cherry_pick = cherry_pick
|
|
18
|
+
@verbose = verbose
|
|
19
|
+
@slack_output_path = ENV.fetch('AFTERLIFE_SLACK_OUTPUT', nil)
|
|
20
|
+
write_slack_output('') if slack_output_path
|
|
21
|
+
@revert_detector = TaskRevertDetector.new(commits)
|
|
15
22
|
token = ENV['CLICKUP_TOKEN'] || fail('CLICKUP_TOKEN required, put `export CLICKUP_TOKEN=...` or the equivalent in your shell') # rubocop:disable Layout/LineLength
|
|
16
|
-
@api = Api.new(token:
|
|
17
|
-
@precedence_checker = StatusPrecedenceChecker.new
|
|
23
|
+
@api = Api.new(token:)
|
|
18
24
|
end
|
|
19
25
|
|
|
20
|
-
def call(task_ids, target_status) # rubocop:disable Metrics/AbcSize
|
|
26
|
+
def call(task_ids, target_status) # rubocop:disable Metrics/AbcSize
|
|
21
27
|
if task_ids.empty?
|
|
22
|
-
|
|
28
|
+
log 'No tasks to update'
|
|
23
29
|
return []
|
|
24
30
|
end
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
log 'WARNING: Forcing status updates...' if force
|
|
33
|
+
log 'WARNING: Dry run, no tasks will be updated' if dry_run
|
|
34
|
+
log "#{dry_run ? 'Would update' : 'Updating'} #{task_ids.length} tasks to '#{target_status}'"
|
|
29
35
|
moved_task_ids = task_ids.map { |task_id| move_task(task_id, target_status) }.compact
|
|
30
|
-
|
|
36
|
+
log "#{dry_run ? 'Would have moved' : 'Moved'} #{moved_task_ids.length} tasks:"
|
|
31
37
|
moved_task_ids.each do |task|
|
|
32
|
-
|
|
38
|
+
cherry_pick_status = " - Cherry Pick: #{task[:is_cherry_pick] ? '✅' : '❌'}" if cherry_pick
|
|
39
|
+
log "- #{task[:link]} (from '#{task[:previous_status]}' to '#{task[:new_status]}')#{cherry_pick_status}"
|
|
33
40
|
end
|
|
34
41
|
moved_task_ids
|
|
35
42
|
end
|
|
36
43
|
|
|
37
44
|
private
|
|
38
45
|
|
|
39
|
-
def move_task(task_id, target_status)
|
|
40
|
-
|
|
46
|
+
def move_task(task_id, target_status) # rubocop:disable Metrics/AbcSize
|
|
47
|
+
task = api.get_task(task_id)
|
|
48
|
+
current_status = task&.dig('status', 'status')&.downcase
|
|
49
|
+
if current_status.nil?
|
|
50
|
+
log "WARNING: Task #{task_id} not found", verbose: true
|
|
51
|
+
return
|
|
52
|
+
end
|
|
41
53
|
|
|
42
|
-
|
|
54
|
+
is_cherry_pick = Array(task['custom_fields']).any? do |field|
|
|
55
|
+
field['name'].match?(/cherry pick/i) && [true, 'true'].include?(field['value'])
|
|
56
|
+
end
|
|
43
57
|
|
|
44
|
-
should_skip_update = !force && precedence_checker.should_skip_update?(current_status, target_status)
|
|
45
58
|
task_link = "https://app.clickup.com/t/#{task_id}"
|
|
46
|
-
|
|
59
|
+
next_status = next_status_for(task_id, task, target_status)
|
|
60
|
+
return if next_status.nil?
|
|
61
|
+
|
|
62
|
+
api.update_task(task_id, { status: next_status }) unless dry_run
|
|
63
|
+
|
|
64
|
+
{
|
|
65
|
+
id: task_id,
|
|
66
|
+
link: task_link,
|
|
67
|
+
previous_status: current_status,
|
|
68
|
+
new_status: next_status,
|
|
69
|
+
is_cherry_pick:,
|
|
70
|
+
}
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def next_status_for(task_id, task, target_status)
|
|
74
|
+
current_status = task.dig('status', 'status')&.downcase
|
|
75
|
+
reverted = revert_detector.reverted?(task_id)
|
|
76
|
+
next_status = NextTaskStatusDecider.call(clickup_task: task, target_status:, force:, reverted:)
|
|
77
|
+
|
|
78
|
+
if reverted && next_status
|
|
79
|
+
log "Moving task https://app.clickup.com/t/#{task_id} to '#{next_status}': reverted in pushed commits"
|
|
80
|
+
elsif !reverted && next_status.nil?
|
|
81
|
+
log(
|
|
82
|
+
"Skipping task https://app.clickup.com/t/#{task_id} because it's already at '#{current_status}'",
|
|
83
|
+
verbose: true,
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
next_status
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def log(message, verbose: false)
|
|
91
|
+
return if verbose && !@verbose
|
|
92
|
+
|
|
93
|
+
puts message
|
|
94
|
+
write_slack_output("#{message}\n", mode: 'a') if !verbose && slack_output_path
|
|
95
|
+
end
|
|
47
96
|
|
|
48
|
-
|
|
49
|
-
|
|
97
|
+
def write_slack_output(message, mode: 'w')
|
|
98
|
+
File.write(slack_output_path, message, mode:)
|
|
99
|
+
rescue SystemCallError
|
|
100
|
+
nil
|
|
50
101
|
end
|
|
51
102
|
end
|
|
52
103
|
end
|
data/lib/afterlife/config/cli.rb
CHANGED
|
@@ -11,6 +11,7 @@ module Afterlife
|
|
|
11
11
|
CLOUD_PROFILES = {
|
|
12
12
|
staging: PROFILE_TESTING,
|
|
13
13
|
qa: PROFILE_TESTING,
|
|
14
|
+
dev: PROFILE_TESTING,
|
|
14
15
|
sandbox: PROFILE_PRODUCTION,
|
|
15
16
|
production: PROFILE_PRODUCTION,
|
|
16
17
|
}.freeze
|
|
@@ -19,7 +20,7 @@ module Afterlife
|
|
|
19
20
|
config.key?(sym) || super
|
|
20
21
|
end
|
|
21
22
|
|
|
22
|
-
def method_missing(sym, *args, &
|
|
23
|
+
def method_missing(sym, *args, &)
|
|
23
24
|
config.key?(sym) ? config[sym] : super
|
|
24
25
|
end
|
|
25
26
|
|
data/lib/afterlife/deploy/cli.rb
CHANGED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Afterlife
|
|
4
|
+
module Deploy
|
|
5
|
+
class DockerDeployment < Deployment
|
|
6
|
+
def setup
|
|
7
|
+
Afterlife.current_repo.env.set('PLATFORM' => 'linux/amd64') if deploy_stage?
|
|
8
|
+
|
|
9
|
+
Afterlife.current_repo.env.set!(
|
|
10
|
+
'REGISTRY' => registry,
|
|
11
|
+
'TAG' => tag,
|
|
12
|
+
)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def run
|
|
16
|
+
Exec.run(commands)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def confirmation_message
|
|
20
|
+
'You are about to deploy the current directory'
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def commands
|
|
26
|
+
[
|
|
27
|
+
authenticate_command,
|
|
28
|
+
build_command,
|
|
29
|
+
].flatten.compact
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# commands
|
|
33
|
+
|
|
34
|
+
def authenticate_command
|
|
35
|
+
return if options['no-auth'] || local_stage?
|
|
36
|
+
|
|
37
|
+
AwsAuth.new(registry).commands
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def build_command
|
|
41
|
+
return if options['no-build']
|
|
42
|
+
|
|
43
|
+
<<-BASH
|
|
44
|
+
docker buildx bake --allow=ssh -f docker-bake.hcl #{targets.join(' ')} #{local_stage? ? '--load' : '--push'}
|
|
45
|
+
BASH
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def targets
|
|
49
|
+
repo.conf.dig(:deploy, :targets) || %w[app]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def tag
|
|
53
|
+
repo.conf.dig(:deploy, :tag) || repo.current_revision
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# utils
|
|
57
|
+
|
|
58
|
+
def local_stage?
|
|
59
|
+
Afterlife.current_stage.name.to_sym == :local
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def deploy_stage?
|
|
63
|
+
%i[qa production staging dev].include?(Afterlife.current_stage.name.to_sym)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Priority:
|
|
67
|
+
# 1. Uses AFTERLIFE_DEPLOY_REGISTRY from the ENV if it's defined. It can be
|
|
68
|
+
# either in real system ENV, or defined in the env section of the repo
|
|
69
|
+
# .afterlife.yml config
|
|
70
|
+
# 2. Uses repo .afterlife.yml config at deploy.registry
|
|
71
|
+
# 3. Uses stages.$current_stage.registry if exists in ~/.afterlife/config.yml
|
|
72
|
+
def registry
|
|
73
|
+
@registry ||= Afterlife.current_repo.variable('deploy.registry') || Afterlife.current_stage.registry
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
class AwsAuth
|
|
77
|
+
attr_reader :registry
|
|
78
|
+
|
|
79
|
+
def initialize(registry)
|
|
80
|
+
@registry = registry
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def commands
|
|
84
|
+
[docker_login]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def docker_login
|
|
88
|
+
<<-BASH
|
|
89
|
+
echo "#{aws_ecr_token}" | docker login --username AWS --password-stdin #{registry}
|
|
90
|
+
BASH
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def aws_ecr_token
|
|
94
|
+
@aws_ecr_token ||= Exec.result("aws ecr get-login-password --region #{region}")
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def region
|
|
98
|
+
@region ||= registry.gsub(/[^.]+\.dkr\.ecr\.([^.]+)\.amazonaws\.com/, '\1')
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -2,16 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Afterlife
|
|
4
4
|
module Deploy
|
|
5
|
-
class KubernetesDeployment <
|
|
6
|
-
def setup
|
|
7
|
-
Afterlife.current_repo.env.set('PLATFORM' => 'linux/amd64') if deploy_stage?
|
|
8
|
-
|
|
9
|
-
Afterlife.current_repo.env.set!(
|
|
10
|
-
'REGISTRY' => registry,
|
|
11
|
-
'TAG' => repo.current_revision,
|
|
12
|
-
)
|
|
13
|
-
end
|
|
14
|
-
|
|
5
|
+
class KubernetesDeployment < DockerDeployment
|
|
15
6
|
def run
|
|
16
7
|
Exec.run(commands)
|
|
17
8
|
end
|
|
@@ -24,7 +15,9 @@ module Afterlife
|
|
|
24
15
|
|
|
25
16
|
def commands
|
|
26
17
|
[
|
|
18
|
+
# from DockerDeployment
|
|
27
19
|
authenticate_command,
|
|
20
|
+
# from DockerDeployment
|
|
28
21
|
build_command,
|
|
29
22
|
set_image_command,
|
|
30
23
|
apply_kubernetes_settings,
|
|
@@ -33,20 +26,6 @@ module Afterlife
|
|
|
33
26
|
|
|
34
27
|
# commands
|
|
35
28
|
|
|
36
|
-
def authenticate_command
|
|
37
|
-
return if options['no-auth'] || local_stage?
|
|
38
|
-
|
|
39
|
-
AwsAuth.new(registry).commands
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def build_command
|
|
43
|
-
return if options['no-build']
|
|
44
|
-
|
|
45
|
-
<<-BASH
|
|
46
|
-
docker buildx bake --allow=ssh -f docker-bake.hcl #{targets.join(' ')} #{local_stage? ? '--load' : '--push'}
|
|
47
|
-
BASH
|
|
48
|
-
end
|
|
49
|
-
|
|
50
29
|
def set_image_command
|
|
51
30
|
<<-BASH
|
|
52
31
|
cd #{kubelocation} &&
|
|
@@ -58,10 +37,6 @@ module Afterlife
|
|
|
58
37
|
BASH
|
|
59
38
|
end
|
|
60
39
|
|
|
61
|
-
def targets
|
|
62
|
-
repo.conf.dig(:deploy, :targets) || %w[app]
|
|
63
|
-
end
|
|
64
|
-
|
|
65
40
|
def registry_image_name(target)
|
|
66
41
|
"#{registry}/#{full_image_name(target)}:#{repo.current_revision}"
|
|
67
42
|
end
|
|
@@ -82,6 +57,7 @@ module Afterlife
|
|
|
82
57
|
end
|
|
83
58
|
|
|
84
59
|
def assert_correct_context
|
|
60
|
+
return if Afterlife.current_stage.name == 'local'
|
|
85
61
|
return if current_context == expected_context
|
|
86
62
|
|
|
87
63
|
fail Error, "kubectl context should be '#{expected_context}' but you are in '#{current_context}'"
|
|
@@ -97,14 +73,6 @@ module Afterlife
|
|
|
97
73
|
|
|
98
74
|
# utils
|
|
99
75
|
|
|
100
|
-
def local_stage?
|
|
101
|
-
Afterlife.current_stage.name.to_sym == :local
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def deploy_stage?
|
|
105
|
-
%i[qa production staging].include?(Afterlife.current_stage.name.to_sym)
|
|
106
|
-
end
|
|
107
|
-
|
|
108
76
|
def kubelocation
|
|
109
77
|
".afterlife/#{Afterlife.current_stage.name}"
|
|
110
78
|
end
|
|
@@ -114,42 +82,6 @@ module Afterlife
|
|
|
114
82
|
fail Error, 'deploy.image_name for kubernetes deployments' unless result
|
|
115
83
|
end
|
|
116
84
|
end
|
|
117
|
-
|
|
118
|
-
# Priority:
|
|
119
|
-
# 1. Uses AFTERLIFE_DEPLOY_REGISTRY from the ENV if it's defined. It can be
|
|
120
|
-
# either in real system ENV, or defined in the env section of the repo
|
|
121
|
-
# .afterlife.yml config
|
|
122
|
-
# 2. Uses repo .afterlife.yml config at deploy.registry
|
|
123
|
-
# 3. Uses stages.$current_stage.registry if exists in ~/.afterlife/config.yml
|
|
124
|
-
def registry
|
|
125
|
-
@registry ||= Afterlife.current_repo.variable('deploy.registry') || Afterlife.current_stage.registry
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
class AwsAuth
|
|
129
|
-
attr_reader :registry
|
|
130
|
-
|
|
131
|
-
def initialize(registry)
|
|
132
|
-
@registry = registry
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
def commands
|
|
136
|
-
[docker_login]
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
def docker_login
|
|
140
|
-
<<-BASH
|
|
141
|
-
echo "#{aws_ecr_token}" | docker login --username AWS --password-stdin #{registry}
|
|
142
|
-
BASH
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
def aws_ecr_token
|
|
146
|
-
@aws_ecr_token ||= Exec.result("aws ecr get-login-password --region #{region}")
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
def region
|
|
150
|
-
@region ||= registry.gsub(/[^.]+\.dkr\.ecr\.([^.]+)\.amazonaws\.com/, '\1')
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
85
|
end
|
|
154
86
|
end
|
|
155
87
|
end
|
data/lib/afterlife/deploy.rb
CHANGED
data/lib/afterlife/notify.rb
CHANGED
|
@@ -28,8 +28,8 @@ module Afterlife
|
|
|
28
28
|
def send_request(repository, branch)
|
|
29
29
|
uri = URI(notification_url)
|
|
30
30
|
body = {
|
|
31
|
-
branch
|
|
32
|
-
repository
|
|
31
|
+
branch:,
|
|
32
|
+
repository:,
|
|
33
33
|
}
|
|
34
34
|
headers = { 'Content-Type': 'application/json' }
|
|
35
35
|
Net::HTTP.post(uri, body.to_json, headers)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Afterlife
|
|
4
|
+
module Propagate
|
|
5
|
+
class Cli < Thor
|
|
6
|
+
include BaseCli
|
|
7
|
+
|
|
8
|
+
def self.exit_on_failure?
|
|
9
|
+
true
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
desc 'propagate-qa-to-sandbox', 'Propagate QA branch to Sandbox branch (fetch, reset, push)'
|
|
13
|
+
option :yes, type: :boolean, aliases: '-y',
|
|
14
|
+
desc: 'Skip confirmation prompt'
|
|
15
|
+
def propagate_qa_to_sandbox
|
|
16
|
+
confirmation_message = <<~MSG
|
|
17
|
+
This command will perform the following actions:
|
|
18
|
+
|
|
19
|
+
1. Fetch the latest changes from origin/qa
|
|
20
|
+
2. Switch to the sandbox branch
|
|
21
|
+
3. Reset sandbox branch to match origin/qa exactly
|
|
22
|
+
4. Force push the changes to origin/sandbox
|
|
23
|
+
5. Return to your original branch
|
|
24
|
+
|
|
25
|
+
This operation will completely overwrite the sandbox branch with the current state of QA.
|
|
26
|
+
Are you sure you want to proceed?
|
|
27
|
+
MSG
|
|
28
|
+
|
|
29
|
+
sure?(confirmation_message) unless options[:yes]
|
|
30
|
+
|
|
31
|
+
PropagateQaToSandbox.call
|
|
32
|
+
rescue StandardError => e
|
|
33
|
+
fatal!(e.message)
|
|
34
|
+
rescue Interrupt
|
|
35
|
+
log_interrupted
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def repo
|
|
41
|
+
Afterlife.current_repo
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Afterlife
|
|
4
|
+
module Propagate
|
|
5
|
+
class PropagateQaToSandbox
|
|
6
|
+
def self.call(repo_path = nil)
|
|
7
|
+
new(repo_path).call
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def initialize(repo_path = nil)
|
|
11
|
+
@repo_path = repo_path || Afterlife.current_repo.full_path
|
|
12
|
+
@original_branch = nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call
|
|
16
|
+
log_info "Starting QA to Sandbox propagation in #{@repo_path}"
|
|
17
|
+
|
|
18
|
+
@original_branch = current_branch
|
|
19
|
+
log_info "Current branch: #{@original_branch}"
|
|
20
|
+
|
|
21
|
+
fetch_qa_branch
|
|
22
|
+
switch_to_sandbox
|
|
23
|
+
reset_sandbox_to_qa
|
|
24
|
+
push_sandbox_changes
|
|
25
|
+
|
|
26
|
+
log_success 'Successfully propagated QA to Sandbox'
|
|
27
|
+
|
|
28
|
+
return_to_original_branch if @original_branch != 'sandbox'
|
|
29
|
+
rescue StandardError => e
|
|
30
|
+
log_error "Error during propagation: #{e.message}"
|
|
31
|
+
return_to_original_branch if @original_branch && @original_branch != current_branch
|
|
32
|
+
raise
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def fetch_qa_branch
|
|
38
|
+
log_info 'Fetching latest changes from remote...'
|
|
39
|
+
`cd #{@repo_path} && git fetch origin qa`
|
|
40
|
+
fail 'Failed to fetch origin/qa' unless $CHILD_STATUS.success?
|
|
41
|
+
|
|
42
|
+
log_success 'Successfully fetched origin/qa'
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def switch_to_sandbox
|
|
46
|
+
log_info 'Switching to sandbox branch...'
|
|
47
|
+
`cd #{@repo_path} && git checkout sandbox`
|
|
48
|
+
fail 'Failed to switch to sandbox branch' unless $CHILD_STATUS.success?
|
|
49
|
+
|
|
50
|
+
log_success 'Successfully switched to sandbox branch'
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def reset_sandbox_to_qa
|
|
54
|
+
log_info 'Resetting sandbox to origin/qa...'
|
|
55
|
+
`cd #{@repo_path} && git reset --hard origin/qa`
|
|
56
|
+
fail 'Failed to reset sandbox to origin/qa' unless $CHILD_STATUS.success?
|
|
57
|
+
|
|
58
|
+
log_success 'Successfully reset sandbox to origin/qa'
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def push_sandbox_changes
|
|
62
|
+
log_info 'About to push changes to remote sandbox...'
|
|
63
|
+
log_info 'This will force push and overwrite the remote sandbox branch'
|
|
64
|
+
|
|
65
|
+
unless yes?('Do you want to continue? [y/n]')
|
|
66
|
+
log_info 'Push cancelled by user. Reverting changes...'
|
|
67
|
+
revert_sandbox_changes
|
|
68
|
+
return
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
log_info 'Pushing changes to remote sandbox...'
|
|
72
|
+
`cd #{@repo_path} && git push origin sandbox --force-with-lease`
|
|
73
|
+
fail 'Failed to push to origin/sandbox' unless $CHILD_STATUS.success?
|
|
74
|
+
|
|
75
|
+
create_and_push_tag
|
|
76
|
+
|
|
77
|
+
log_success 'Successfully pushed to origin/sandbox'
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def revert_sandbox_changes
|
|
81
|
+
log_info 'Reverting sandbox to previous state...'
|
|
82
|
+
`cd #{@repo_path} && git reset --hard HEAD@{1}`
|
|
83
|
+
if $CHILD_STATUS.success?
|
|
84
|
+
log_success 'Successfully reverted sandbox changes'
|
|
85
|
+
else
|
|
86
|
+
log_error 'Failed to revert changes. Manual intervention may be required.'
|
|
87
|
+
fail 'Failed to revert sandbox changes'
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def return_to_original_branch
|
|
92
|
+
log_info "Returning to original branch: #{@original_branch}"
|
|
93
|
+
`cd #{@repo_path} && git checkout #{@original_branch}`
|
|
94
|
+
fail "Failed to return to original branch #{@original_branch}" unless $CHILD_STATUS.success?
|
|
95
|
+
|
|
96
|
+
log_success "Successfully returned to #{@original_branch}"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def current_branch
|
|
100
|
+
`cd #{@repo_path} && git rev-parse --abbrev-ref HEAD`.chomp
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def create_and_push_tag
|
|
104
|
+
tag_name = generate_tag_name
|
|
105
|
+
log_info "Creating tag: #{tag_name}"
|
|
106
|
+
|
|
107
|
+
`cd #{@repo_path} && git tag #{tag_name}`
|
|
108
|
+
fail "Failed to create tag #{tag_name}" unless $CHILD_STATUS.success?
|
|
109
|
+
|
|
110
|
+
log_info 'Pushing tag to remote...'
|
|
111
|
+
`cd #{@repo_path} && git push origin #{tag_name}`
|
|
112
|
+
fail "Failed to push tag #{tag_name}" unless $CHILD_STATUS.success?
|
|
113
|
+
|
|
114
|
+
log_success "Successfully created and pushed tag: #{tag_name}"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def generate_tag_name
|
|
118
|
+
now = Time.now
|
|
119
|
+
year = now.year.to_s[2..3] # Last two digits of year
|
|
120
|
+
month = format('%02d', now.month)
|
|
121
|
+
day = format('%02d', now.day)
|
|
122
|
+
"#{year}#{month}#{day}"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def log_info(message)
|
|
126
|
+
puts "\e[36m#{message}\e[0m"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def log_success(message)
|
|
130
|
+
puts "\e[32m#{message}\e[0m"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def log_error(message)
|
|
134
|
+
puts "\e[31m#{message}\e[0m"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def fail(message)
|
|
138
|
+
fail StandardError, message
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def yes?(question)
|
|
142
|
+
print "#{question} "
|
|
143
|
+
response = $stdin.gets.chomp.downcase
|
|
144
|
+
%w[y yes].include?(response)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -26,7 +26,7 @@ module Afterlife
|
|
|
26
26
|
assert_git_dependency
|
|
27
27
|
fatal! "Part '#{part}' is not allowed" unless CreateHelper::PARTS.include?(part.to_sym)
|
|
28
28
|
|
|
29
|
-
CreateHelper.call(self, options.merge(part:
|
|
29
|
+
CreateHelper.call(self, options.merge(part:))
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
desc 'hotfix', 'create a hotfix branch'
|
|
@@ -19,7 +19,7 @@ module Afterlife
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def call
|
|
22
|
-
|
|
22
|
+
creation_prompt unless cli.options[:yes]
|
|
23
23
|
make_assertions unless cli.options[:force]
|
|
24
24
|
create_branch
|
|
25
25
|
ChangeVersion.call(new_version)
|
|
@@ -49,10 +49,11 @@ module Afterlife
|
|
|
49
49
|
cli.fatal! 'Local and remote are not on the same commit'
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
def
|
|
52
|
+
def creation_prompt
|
|
53
53
|
cli.sure? 'You are about to create the branch ' \
|
|
54
54
|
"#{cli.set_color(branch_name, :bold)} " \
|
|
55
55
|
"from #{cli.set_color(from, :bold)}"
|
|
56
|
+
nil
|
|
56
57
|
end
|
|
57
58
|
|
|
58
59
|
def create_branch
|
|
@@ -4,20 +4,21 @@ module Afterlife
|
|
|
4
4
|
module Release
|
|
5
5
|
class PreHelper < Helper
|
|
6
6
|
def call
|
|
7
|
-
|
|
7
|
+
creation_prompt
|
|
8
8
|
check_tag_does_not_exist
|
|
9
9
|
commit
|
|
10
10
|
create_tag
|
|
11
11
|
push_tag if cli.options[:push]
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
def
|
|
14
|
+
def creation_prompt
|
|
15
15
|
return if cli.options[:yes]
|
|
16
16
|
|
|
17
|
-
cli.
|
|
18
|
-
|
|
17
|
+
action = cli.options[:push] ? '(and push) ' : ''
|
|
18
|
+
cli.sure? "You are about to create #{action}" \
|
|
19
19
|
"the tag #{cli.set_color(tag, :bold)} " \
|
|
20
20
|
'in the current commit'
|
|
21
|
+
nil
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
def check_tag_does_not_exist
|
|
@@ -5,24 +5,24 @@ module Afterlife
|
|
|
5
5
|
# Reads configuration from the repo,
|
|
6
6
|
# either from YML or from the repo instance methods
|
|
7
7
|
class Config
|
|
8
|
-
Error
|
|
8
|
+
class Error < StandardError; end
|
|
9
9
|
|
|
10
10
|
attr_reader :path
|
|
11
11
|
|
|
12
|
-
def self.read(path, &
|
|
13
|
-
new(path).read(&
|
|
12
|
+
def self.read(path, &)
|
|
13
|
+
new(path).read(&)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def initialize(path)
|
|
17
17
|
@path = path
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
def read(&
|
|
20
|
+
def read(&)
|
|
21
21
|
case from_yml
|
|
22
22
|
when Hash
|
|
23
23
|
yield(JSON.generate(from_yml))
|
|
24
24
|
when Array
|
|
25
|
-
from_yml.each(&
|
|
25
|
+
from_yml.each(&)
|
|
26
26
|
when String, Symbol
|
|
27
27
|
yield(from_yml)
|
|
28
28
|
else
|
data/lib/afterlife/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: afterlife
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Genaro Madrid
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-07-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -24,6 +24,20 @@ dependencies:
|
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: ostruct
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
27
41
|
- !ruby/object:Gem::Dependency
|
|
28
42
|
name: racc
|
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -97,9 +111,12 @@ files:
|
|
|
97
111
|
- lib/afterlife/clickup.rb
|
|
98
112
|
- lib/afterlife/clickup/api.rb
|
|
99
113
|
- lib/afterlife/clickup/cli.rb
|
|
114
|
+
- lib/afterlife/clickup/commit_input_splitter.rb
|
|
100
115
|
- lib/afterlife/clickup/get_ids_from_text.rb
|
|
101
116
|
- lib/afterlife/clickup/get_range_commits.rb
|
|
117
|
+
- lib/afterlife/clickup/next_task_status_decider.rb
|
|
102
118
|
- lib/afterlife/clickup/status_precedence_checker.rb
|
|
119
|
+
- lib/afterlife/clickup/task_revert_detector.rb
|
|
103
120
|
- lib/afterlife/clickup/update_task_status.rb
|
|
104
121
|
- lib/afterlife/config.rb
|
|
105
122
|
- lib/afterlife/config/cli.rb
|
|
@@ -110,10 +127,14 @@ files:
|
|
|
110
127
|
- lib/afterlife/deploy/cli.rb
|
|
111
128
|
- lib/afterlife/deploy/custom_deployment.rb
|
|
112
129
|
- lib/afterlife/deploy/deployment.rb
|
|
130
|
+
- lib/afterlife/deploy/docker_deployment.rb
|
|
113
131
|
- lib/afterlife/deploy/kubernetes_deployment.rb
|
|
114
132
|
- lib/afterlife/environment.rb
|
|
115
133
|
- lib/afterlife/exec.rb
|
|
116
134
|
- lib/afterlife/notify.rb
|
|
135
|
+
- lib/afterlife/propagate.rb
|
|
136
|
+
- lib/afterlife/propagate/cli.rb
|
|
137
|
+
- lib/afterlife/propagate/propagate_qa_to_sandbox.rb
|
|
117
138
|
- lib/afterlife/release.rb
|
|
118
139
|
- lib/afterlife/release/change_version.rb
|
|
119
140
|
- lib/afterlife/release/cli.rb
|
|
@@ -140,7 +161,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
140
161
|
requirements:
|
|
141
162
|
- - ">="
|
|
142
163
|
- !ruby/object:Gem::Version
|
|
143
|
-
version: '
|
|
164
|
+
version: '3.3'
|
|
144
165
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
145
166
|
requirements:
|
|
146
167
|
- - ">="
|