envirobly 0.5.1 → 0.6.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: ca86599ff3741799e0a3faf9d3386c3c937713b15c8fb063dc6c647879eacfd1
4
- data.tar.gz: d22ece59c009474e6f3bf465e96452eddfc344c344e1a64e4255c88c828d7e12
3
+ metadata.gz: a2750aa4a83c0407fbba1c7db3aa5b7cebaa6ea08fd2112064a4c6c5d88aad4c
4
+ data.tar.gz: 464b4a65da73a81d6a62c1f2fa35f4dd927bd790e16cc3f0f58e3d1048e0d87c
5
5
  SHA512:
6
- metadata.gz: 341a285db112636480ec428f8647ecddc573ce063e64a57e0a79f289bebad4567397c428179d0b109e2e9b443d1e0853f9a4edadbae7280744b42e79381ee834
7
- data.tar.gz: efed0f09a1fa8a33095d4a838c04a8c2657b7673e986796bc5df68f962e181ad7db975678532a31e2f86f2b2b9efe3ba2fb823b0a7582a91bc5adfc37c2d7253
6
+ metadata.gz: '09321732787c09ee02ca9c4071fb916930db737f07dee8a2cd0ffe482620f3f61586a01d796df4a8452b5c7e2b4f6186e8f9ad7ad9feb633f2f5135c5ce7aefb'
7
+ data.tar.gz: 44513ce52f34fef1310f54095589dec1ab05a0233c1a16f77309ebc9c3cbce2cacf1dea70b01a63fafeab26ae7005de6b9ecb46e8d6b25aa6d938d8e939bcc81
@@ -4,6 +4,25 @@ class Envirobly::Cli::Main < Envirobly::Base
4
4
  puts Envirobly::VERSION
5
5
  end
6
6
 
7
+ desc "validate", "Validates config"
8
+ def validate
9
+ commit = Envirobly::Git::Unstaged.new
10
+ config = Envirobly::Config.new(commit)
11
+ config.validate
12
+
13
+ if config.errors.any?
14
+ puts "Issues found validating `#{Envirobly::Config::PATH}`:"
15
+ puts
16
+ config.errors.each_with_index do |error, index|
17
+ puts " #{index + 1}. #{error}"
18
+ end
19
+ puts
20
+ exit 1
21
+ else
22
+ puts "All checks pass."
23
+ end
24
+ end
25
+
7
26
  desc "deploy ENVIRONMENT", "Deploy to environment identified by name or URL"
8
27
  method_option :commit, type: :string, default: "HEAD"
9
28
  method_option :dry_run, type: :boolean, default: false
@@ -15,7 +34,9 @@ class Envirobly::Cli::Main < Envirobly::Base
15
34
  desc "set_access_token TOKEN", "Save and use an access token generated at Envirobly"
16
35
  def set_access_token
17
36
  token = ask("Access Token:", echo: false).strip
18
- if token == ""
37
+
38
+ if token.blank?
39
+ $stderr.puts
19
40
  $stderr.puts "Token can't be empty."
20
41
  exit 1
21
42
  end
@@ -11,9 +11,10 @@ class Envirobly::Config
11
11
  def initialize(commit)
12
12
  @commit = commit
13
13
  @errors = []
14
- @result = nil
14
+ @result = {}
15
15
  @project_url = nil
16
16
  @raw = @commit.file_content PATH
17
+ @project = parse
17
18
  end
18
19
 
19
20
  def dig(*args)
@@ -22,20 +23,27 @@ class Envirobly::Config
22
23
  nil
23
24
  end
24
25
 
26
+ def validate
27
+ return unless @project
28
+ validate_top_level_keys
29
+ validate_services @project.fetch(:services)
30
+ validate_environments
31
+ end
32
+
25
33
  def compile(environment = nil)
34
+ return unless @project
26
35
  @environment = environment
27
- return unless @project = parse
36
+ @result = @project.slice(:services)
28
37
  set_project_url
29
38
  merge_environment_overrides! unless @environment.nil?
30
39
  transform_env_var_values!
31
40
  append_image_tags!
32
- @result = @project.slice(:services)
33
41
  end
34
42
 
35
43
  def to_deployment_params
36
44
  {
37
45
  environ: {
38
- logical_id: @environment,
46
+ name: @environment,
39
47
  project_url: @project_url
40
48
  },
41
49
  commit: {
@@ -57,17 +65,14 @@ class Envirobly::Config
57
65
  end
58
66
 
59
67
  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
68
+ @project_url = dig :project
64
69
  end
65
70
 
66
71
  def transform_env_var_values!
67
- @project.fetch(:services, {}).each do |logical_id, service|
72
+ @result[:services].each do |name, service|
68
73
  service.fetch(:env, {}).each do |key, value|
69
74
  if value.is_a?(Hash) && value.has_key?(:file)
70
- @project[:services][logical_id][:env][key] = @commit.file_content(value.fetch(:file)).strip
75
+ @result[:services][name][:env][key] = @commit.file_content(value.fetch(:file)).strip
71
76
  end
72
77
  end
73
78
  end
@@ -75,41 +80,128 @@ class Envirobly::Config
75
80
 
76
81
  NON_BUILDABLE_TYPES = %w[ postgres mysql valkey ]
77
82
  BUILD_DEFAULTS = {
78
- dockerfile: "Dockerfile",
79
- build_context: "."
83
+ dockerfile: [ "Dockerfile", :file_exists? ],
84
+ build_context: [ ".", :dir_exists? ]
80
85
  }
81
86
  def append_image_tags!
82
- @project.fetch(:services, {}).each do |logical_id, service|
87
+ @result[:services].each do |name, service|
83
88
  next if NON_BUILDABLE_TYPES.include?(service[:type]) || service[:image].present?
84
89
  checksums = []
85
90
 
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
91
+ BUILD_DEFAULTS.each do |attribute, options|
92
+ value = service.fetch(attribute, options.first)
93
+ if @commit.public_send(options.second, value)
94
+ checksums << @commit.objects_with_checksum_at(value)
93
95
  end
94
96
  end
95
97
 
96
98
  if checksums.size == 2
97
- @project[:services][logical_id][:image_tag] = Digest::SHA1.hexdigest checksums.to_json
99
+ @result[:services][name][:image_tag] = Digest::SHA1.hexdigest checksums.to_json
98
100
  end
99
101
  end
100
102
  end
101
103
 
102
104
  def merge_environment_overrides!
103
105
  return unless services = @project.dig(:environments, @environment.to_sym)
104
- services.each do |logical_id, service|
106
+ services.each do |name, service|
105
107
  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!
108
+ if value.is_a?(Hash) && @result[:services][name][attribute].is_a?(Hash)
109
+ @result[:services][name][attribute].merge! value
110
+ @result[:services][name][attribute].compact!
109
111
  else
110
- @project[:services][logical_id][attribute] = value
112
+ @result[:services][name][attribute] = value
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ VALID_TOP_LEVEL_KEYS = %i[ project services environments ]
119
+ def validate_top_level_keys
120
+ unless @project.is_a?(Hash)
121
+ @errors << "Config doesn't contain a top level hash structure."
122
+ return
123
+ end
124
+
125
+ @errors << "Missing `project: <url>` top level attribute." if @project[:project].blank?
126
+
127
+ @project.keys.each do |key|
128
+ unless VALID_TOP_LEVEL_KEYS.include?(key)
129
+ @errors << "Top level key `#{key}` is not allowed. Allowed keys: #{VALID_TOP_LEVEL_KEYS.map{ "`#{_1}`" }.join(", ")}."
130
+ end
131
+ end
132
+ end
133
+
134
+ VALID_SERVICE_KEYS = %i[
135
+ type
136
+ image
137
+ engine_version
138
+ instance_type
139
+ volume_size
140
+ volume_mount
141
+ dockerfile
142
+ build_context
143
+ command
144
+ env
145
+ health_check
146
+ private
147
+ aliases
148
+ ]
149
+ NAME_FORMAT = /\A[a-z0-9\-_]+\z/
150
+ def validate_services(services)
151
+ unless services.is_a?(Hash)
152
+ @errors << "`services` key must be a hash."
153
+ return
154
+ end
155
+
156
+ services.each do |name, service|
157
+ unless name =~ NAME_FORMAT
158
+ @errors << "`#{name}` is not a valid service name. Allowed characters: a-z, 0-9, -, _"
159
+ end
160
+
161
+ unless service.is_a?(Hash)
162
+ @errors << "Service `#{name}` must be a hash."
163
+ next
164
+ end
165
+
166
+ service.each do |attribute, value|
167
+ unless VALID_SERVICE_KEYS.include?(attribute)
168
+ @errors << "Service `#{name}` attribute `#{attribute}` is not a valid attribute."
111
169
  end
112
170
  end
171
+
172
+ BUILD_DEFAULTS.each do |attribute, options|
173
+ value = service.fetch(attribute, options.first)
174
+ unless @commit.public_send(options.second, value)
175
+ @errors << "Service `#{name}` specifies `#{attribute}` as `#{value}` which doesn't exist in this commit."
176
+ end
177
+ end
178
+
179
+ service.fetch(:env, {}).each do |key, value|
180
+ if value.is_a?(Hash) && value.has_key?(:file)
181
+ unless @commit.file_exists?(value.fetch(:file))
182
+ @errors << "Environment variable `#{key}` referring to a file `#{value.fetch(:file)}` doesn't exist in this commit."
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
188
+
189
+ def validate_environments
190
+ return unless @project.has_key?(:environments)
191
+
192
+ environments = @project.fetch :environments, nil
193
+
194
+ unless environments.is_a?(Hash)
195
+ @errors << "`environments` key must be a hash."
196
+ return
197
+ end
198
+
199
+ environments.each do |environment, services|
200
+ unless environment =~ NAME_FORMAT
201
+ @errors << "`#{environment}` is not a valid environment name. Allowed characters: a-z, 0-9, -, _"
202
+ end
203
+
204
+ validate_services services
113
205
  end
114
206
  end
115
207
  end
@@ -8,7 +8,7 @@ class Envirobly::Deployment
8
8
  end
9
9
 
10
10
  config = Envirobly::Config.new(commit)
11
- config.compile(environment)
11
+ config.validate
12
12
 
13
13
  if config.errors.any?
14
14
  $stderr.puts "Errors found while parsing #{Envirobly::Config::PATH}:"
@@ -21,6 +21,7 @@ class Envirobly::Deployment
21
21
  exit 1
22
22
  end
23
23
 
24
+ config.compile(environment)
24
25
  params = config.to_deployment_params
25
26
 
26
27
  puts "Deployment config:"
@@ -23,6 +23,15 @@ class Envirobly::Git::Commit
23
23
  Time.parse git(%(log #{@ref} -n1 --date=iso --pretty=format:"%ad")).stdout
24
24
  end
25
25
 
26
+ def file_exists?(path)
27
+ git(%(cat-file -t #{@ref}:#{path})).stdout.strip == "blob"
28
+ end
29
+
30
+ def dir_exists?(path)
31
+ suffix = path.end_with?("/") ? nil : "/"
32
+ git(%(cat-file -t #{@ref}:#{path}#{suffix})).stdout.strip == "tree"
33
+ end
34
+
26
35
  def file_content(path)
27
36
  git(%(show #{@ref}:#{path})).stdout
28
37
  end
@@ -0,0 +1,17 @@
1
+ class Envirobly::Git::Unstaged < Envirobly::Git::Commit
2
+ def initialize(working_dir: Dir.getwd)
3
+ @working_dir = working_dir
4
+ end
5
+
6
+ def file_exists?(path)
7
+ File.exist? path
8
+ end
9
+
10
+ def dir_exists?(path)
11
+ Dir.exist? path
12
+ end
13
+
14
+ def file_content(path)
15
+ File.read path
16
+ end
17
+ end
@@ -1,3 +1,3 @@
1
1
  module Envirobly
2
- VERSION = "0.5.1"
2
+ VERSION = "0.6.0"
3
3
  end
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.5.1
4
+ version: 0.6.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-19 00:00:00.000000000 Z
11
+ date: 2024-09-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -130,6 +130,7 @@ files:
130
130
  - lib/envirobly/deployment.rb
131
131
  - lib/envirobly/git.rb
132
132
  - lib/envirobly/git/commit.rb
133
+ - lib/envirobly/git/unstaged.rb
133
134
  - lib/envirobly/version.rb
134
135
  homepage: https://github.com/envirobly/envirobly-cli
135
136
  licenses: