afterlife 1.7.4 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5b3027a754b5aeaaa15a7dbbba4b5aa895ce11fb48402dd9a4348f30b3dc8aa7
4
- data.tar.gz: 2253a73fd40b0e247cafb3b35b0bd0d75fb9b78d722b7887a7800b30d0f8faa9
3
+ metadata.gz: 6f862660ef3c576008d2054a427dcd03fbbbd8a1fe070158507f1eeaa32d288c
4
+ data.tar.gz: 9cc64fe488e5c3da7213bd8ca3b61d86539207dfef6a42fc5e043cb5047d3246
5
5
  SHA512:
6
- metadata.gz: 01a9c292bef075d314f0ce97ff3deda29705247f9e6e0227c82645292fe0ffe262a4d18d62b8abd896b6376325bcb5071a61d90f992ee47e42dba0a0828bba64
7
- data.tar.gz: 2f7763617dbd485d863afb4ea226caac63fa3493b14ab3a0c35c186d11ff22f01a8b2e895ba0b7d12ca5cc30db45e140a4be0814e557840cdffd3ff085016b99
6
+ metadata.gz: 7a81d5767dadaf2ef615236856124f26fb17bdaaf198a5fef2edbfe520ca0aacb8196deab2345605f5d591ecfa35dbffd516db9061c98628b2aecf808986c8df
7
+ data.tar.gz: 8666f13424d39b7b5bc045e7b04c0b71de3a5842d3c519963e399fd62fc465ca6242711ad3ed79a1d4ce25e4b5db70065a6ff7fb2774bc1b9954c5851745fe9e
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
@@ -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
@@ -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 < 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' => 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
@@ -8,6 +8,7 @@ module Afterlife
8
8
  cdn: 'CdnDeployment',
9
9
  cdn_stenciljs: 'CdnStenciljsDeployment',
10
10
  custom: 'CustomDeployment',
11
+ docker: 'DockerDeployment',
11
12
  kubernetes: 'KubernetesDeployment',
12
13
  }.freeze
13
14
 
@@ -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
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Afterlife
4
+ module Propagate
5
+ end
6
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Afterlife
4
- VERSION = '1.7.4'
4
+ VERSION = '1.8.0'
5
5
  end
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.7.4
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-07-30 00:00:00.000000000 Z
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.5.16
154
+ rubygems_version: 3.2.33
151
155
  signing_key:
152
156
  specification_version: 4
153
157
  summary: Devops utils