afterlife 1.7.3 → 1.8.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/README.md +17 -0
- data/lib/afterlife/cli.rb +3 -0
- data/lib/afterlife/clickup/cli.rb +11 -3
- data/lib/afterlife/clickup/get_ids_from_text.rb +6 -2
- data/lib/afterlife/clickup/get_range_commits.rb +6 -2
- data/lib/afterlife/clickup/update_task_status.rb +18 -10
- data/lib/afterlife/config/provider.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/propagate/cli.rb +44 -0
- data/lib/afterlife/propagate/propagate_qa_to_sandbox.rb +148 -0
- data/lib/afterlife/propagate.rb +6 -0
- data/lib/afterlife/version.rb +1 -1
- metadata +7 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6f862660ef3c576008d2054a427dcd03fbbbd8a1fe070158507f1eeaa32d288c
|
|
4
|
+
data.tar.gz: 9cc64fe488e5c3da7213bd8ca3b61d86539207dfef6a42fc5e043cb5047d3246
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7a81d5767dadaf2ef615236856124f26fb17bdaaf198a5fef2edbfe520ca0aacb8196deab2345605f5d591ecfa35dbffd516db9061c98628b2aecf808986c8df
|
|
7
|
+
data.tar.gz: 8666f13424d39b7b5bc045e7b04c0b71de3a5842d3c519963e399fd62fc465ca6242711ad3ed79a1d4ce25e4b5db70065a6ff7fb2774bc1b9954c5851745fe9e
|
data/README.md
CHANGED
|
@@ -40,12 +40,29 @@ To not do this blindly, you can run just `afterlife clickup get-commits <revisio
|
|
|
40
40
|
clickup extract-ids` to review if the tasks being moved are correct. But the `update-status` in the
|
|
41
41
|
full command will print the list of moved or ignored tasks anyway.
|
|
42
42
|
|
|
43
|
+
Also, to easily run these commands, is recommended to setup an alias:
|
|
44
|
+
|
|
45
|
+
For bash or zsh:
|
|
46
|
+
```bash
|
|
47
|
+
alias ci="afterlife clickup"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
For fish:
|
|
51
|
+
```fish
|
|
52
|
+
alias ci 'afterlife clickup'
|
|
53
|
+
```
|
|
54
|
+
|
|
43
55
|
## Development
|
|
44
56
|
|
|
45
57
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
46
58
|
|
|
47
59
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
48
60
|
|
|
61
|
+
## Publishing
|
|
62
|
+
This gem is published on rubygems. To publish there, just:
|
|
63
|
+
1. Do a version bump in `lib/afterlife/version.rb`.
|
|
64
|
+
2. Execute `bundle exec rake release`. This will ask you for rubygems credentials. You'll access, ask Genaro for it.
|
|
65
|
+
|
|
49
66
|
## Contributing
|
|
50
67
|
|
|
51
68
|
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/afterlife.
|
data/lib/afterlife/cli.rb
CHANGED
|
@@ -50,6 +50,9 @@ module Afterlife
|
|
|
50
50
|
desc 'clickup', 'ClickUp integration commands'
|
|
51
51
|
subcommand 'clickup', Clickup::Cli
|
|
52
52
|
|
|
53
|
+
desc 'propagate', 'Propagate commands'
|
|
54
|
+
subcommand 'propagate', Propagate::Cli
|
|
55
|
+
|
|
53
56
|
map %w[-v --version] => :version
|
|
54
57
|
desc 'version', 'Prints afterlife version'
|
|
55
58
|
def version
|
|
@@ -12,16 +12,20 @@ module Afterlife
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
desc 'extract-ids [TEXT]', 'Extract ClickUp task IDs from text or stdin'
|
|
15
|
+
option :link, type: :boolean, default: false, aliases: '-l',
|
|
16
|
+
desc: 'Convert task IDs to links'
|
|
15
17
|
def extract_ids(text = nil)
|
|
16
18
|
text ||= $stdin.read
|
|
17
|
-
ids = GetIdsFromText.call(text)
|
|
19
|
+
ids = GetIdsFromText.call(text, options[:link])
|
|
18
20
|
ids.each { |id| puts id }
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
desc 'get-commits FROM_REVISION [TO_REVISION]',
|
|
22
24
|
'Get commits between revisions. The revision can be a short or long version of the commit sha.'
|
|
25
|
+
option :short, type: :boolean, default: false, aliases: '-s',
|
|
26
|
+
desc: 'Only prints a short version of the commit, do not use it on CI.'
|
|
23
27
|
def get_commits(from_revision, to_revision = 'HEAD')
|
|
24
|
-
messages = GetRangeCommits.call(from_revision, to_revision)
|
|
28
|
+
messages = GetRangeCommits.call(from_revision, to_revision, short: options[:short])
|
|
25
29
|
puts messages
|
|
26
30
|
rescue StandardError => e
|
|
27
31
|
fatal!(e.message)
|
|
@@ -29,10 +33,14 @@ module Afterlife
|
|
|
29
33
|
|
|
30
34
|
desc 'update-status TARGET_STATUS TASK_ID [TASK_ID...]',
|
|
31
35
|
"Update status of ClickUp tasks. Possible values for TARGET_STATUS: #{CLICKUP_STATUS_OPTIONS}"
|
|
36
|
+
option :force, type: :boolean, default: false, aliases: '-f',
|
|
37
|
+
desc: 'Force update status of tasks even if they would not be updated due to status precedence'
|
|
38
|
+
option :dry_run, type: :boolean, default: false, aliases: '-d',
|
|
39
|
+
desc: 'Do not actually update the status of the tasks, just print what would be done'
|
|
32
40
|
def update_status(target_status, *task_ids)
|
|
33
41
|
task_ids = $stdin.read.strip.split("\n") if task_ids.empty?
|
|
34
42
|
|
|
35
|
-
UpdateTaskStatus.call(task_ids, target_status)
|
|
43
|
+
UpdateTaskStatus.call(task_ids, target_status, force: options[:force], dry_run: options[:dry_run])
|
|
36
44
|
rescue StandardError => e
|
|
37
45
|
fatal!(e.message)
|
|
38
46
|
end
|
|
@@ -10,7 +10,7 @@ module Afterlife
|
|
|
10
10
|
/CU-([a-z0-9]+)/i,
|
|
11
11
|
].freeze
|
|
12
12
|
|
|
13
|
-
def self.call(text)
|
|
13
|
+
def self.call(text, convert_to_link = false) # rubocop:disable Metrics/MethodLength
|
|
14
14
|
ids = Set.new
|
|
15
15
|
|
|
16
16
|
ID_PATTERNS.each do |pattern|
|
|
@@ -19,7 +19,11 @@ module Afterlife
|
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
if convert_to_link
|
|
23
|
+
ids.map { |id| "https://app.clickup.com/t/#{id}" }
|
|
24
|
+
else
|
|
25
|
+
ids.to_a
|
|
26
|
+
end
|
|
23
27
|
end
|
|
24
28
|
end
|
|
25
29
|
end
|
|
@@ -5,9 +5,13 @@ require 'English'
|
|
|
5
5
|
module Afterlife
|
|
6
6
|
module Clickup
|
|
7
7
|
class GetRangeCommits
|
|
8
|
-
|
|
8
|
+
SHORT_PARAMS = "--graph --decorate --pretty=format:'%C(yellow)%h %C(cyan)%ad %C(cyan)%d %Creset%s %Cgreen[%an]' --date=relative" # rubocop:disable Layout/LineLength
|
|
9
|
+
DEFAULT_PARAMS = "--pretty=format:'%s%n%b'"
|
|
10
|
+
|
|
11
|
+
def self.call(from_revision, to_revision = 'HEAD', short: nil)
|
|
9
12
|
range = "#{from_revision}..#{to_revision}"
|
|
10
|
-
|
|
13
|
+
params = short ? SHORT_PARAMS : DEFAULT_PARAMS
|
|
14
|
+
commit_messages = `git log #{params} #{range}`.strip
|
|
11
15
|
fail "Error getting commits for range #{range}" unless $CHILD_STATUS.success?
|
|
12
16
|
|
|
13
17
|
commit_messages
|
|
@@ -3,27 +3,34 @@
|
|
|
3
3
|
module Afterlife
|
|
4
4
|
module Clickup
|
|
5
5
|
class UpdateTaskStatus
|
|
6
|
-
attr_reader :target_status, :precedence_checker, :api
|
|
6
|
+
attr_reader :target_status, :precedence_checker, :api, :force, :dry_run
|
|
7
7
|
|
|
8
|
-
def self.call(task_ids, target_status)
|
|
9
|
-
new.call(task_ids, target_status)
|
|
8
|
+
def self.call(task_ids, target_status, force: false, dry_run: false)
|
|
9
|
+
new(force: force, dry_run: dry_run).call(task_ids, target_status)
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
def initialize
|
|
12
|
+
def initialize(force: false, dry_run: false)
|
|
13
|
+
@force = force
|
|
14
|
+
@dry_run = dry_run
|
|
13
15
|
token = ENV['CLICKUP_TOKEN'] || fail('CLICKUP_TOKEN required, put `export CLICKUP_TOKEN=...` or the equivalent in your shell') # rubocop:disable Layout/LineLength
|
|
14
16
|
@api = Api.new(token: token)
|
|
15
17
|
@precedence_checker = StatusPrecedenceChecker.new
|
|
16
18
|
end
|
|
17
19
|
|
|
18
|
-
def call(task_ids, target_status)
|
|
20
|
+
def call(task_ids, target_status) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
19
21
|
if task_ids.empty?
|
|
20
22
|
puts 'No tasks to update'
|
|
21
23
|
return []
|
|
22
24
|
end
|
|
23
25
|
|
|
26
|
+
puts 'WARNING: Forcing status updates...' if force
|
|
27
|
+
puts 'WARNING: Dry run, no tasks will be updated' if dry_run
|
|
24
28
|
puts "Updating #{task_ids.length} tasks to '#{target_status}'"
|
|
25
29
|
moved_task_ids = task_ids.map { |task_id| move_task(task_id, target_status) }.compact
|
|
26
|
-
puts "
|
|
30
|
+
puts "#{dry_run ? 'Would have moved' : 'Moved'} #{moved_task_ids.length} tasks:"
|
|
31
|
+
moved_task_ids.each do |task|
|
|
32
|
+
puts "- #{task[:link]} (from '#{task[:previous_status]}' to '#{task[:new_status]}')"
|
|
33
|
+
end
|
|
27
34
|
moved_task_ids
|
|
28
35
|
end
|
|
29
36
|
|
|
@@ -34,11 +41,12 @@ module Afterlife
|
|
|
34
41
|
|
|
35
42
|
return puts "WARNING: Task #{task_id} not found" if current_status.nil?
|
|
36
43
|
|
|
37
|
-
should_skip_update = precedence_checker.should_skip_update?(current_status, target_status)
|
|
38
|
-
|
|
44
|
+
should_skip_update = !force && precedence_checker.should_skip_update?(current_status, target_status)
|
|
45
|
+
task_link = "https://app.clickup.com/t/#{task_id}"
|
|
46
|
+
return puts "Skipping task #{task_link} because it's already at '#{current_status}'" if should_skip_update
|
|
39
47
|
|
|
40
|
-
api.update_task(task_id, { status: target_status })
|
|
41
|
-
task_id
|
|
48
|
+
api.update_task(task_id, { status: target_status }) unless dry_run
|
|
49
|
+
{ id: task_id, link: task_link, previous_status: current_status, new_status: target_status }
|
|
42
50
|
end
|
|
43
51
|
end
|
|
44
52
|
end
|
|
@@ -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
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Afterlife
|
|
4
|
+
module Propagate
|
|
5
|
+
class Cli < Thor
|
|
6
|
+
include BaseCli
|
|
7
|
+
def self.exit_on_failure?
|
|
8
|
+
true
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
desc 'propagate-qa-to-sandbox', 'Propagate QA branch to Sandbox branch (fetch, reset, push)'
|
|
12
|
+
option :yes, type: :boolean, aliases: '-y',
|
|
13
|
+
desc: 'Skip confirmation prompt'
|
|
14
|
+
def propagate_qa_to_sandbox
|
|
15
|
+
confirmation_message = <<~MSG
|
|
16
|
+
This command will perform the following actions:
|
|
17
|
+
|
|
18
|
+
1. Fetch the latest changes from origin/qa
|
|
19
|
+
2. Switch to the sandbox branch
|
|
20
|
+
3. Reset sandbox branch to match origin/qa exactly
|
|
21
|
+
4. Force push the changes to origin/sandbox
|
|
22
|
+
5. Return to your original branch
|
|
23
|
+
|
|
24
|
+
This operation will completely overwrite the sandbox branch with the current state of QA.
|
|
25
|
+
Are you sure you want to proceed?
|
|
26
|
+
MSG
|
|
27
|
+
|
|
28
|
+
sure?(confirmation_message) unless options[:yes]
|
|
29
|
+
|
|
30
|
+
PropagateQaToSandbox.call
|
|
31
|
+
rescue StandardError => e
|
|
32
|
+
fatal!(e.message)
|
|
33
|
+
rescue Interrupt
|
|
34
|
+
log_interrupted
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def repo
|
|
40
|
+
Afterlife.current_repo
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
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
|
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.8.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: 2025-
|
|
11
|
+
date: 2025-11-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -110,10 +110,14 @@ files:
|
|
|
110
110
|
- lib/afterlife/deploy/cli.rb
|
|
111
111
|
- lib/afterlife/deploy/custom_deployment.rb
|
|
112
112
|
- lib/afterlife/deploy/deployment.rb
|
|
113
|
+
- lib/afterlife/deploy/docker_deployment.rb
|
|
113
114
|
- lib/afterlife/deploy/kubernetes_deployment.rb
|
|
114
115
|
- lib/afterlife/environment.rb
|
|
115
116
|
- lib/afterlife/exec.rb
|
|
116
117
|
- lib/afterlife/notify.rb
|
|
118
|
+
- lib/afterlife/propagate.rb
|
|
119
|
+
- lib/afterlife/propagate/cli.rb
|
|
120
|
+
- lib/afterlife/propagate/propagate_qa_to_sandbox.rb
|
|
117
121
|
- lib/afterlife/release.rb
|
|
118
122
|
- lib/afterlife/release/change_version.rb
|
|
119
123
|
- lib/afterlife/release/cli.rb
|
|
@@ -147,7 +151,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
147
151
|
- !ruby/object:Gem::Version
|
|
148
152
|
version: '0'
|
|
149
153
|
requirements: []
|
|
150
|
-
rubygems_version: 3.
|
|
154
|
+
rubygems_version: 3.2.33
|
|
151
155
|
signing_key:
|
|
152
156
|
specification_version: 4
|
|
153
157
|
summary: Devops utils
|