afterlife 1.3.0 → 1.4.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: 785bb4c634a1b73338a56cb64b6119389ca07904457159b14af3a00ad48869f0
4
- data.tar.gz: 0f24b2547eb32ab72ae1807f2c5bcb813a5cc09f9dee589f6343bbd047c32c26
3
+ metadata.gz: 50bb80b8a5c213397eb84b52d78f39fc6ff72491a45df415e0c7700d1c2eb4bc
4
+ data.tar.gz: d6a8963da3e0d48f511e182d4bcf5c49f20e4e7449e29e9d8cc1f35c2a43fcc8
5
5
  SHA512:
6
- metadata.gz: 3ff42032daec81547cc0075ab8ed71d250b2cec8ac677383f6a9702e75124ebb27d173c085c8e5d155cfd9c0b63f49e79c3a7f35630de88424127816f3de4593
7
- data.tar.gz: 48d016ace44ecc8f285eee917e6493766bc914adc324cc582f0e7a7c495fdc9ede0548f5c3529fb6ac296a2e529ed8b83a0208c5f9ae87e5d6d43eafad540afc
6
+ metadata.gz: '0594d2bce7f949b133c67536d8a3115b2bcdc3a12dc6826135335d6aa5d1cd5e0bd938088a5437bb0381aef2b509b06f990c5268b5a21e8d667882b1355ed274'
7
+ data.tar.gz: 8b40060493294350391a2aec32c866cadd04a7aaf087ada970fb267430e836c3f724b4dc1ef683beb3337e59cd8b3a77f420afd168830d6972b7c8ee301e8fb8
data/lib/afterlife/cli.rb CHANGED
@@ -31,12 +31,13 @@ module Afterlife
31
31
  desc 'deploy <stage>', 'Deploy current repo'
32
32
  option 'no-build', type: :boolean
33
33
  option 'no-install', type: :boolean
34
+ option 'no-auth', type: :boolean
34
35
  option 'dry-run', type: :boolean
35
36
  option 'skip-after-hooks', type: :boolean
36
37
  option :yes, type: :boolean
37
38
  def deploy(stage)
38
39
  invoke Deploy::Cli, :call, [stage], options
39
- rescue Deploy::Error
40
+ rescue Error
40
41
  fatal!(e.message)
41
42
  rescue Interrupt
42
43
  log_interrupted
@@ -11,14 +11,14 @@ module Afterlife
11
11
  desc 'deploy <stage>', 'Deploy current repo'
12
12
  def call(stage)
13
13
  Afterlife.cli = self
14
- Deploy.call(stage) do |deployment|
14
+ Deploy.call(stage, options) do |deployment|
15
15
  setup_deploy(deployment)
16
16
  say_status 'Deploying', deployment.initial_message
17
17
  deployment.run
18
18
  say_status 'Deployed', deployment.output
19
19
  run_after_hooks
20
20
  end
21
- rescue Deploy::Error, Afterlife::Error => e
21
+ rescue Afterlife::Error => e
22
22
  fatal!(e.message)
23
23
  end
24
24
 
@@ -3,6 +3,13 @@
3
3
  module Afterlife
4
4
  module Deploy
5
5
  class Deployment
6
+
7
+ attr_reader :options
8
+
9
+ def initialize(options)
10
+ @options = options
11
+ end
12
+
6
13
  def run
7
14
  fail NotImplementedError
8
15
  end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Afterlife
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
+
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
+ set_image_command,
30
+ local_stage? ? delete_kubernetes_resource : nil,
31
+ apply_kubernetes_settings,
32
+ ].flatten.compact
33
+ end
34
+
35
+ # commands
36
+
37
+ def authenticate_command
38
+ return if options['no-auth']
39
+
40
+ <<-BASH
41
+ echo "#{aws_ecr_token}" | docker login --username AWS --password-stdin #{registry} &&
42
+ kubectl delete secret regcred &&
43
+ kubectl create secret docker-registry regcred
44
+ --docker-server=#{registry}
45
+ --docker-username=AWS
46
+ --docker-password=#{aws_ecr_token}
47
+ --docker-email=devs@mifiel.com
48
+ BASH
49
+ end
50
+
51
+ def build_command
52
+ return if options['no-build']
53
+
54
+ <<-BASH
55
+ docker buildx bake -f docker-bake.hcl #{local_stage? ? '--load' : '--push'}
56
+ BASH
57
+ end
58
+
59
+ def set_image_command
60
+ <<-BASH
61
+ $(cd #{kubelocation} && kustomize edit set image #{image_name}:latest=#{full_image_name})
62
+ BASH
63
+ end
64
+
65
+ def delete_kubernetes_resource
66
+ <<-BASH
67
+ kubectl delete -k #{kubelocation}
68
+ BASH
69
+ end
70
+
71
+ def apply_kubernetes_settings
72
+ <<-BASH
73
+ kubectl apply -k #{kubelocation}
74
+ BASH
75
+ end
76
+
77
+ # utils
78
+
79
+ def local_stage?
80
+ Afterlife.current_stage.name.to_sym == :local
81
+ end
82
+
83
+ def deploy_stage?
84
+ %i[qa production staging].include?(Afterlife.current_stage.name.to_sym)
85
+ end
86
+
87
+ def kubelocation
88
+ ".afterlife/#{Afterlife.current_stage.name}"
89
+ end
90
+
91
+ def full_image_name
92
+ "#{registry}/#{image_name}:#{repo.current_revision}"
93
+ end
94
+
95
+ def image_name
96
+ @image_name ||= repo.conf.dig(:deploy, :image_name)
97
+ end
98
+
99
+ # Priority:
100
+ # 1. Uses AFTERLIFE_DEPLOY_REGISTRY from the ENV if it's defined. It can be
101
+ # either in real system ENV, or defined in the env section of the repo
102
+ # .afterlife.yml config
103
+ # 2. Uses repo .afterlife.yml config at deploy.registry
104
+ # 3. Uses stages.$current_stage.registry if exists in ~/.afterlife/config.yml
105
+ def registry
106
+ @registry ||= Afterlife.current_repo.variable('deploy.registry') || Afterlife.current_stage.registry
107
+ end
108
+
109
+ # command outputs
110
+
111
+ def aws_ecr_token
112
+ @aws_ecr_token ||= `aws ecr get-login-password --region us-west-2`.strip
113
+ end
114
+ end
115
+ end
116
+ end
@@ -4,11 +4,9 @@ module Afterlife
4
4
  module Deploy
5
5
  module_function
6
6
 
7
- Error = Class.new StandardError
8
-
9
- def call(stage)
10
- deployment = klass.new
11
- Afterlife.current_stage = find_stage(stage)
7
+ def call(stage, options)
8
+ deployment = klass.new(options)
9
+ Afterlife.current_stage = stage
12
10
  fill_env
13
11
  deployment.setup
14
12
  yield deployment
@@ -28,16 +26,6 @@ module Afterlife
28
26
  end
29
27
  end
30
28
 
31
- def find_stage(stage)
32
- return Stage.build(stage) unless Afterlife.config.file_exist?
33
-
34
- unless Afterlife.config.stages.key?(stage.to_sym)
35
- fail Error, "invalid stage '#{stage}'. Possible values: #{Afterlife.config.stages.keys.map(&:to_s)}"
36
- end
37
-
38
- Afterlife.config.stages[stage.to_sym]
39
- end
40
-
41
29
  def fill_env
42
30
  # in nested deployments we need the current definition, not the parent's
43
31
  Afterlife.current_repo.env.set!(
@@ -21,21 +21,19 @@ module Afterlife
21
21
  from_stage_envs
22
22
  end
23
23
 
24
- def env_hash_by_branch
25
- @env_hash_by_branch ||= env_hash&.dig(:by_branch, repo.current_branch.to_sym)
26
- end
27
-
28
- def env_hash_by_stage
29
- @env_hash_by_stage ||= env_hash&.dig(:by_stage, Afterlife.current_stage.name.to_sym)
30
- end
24
+ def [](key)
25
+ key = key.gsub('.', '_').upcase
26
+ env_name = "AFTERLIFE_#{key}"
27
+ return ENV[env_name] if ENV.key?(env_name)
31
28
 
32
- def env_hash
33
- @env_hash ||= repo.conf.dig(:deploy, :env)
29
+ @env[env_name]
34
30
  end
35
31
 
36
32
  # soft set, do not override existent ENVs
37
33
  def set(arg)
38
34
  arg.each do |k, v|
35
+ fail ArgumentError, "key '#{k}' must be a string" unless k.is_a?(String)
36
+
39
37
  @env[k] ||= ENV.fetch(k, v)
40
38
  end
41
39
  end
@@ -43,6 +41,8 @@ module Afterlife
43
41
  # forces to be exactly dthe value
44
42
  def set!(arg)
45
43
  arg.each do |k, v|
44
+ fail ArgumentError, "key '#{k}' must be a string" unless k.is_a?(String)
45
+
46
46
  @env[k] = v
47
47
  end
48
48
  end
@@ -50,9 +50,9 @@ module Afterlife
50
50
  private
51
51
 
52
52
  def from_flat_envs
53
- return unless env_hash.is_a?(Hash)
53
+ return unless repo_env_config.is_a?(Hash)
54
54
 
55
- env_hash.each do |key, value|
55
+ repo_env_config.each do |key, value|
56
56
  next if key == :by_branch
57
57
  next if key == :by_stage
58
58
 
@@ -68,6 +68,10 @@ module Afterlife
68
68
  end
69
69
  end
70
70
 
71
+ def env_hash_by_branch
72
+ @env_hash_by_branch ||= repo_env_config&.dig(:by_branch, repo.current_branch.to_sym)
73
+ end
74
+
71
75
  def from_stage_envs
72
76
  return unless env_hash_by_stage.is_a?(Hash)
73
77
 
@@ -75,5 +79,15 @@ module Afterlife
75
79
  @env[key.to_s] = value.to_s
76
80
  end
77
81
  end
82
+
83
+ def env_hash_by_stage
84
+ return unless Afterlife.current_stage
85
+
86
+ @env_hash_by_stage ||= repo_env_config&.dig(:by_stage, Afterlife.current_stage.name.to_sym)
87
+ end
88
+
89
+ def repo_env_config
90
+ @repo_env_config ||= repo.conf.dig(:deploy, :env) || {}
91
+ end
78
92
  end
79
93
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Afterlife
4
4
  class Repo
5
+ # Reads configuration from the repo,
6
+ # either from YML or from the repo instance methods
5
7
  class Config
6
8
  Error = Class.new StandardError
7
9
 
@@ -12,7 +14,7 @@ module Afterlife
12
14
  end
13
15
 
14
16
  def initialize(path)
15
- @path = path.split('.').map(&:to_sym)
17
+ @path = path
16
18
  end
17
19
 
18
20
  def read(&block) # rubocop:disable Metrics/MethodLength
@@ -24,7 +26,7 @@ module Afterlife
24
26
  when String, Symbol
25
27
  yield(from_yml)
26
28
  else
27
- fail Error, "No config with path '#{path.join('.')}'" unless repo_method?
29
+ fail Error, "No config with path '#{path}'" unless repo_method?
28
30
 
29
31
  yield(repo.public_send(repo_method))
30
32
  end
@@ -33,7 +35,7 @@ module Afterlife
33
35
  private
34
36
 
35
37
  def repo_method?
36
- path.count == 1 && repo.public_methods(false).include?(repo_method)
38
+ path.count('.').zero? && repo.public_methods(false).include?(repo_method)
37
39
  end
38
40
 
39
41
  def repo_method
@@ -47,7 +49,7 @@ module Afterlife
47
49
  def from_yml
48
50
  return @from_yml if defined?(@from_yml)
49
51
 
50
- @from_yml ||= repo.conf.dig(*path)
52
+ @from_yml ||= repo.variable(path)
51
53
  end
52
54
  end
53
55
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Afterlife
4
4
  class Repo
5
+ # Reads (and builds) the .afterlife.yml file in the repo
5
6
  class Provider
6
7
  CONFIG_NAME = '.afterlife.yml'
7
8
 
@@ -46,6 +46,14 @@ module Afterlife
46
46
  full_path.join(conf.dig(:cdn, :dist) || DEFAULT_DIST)
47
47
  end
48
48
 
49
+ # 1. Searchs for the AFTERLIFE_#{name} env var
50
+ # 2. Searchs the path in the repo .afterlife.yml config
51
+ def variable(name)
52
+ return env[name] if env[name]
53
+
54
+ conf.dig(*name.split('.').map(&:to_sym))
55
+ end
56
+
49
57
  def env
50
58
  @env ||= Environment.from(self)
51
59
  end
@@ -109,10 +117,6 @@ module Afterlife
109
117
  @package_json ||= JSON.parse(File.read(@pkg_path), symbolize_names: true)
110
118
  end
111
119
 
112
- def branch_conf
113
- conf.dig(:deploy, :env, :by_branch, current_branch.to_sym)
114
- end
115
-
116
120
  def conf
117
121
  @conf ||= Provider.new(full_path).config
118
122
  end
@@ -6,6 +6,7 @@ module Afterlife
6
6
  :bucket,
7
7
  :region,
8
8
  :cdn_url,
9
+ :registry,
9
10
  keyword_init: true,
10
11
  ) do
11
12
  def self.build(name = nil)
@@ -14,7 +15,18 @@ module Afterlife
14
15
  bucket: ENV.fetch('AWS_BUCKET', nil),
15
16
  region: ENV.fetch('AWS_REGION'),
16
17
  cdn_url: ENV.fetch('AWS_BUCKET', nil),
18
+ registry: ENV.fetch('AFTERLIFE_DEPLOY_REGISTRY', nil),
17
19
  )
18
20
  end
21
+
22
+ def self.find(stage)
23
+ return build(stage) unless Afterlife.config.file_exist?
24
+
25
+ unless Afterlife.config.stages.key?(stage.to_sym)
26
+ fail Error, "invalid stage '#{stage}'. Possible values: #{Afterlife.config.stages.keys.map(&:to_s)}"
27
+ end
28
+
29
+ Afterlife.config.stages[stage.to_sym]
30
+ end
19
31
  end
20
32
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Afterlife
4
- VERSION = '1.3.0'
4
+ VERSION = '1.4.0'
5
5
  end
data/lib/afterlife.rb CHANGED
@@ -33,7 +33,7 @@ module Afterlife
33
33
  end
34
34
 
35
35
  def self.current_stage=(other)
36
- @current_stage = other
36
+ @current_stage = Stage.find(other)
37
37
  end
38
38
 
39
39
  def self.current_stage
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.3.0
4
+ version: 1.4.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: 2023-11-21 00:00:00.000000000 Z
11
+ date: 2024-01-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -130,6 +130,7 @@ files:
130
130
  - lib/afterlife/deploy/cli.rb
131
131
  - lib/afterlife/deploy/custom_deployment.rb
132
132
  - lib/afterlife/deploy/deployment.rb
133
+ - lib/afterlife/deploy/kubernetes_deployment.rb
133
134
  - lib/afterlife/environment.rb
134
135
  - lib/afterlife/exec.rb
135
136
  - lib/afterlife/release.rb