envirobly 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7cf07e75c65933a5f7c064588ac13a36ee5af3e698fc82f90affc0960bf7d170
4
- data.tar.gz: 2f721b799aeb0b2725c92b75b9f1d63b461ad5cfd44d095481aea5c809bd1a2c
3
+ metadata.gz: aefc0201069c2ebcf4e9038a80904d7e3c6b3217a582572db2536cf9f877367e
4
+ data.tar.gz: 26b791aa16c6c38613e7ef5b32cc8f1c93eb7342327ed52968e6ee345a9fa12f
5
5
  SHA512:
6
- metadata.gz: a37f7b8895957e017ddab73d991c1a34b7b50aeff1e491caf923164e3e6c5abe8ddc67900371fc8dc1c7a552c3e2c930e9cb2b92174953531c87fc495debf357
7
- data.tar.gz: 43075318aa04c61690e71665823e19da68c2797e1d4534433235a4f3f46254343d454abaa9cd367b641184452df6546ad5f9f447a89e51943dc34084f08202a7
6
+ metadata.gz: b5dd97f5d8c19a6e3690884f9f9fc9803a338db2048aaf96a9e5502f68f1d70890a1906f5a478dc998b78e39874cce3db600411a71ee615b41276d63dccdf8c4
7
+ data.tar.gz: fe25c4413c314d7368e7fe6b0b05c44f3919159421b75a6511c1ee5c3ba3174ca8c5e3dc52628cd485556a1ef81e92cb2116549573b3d843ed1f753df3ba337b
data/lib/core_ext.rb ADDED
@@ -0,0 +1,113 @@
1
+ class Array
2
+ alias_method :blank?, :empty?
3
+
4
+ def present?
5
+ !empty?
6
+ end
7
+
8
+ def second
9
+ self[1]
10
+ end
11
+
12
+ def third
13
+ self[2]
14
+ end
15
+
16
+ def fourth
17
+ self[3]
18
+ end
19
+
20
+ def fifth
21
+ self[4]
22
+ end
23
+ end
24
+
25
+ class Object
26
+ def blank?
27
+ respond_to?(:empty?) ? !!empty? : false
28
+ end
29
+
30
+ def present?
31
+ !blank?
32
+ end
33
+
34
+ def presence
35
+ self if present?
36
+ end
37
+ end
38
+
39
+ class NilClass
40
+ def blank?
41
+ true
42
+ end
43
+
44
+ def present?
45
+ false
46
+ end
47
+ end
48
+
49
+ class FalseClass
50
+ def blank?
51
+ true
52
+ end
53
+
54
+ def present?
55
+ false
56
+ end
57
+ end
58
+
59
+ class TrueClass
60
+ def blank?
61
+ false
62
+ end
63
+
64
+ def present?
65
+ true
66
+ end
67
+ end
68
+
69
+ class Hash
70
+ alias_method :blank?, :empty?
71
+
72
+ def present?
73
+ !empty?
74
+ end
75
+ end
76
+
77
+ class Symbol
78
+ alias_method :blank?, :empty?
79
+
80
+ def present?
81
+ !empty?
82
+ end
83
+ end
84
+
85
+ class String
86
+ def blank?
87
+ strip.empty?
88
+ end
89
+
90
+ def present?
91
+ !blank?
92
+ end
93
+ end
94
+
95
+ class Numeric
96
+ def blank?
97
+ false
98
+ end
99
+
100
+ def present?
101
+ true
102
+ end
103
+ end
104
+
105
+ class Time
106
+ def blank?
107
+ false
108
+ end
109
+
110
+ def present?
111
+ true
112
+ end
113
+ end
data/lib/envirobly/api.rb CHANGED
@@ -28,7 +28,7 @@ class Envirobly::Api
28
28
  response = get_as_json URI(url)
29
29
 
30
30
  if response.code.to_i == 200
31
- return response
31
+ response
32
32
  elsif MAX_RETRIES <= tries
33
33
  $stderr.puts "Max retries exhausted while waiting for deployment credentials. Aborting."
34
34
  exit 1
@@ -5,9 +5,9 @@ class Envirobly::Aws::Credentials
5
5
 
6
6
  def as_env_vars
7
7
  [
8
- %{AWS_ACCESS_KEY_ID="#{@params.fetch("access_key_id")}"},
9
- %{AWS_SECRET_ACCESS_KEY="#{@params.fetch("secret_access_key")}"},
10
- %{AWS_SESSION_TOKEN="#{@params.fetch("session_token")}"}
8
+ %(AWS_ACCESS_KEY_ID="#{@params.fetch("access_key_id")}"),
9
+ %(AWS_SECRET_ACCESS_KEY="#{@params.fetch("secret_access_key")}"),
10
+ %(AWS_SESSION_TOKEN="#{@params.fetch("session_token")}")
11
11
  ]
12
12
  end
13
13
 
@@ -6,6 +6,7 @@ class Envirobly::Cli::Main < Envirobly::Base
6
6
 
7
7
  desc "deploy ENVIRONMENT", "Deploy to environment identified by name or URL"
8
8
  method_option :commit, type: :string, default: "HEAD"
9
+ method_option :dry_run, type: :boolean, default: false
9
10
  def deploy(environment)
10
11
  abort_if_aws_cli_is_missing
11
12
  Envirobly::Deployment.new environment, options
@@ -6,76 +6,110 @@ class Envirobly::Config
6
6
  DIR = ".envirobly"
7
7
  PATH = "#{DIR}/project.yml"
8
8
 
9
- attr_reader :parsing_error
9
+ attr_reader :errors, :result, :raw
10
10
 
11
11
  def initialize(commit)
12
12
  @commit = commit
13
- @parsing_error = nil
14
- @project = parse_config_content_at_commit
15
-
16
- if @project
17
- transform_env_var_values!
18
- append_image_tags!
19
- end
13
+ @errors = []
14
+ @result = nil
15
+ @project_url = nil
16
+ @raw = @commit.file_content PATH
20
17
  end
21
18
 
22
19
  def dig(*args)
23
- @project.dig *args
20
+ @project.dig(*args)
24
21
  rescue NoMethodError
25
22
  nil
26
23
  end
27
24
 
28
- def to_h
29
- @project
30
- end
31
-
32
- def parsing_error?
33
- !@parsing_error.nil?
25
+ def compile(environment = nil)
26
+ @environment = environment
27
+ return unless @project = parse
28
+ set_project_url
29
+ merge_environment_overrides! unless @environment.nil?
30
+ transform_env_var_values!
31
+ append_image_tags!
32
+ @result = @project.slice(:services)
34
33
  end
35
34
 
36
- def path
37
- PATH
35
+ def to_deployment_params
36
+ {
37
+ environ: {
38
+ logical_id: @environment,
39
+ project_url: @project_url
40
+ },
41
+ commit: {
42
+ ref: @commit.ref,
43
+ time: @commit.time,
44
+ message: @commit.message
45
+ },
46
+ config: @result,
47
+ raw_config: @raw
48
+ }
38
49
  end
39
50
 
40
51
  private
41
- def parse_config_content_at_commit
42
- YAML.load config_content_at_commit, aliases: true
52
+ def parse
53
+ YAML.safe_load @raw, aliases: true, symbolize_names: true
43
54
  rescue Psych::Exception => exception
44
- @parsing_error = exception.message
55
+ @errors << exception.message
45
56
  nil
46
57
  end
47
58
 
48
- def config_content_at_commit
49
- `git show #{@commit.ref}:#{path}`
59
+ def set_project_url
60
+ @project_url = dig :remote, :origin
61
+ if @project_url.blank?
62
+ @errors << "Missing a `remote.origin` link to project."
63
+ end
50
64
  end
51
65
 
52
66
  def transform_env_var_values!
53
- @project.fetch("services", {}).each do |logical_id, service|
54
- service.fetch("env", {}).each do |key, value|
55
- if value.is_a?(Hash) && value.has_key?("file")
56
- @project["services"][logical_id]["env"][key] = File.read value.fetch("file")
67
+ @project.fetch(:services, {}).each do |logical_id, service|
68
+ service.fetch(:env, {}).each do |key, value|
69
+ if value.is_a?(Hash) && value.has_key?(:file)
70
+ @project[:services][logical_id][:env][key] = @commit.file_content(value.fetch(:file)).strip
57
71
  end
58
72
  end
59
73
  end
60
74
  end
61
75
 
62
76
  NON_BUILDABLE_TYPES = %w[ postgres mysql valkey ]
77
+ BUILD_DEFAULTS = {
78
+ dockerfile: "Dockerfile",
79
+ build_context: "."
80
+ }
63
81
  def append_image_tags!
64
- @project.fetch("services", {}).each do |logical_id, service|
65
- next if NON_BUILDABLE_TYPES.include?(service["type"]) || service["image_uri"]
82
+ @project.fetch(:services, {}).each do |logical_id, service|
83
+ next if NON_BUILDABLE_TYPES.include?(service[:type]) || service[:image].present?
84
+ checksums = []
66
85
 
67
- dockerfile = service.fetch("dockerfile", "Dockerfile")
68
- build_context = service.fetch("build_context", ".")
86
+ BUILD_DEFAULTS.each do |attribute, default|
87
+ value = service.fetch(attribute, default)
88
+ checksum = @commit.objects_with_checksum_at value
89
+ if checksum.empty?
90
+ @errors << "Service `#{logical_id}` specifies `#{attribute}` as `#{value}` which doesn't exist in this commit."
91
+ else
92
+ checksums << checksum
93
+ end
94
+ end
69
95
 
70
- @project["services"][logical_id]["image_tag"] = Digest::SHA1.hexdigest [
71
- git_path_checksums_at_commit(dockerfile),
72
- git_path_checksums_at_commit(build_context)
73
- ].to_json
96
+ if checksums.size == 2
97
+ @project[:services][logical_id][:image_tag] = Digest::SHA1.hexdigest checksums.to_json
98
+ end
74
99
  end
75
100
  end
76
101
 
77
- def git_path_checksums_at_commit(path)
78
- `git ls-tree #{@commit.ref} --format='%(objectname) %(path)' #{path}`.
79
- lines.reject { _1.split(" ").last == DIR }
102
+ def merge_environment_overrides!
103
+ return unless services = @project.dig(:environments, @environment.to_sym)
104
+ services.each do |logical_id, service|
105
+ service.each do |attribute, value|
106
+ if value.is_a?(Hash) && @project[:services][logical_id][attribute].is_a?(Hash)
107
+ @project[:services][logical_id][attribute].merge! value
108
+ @project[:services][logical_id][attribute].compact!
109
+ else
110
+ @project[:services][logical_id][attribute] = value
111
+ end
112
+ end
113
+ end
80
114
  end
81
115
  end
@@ -1,71 +1,49 @@
1
1
  class Envirobly::Deployment
2
- URL_MATCHER = /^https:\/\/envirobly\.(test|com)\/(\d+)\/environs\/(\d+)$/
3
-
4
2
  def initialize(environment, options)
5
- @commit = Envirobly::Git::Commit.new options.commit
3
+ commit = Envirobly::Git::Commit.new options.commit
6
4
 
7
- unless @commit.exists?
5
+ unless commit.exists?
8
6
  $stderr.puts "Commit #{options.commit} doesn't exist in this repository. Aborting."
9
7
  exit 1
10
8
  end
11
9
 
12
- config = Envirobly::Config.new(@commit)
13
- if config.parsing_error?
14
- $stderr.puts "Error while parsing #{config.path}"
15
- $stderr.puts config.parsing_error
10
+ config = Envirobly::Config.new(commit)
11
+ config.compile(environment)
12
+
13
+ if config.errors.any?
14
+ $stderr.puts "Errors found while parsing #{Envirobly::Config::PATH}:"
15
+ $stderr.puts
16
+ config.errors.each do |error|
17
+ $stderr.puts " - #{error}"
18
+ end
19
+ $stderr.puts
20
+ $stderr.puts "Please fix these, commit the changes and try again."
16
21
  exit 1
17
22
  end
18
23
 
19
- params = {
20
- environ: {
21
- logical_id: environment
22
- },
23
- commit: {
24
- ref: @commit.ref,
25
- time: @commit.time,
26
- message: @commit.message
27
- },
28
- config: config.to_h
29
- }
24
+ params = config.to_deployment_params
30
25
 
31
26
  puts "Deployment config:"
32
27
  puts params.to_yaml
33
28
 
34
- unless environment =~ URL_MATCHER
35
- if project_url = config.dig("remote", "origin")
36
- params[:environ][:project_url] = project_url
37
- else
38
- $stderr.puts "{remote.origin} is required in .envirobly/project.yml"
39
- exit 1
40
- end
41
- end
29
+ exit if options.dry_run?
42
30
 
43
- @api = Envirobly::Api.new
44
- response = @api.create_deployment params
31
+ api = Envirobly::Api.new
32
+ response = api.create_deployment params
45
33
  deployment_url = response.object.fetch("url")
46
- response = @api.get_deployment_with_delay_and_retry deployment_url
47
- @credentials = Envirobly::Aws::Credentials.new response.object.fetch("credentials")
48
- @bucket = response.object.fetch("bucket")
34
+ response = api.get_deployment_with_delay_and_retry deployment_url
35
+ credentials = Envirobly::Aws::Credentials.new response.object.fetch("credentials")
36
+ bucket = response.object.fetch("bucket")
49
37
 
50
38
  puts "Uploading build context, please wait..."
51
- unless archive_commit_and_upload
39
+ unless commit.archive_and_upload(bucket:, credentials:)
52
40
  $stderr.puts "Error exporting build context. Aborting."
53
41
  exit 1
54
42
  end
55
43
 
56
44
  puts "Build context uploaded."
57
- @api.put_as_json deployment_url
45
+ api.put_as_json deployment_url
58
46
 
59
47
  # TODO: Output URL to watch the deployment progress
60
48
  end
61
-
62
- private
63
- def archive_uri
64
- "s3://#{@bucket}/#{@commit.ref}.tar.gz"
65
- end
66
-
67
- def archive_commit_and_upload
68
- `git archive --format=tar.gz #{@commit.ref} | #{@credentials.as_inline_env_vars} aws s3 cp - #{archive_uri}`
69
- $?.success?
70
- end
71
49
  end
@@ -1,23 +1,57 @@
1
1
  require "time"
2
+ require "open3"
2
3
 
3
4
  class Envirobly::Git::Commit
4
- def initialize(ref)
5
+ def initialize(ref, working_dir: Dir.getwd)
5
6
  @ref = ref
7
+ @working_dir = working_dir
6
8
  end
7
9
 
8
10
  def exists?
9
- `git cat-file -t #{@ref}`.strip == "commit"
11
+ run(%(cat-file -t #{@ref})).strip == "commit"
10
12
  end
11
13
 
12
14
  def ref
13
- @normalized_ref ||= `git rev-parse #{@ref}`.strip
15
+ @normalized_ref ||= run(%(rev-parse #{@ref})).strip
14
16
  end
15
17
 
16
18
  def message
17
- `git log #{@ref} -n1 --pretty=%B`.strip
19
+ run(%(log #{@ref} -n1 --pretty=%B)).strip
18
20
  end
19
21
 
20
22
  def time
21
- Time.parse `git log #{@ref} -n1 --date=iso --pretty=format:"%ad"`
23
+ Time.parse run(%(log #{@ref} -n1 --date=iso --pretty=format:"%ad"))
22
24
  end
25
+
26
+ def file_content(path)
27
+ run %(show #{@ref}:#{path})
28
+ end
29
+
30
+ def objects_with_checksum_at(path)
31
+ run(%{ls-tree #{@ref} --format='%(objectname) %(path)' #{path}}).lines.map(&:chomp).
32
+ reject { _1.split(" ").last == Envirobly::Config::DIR }
33
+ end
34
+
35
+ def archive_and_upload(bucket:, credentials:)
36
+ `GIT_WORK_TREE="#{@working_dir}" GIT_DIR="#{@working_dir}/.git" git archive --format=tar.gz #{ref} | #{credentials.as_inline_env_vars} aws s3 cp - #{archive_uri(bucket)}`
37
+ $?.success?
38
+ end
39
+
40
+ private
41
+ def run(cmd)
42
+ @stdout = @stderr = @exit_code = @success = nil
43
+ full_cmd = %(GIT_WORK_TREE="#{@working_dir}" GIT_DIR="#{@working_dir}/.git" git #{cmd})
44
+ Open3.popen3(full_cmd) do |stdin, stdout, stderr, thread|
45
+ stdin.close
46
+ @stdout = stdout.read
47
+ @stderr = stderr.read
48
+ @exit_code = thread.value.exitstatus
49
+ @success = thread.value.success?
50
+ end
51
+ @stdout
52
+ end
53
+
54
+ def archive_uri(bucket)
55
+ "s3://#{bucket}/#{ref}.tar.gz"
56
+ end
23
57
  end
@@ -1,3 +1,3 @@
1
1
  module Envirobly
2
- VERSION = "0.4.2"
2
+ VERSION = "0.5.0"
3
3
  end
data/lib/envirobly.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  module Envirobly
2
2
  end
3
3
 
4
- # require "active_support"
5
- # require "active_support/core_ext"
6
4
  require "zeitwerk"
5
+ require "core_ext"
7
6
 
8
7
  loader = Zeitwerk::Loader.for_gem
8
+ loader.ignore("#{__dir__}/core_ext.rb")
9
9
  loader.setup
10
10
  loader.eager_load
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: envirobly
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Starsi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-09-16 00:00:00.000000000 Z
11
+ date: 2024-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -39,19 +39,75 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '2.6'
41
41
  - !ruby/object:Gem::Dependency
42
- name: debug
42
+ name: ostruct
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '1.8'
48
- type: :development
47
+ version: 0.1.0
48
+ type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '1.8'
54
+ version: 0.1.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: debug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: activesupport
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: railties
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
55
111
  description:
56
112
  email: klevo@klevo.sk
57
113
  executables:
@@ -61,6 +117,7 @@ extra_rdoc_files: []
61
117
  files:
62
118
  - LICENSE
63
119
  - bin/envirobly
120
+ - lib/core_ext.rb
64
121
  - lib/envirobly.rb
65
122
  - lib/envirobly/access_token.rb
66
123
  - lib/envirobly/api.rb
@@ -93,7 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
93
150
  - !ruby/object:Gem::Version
94
151
  version: '0'
95
152
  requirements: []
96
- rubygems_version: 3.5.14
153
+ rubygems_version: 3.5.18
97
154
  signing_key:
98
155
  specification_version: 4
99
156
  summary: Envirobly command line interface