envirobly 0.4.2 → 0.5.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: 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