satorix 0.0.1 → 1.5.3

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.
Files changed (40) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +4 -1
  3. data/.gitlab-ci.yml +45 -0
  4. data/.rspec +2 -1
  5. data/.rubocop.yml +11 -0
  6. data/.ruby-version +1 -0
  7. data/Gemfile +2 -0
  8. data/Gemfile.lock +25 -0
  9. data/Procfile +1 -0
  10. data/README.md +93 -1
  11. data/Rakefile +8 -4
  12. data/bin/console +3 -3
  13. data/bin/satorix +8 -0
  14. data/lib/satorix/CI/deploy/flynn/environment_variables.rb +123 -0
  15. data/lib/satorix/CI/deploy/flynn/resources.rb +79 -0
  16. data/lib/satorix/CI/deploy/flynn/routes.rb +267 -0
  17. data/lib/satorix/CI/deploy/flynn/scale.rb +52 -0
  18. data/lib/satorix/CI/deploy/flynn.rb +132 -0
  19. data/lib/satorix/CI/shared/buildpack_manager/buildpack.rb +159 -0
  20. data/lib/satorix/CI/shared/buildpack_manager.rb +220 -0
  21. data/lib/satorix/CI/shared/ruby/gem_manager.rb +80 -0
  22. data/lib/satorix/CI/shared/yarn_manager.rb +25 -0
  23. data/lib/satorix/CI/test/python/django_test.rb +38 -0
  24. data/lib/satorix/CI/test/python/safety.rb +30 -0
  25. data/lib/satorix/CI/test/ruby/brakeman.rb +35 -0
  26. data/lib/satorix/CI/test/ruby/bundler_audit.rb +35 -0
  27. data/lib/satorix/CI/test/ruby/cucumber.rb +29 -0
  28. data/lib/satorix/CI/test/ruby/rails_test.rb +29 -0
  29. data/lib/satorix/CI/test/ruby/rspec.rb +29 -0
  30. data/lib/satorix/CI/test/ruby/rubocop.rb +98 -0
  31. data/lib/satorix/CI/test/shared/database.rb +74 -0
  32. data/lib/satorix/shared/console.rb +157 -0
  33. data/lib/satorix/version.rb +1 -1
  34. data/lib/satorix.rb +343 -2
  35. data/satorix/CI/deploy/ie_gem_server.rb +80 -0
  36. data/satorix/CI/deploy/rubygems.rb +81 -0
  37. data/satorix/custom.rb +21 -0
  38. data/satorix.gemspec +13 -11
  39. metadata +57 -29
  40. data/.travis.yml +0 -5
@@ -0,0 +1,267 @@
1
+ module Satorix
2
+ module CI
3
+ module Deploy
4
+ module Flynn
5
+ module Routes
6
+
7
+
8
+ def find_or_create_route(domain)
9
+ route_ids = route_ids(domain)
10
+ if route_ids.empty?
11
+ log "Adding route for #{ domain }..."
12
+ route_ids << fc_route_add(domain)
13
+ log "Route for #{ domain } added with the ID of #{ route_ids.first }."
14
+ else
15
+ multiple = route_ids.length > 1
16
+ log "Route#{ 's' if multiple } already exist#{ 's' unless multiple } for #{ domain }."
17
+ end
18
+
19
+ route_ids
20
+ end
21
+
22
+
23
+ def configure_routes
24
+ defined_and_internal_routes.each do |ddev_id, domain|
25
+ route_ids = find_or_create_route(domain)
26
+ route_ids.each do |route_id|
27
+ add_tls_to_route(route_id: route_id, ddev_id: ddev_id)
28
+ end
29
+ end
30
+ end
31
+
32
+
33
+ def add_tls_to_route(route_id:, ddev_id:)
34
+ domain = defined_and_internal_routes[ddev_id]
35
+ if use_tls?(ddev_id)
36
+ if use_lets_encrypt?(ddev_id)
37
+ log "Using Let's Encrypt for #{ domain }."
38
+ log_error_and_abort "Let's Encrypt support is not implemented, yet!"
39
+ else
40
+ log "Using #{ user_defined_tls?(ddev_id) ? 'custom' : 'default' } certificate details for #{ domain } (#{ service_for_route_id(route_id) } service)."
41
+ File.open('crt', 'w') { |f| f.write(crt_for_ddev_id(ddev_id)) }
42
+ File.open('key', 'w') { |f| f.write(key_for_ddev_id(ddev_id)) }
43
+ fc_route_update(route_id)
44
+ end
45
+ else
46
+ log "Skipping TLS configuration for #{ domain }."
47
+ log "Environment variables #{ env_var_crt_prefix }#{ ddev_id } and #{ env_var_key_prefix }#{ ddev_id } have not been specified."
48
+ log 'For more information, please refer to https://www.satorix.com/docs/user/projects#certificates'
49
+ end
50
+ log ''
51
+ end
52
+
53
+
54
+ def canonical_domain
55
+ ENV["AEEV_#{ current_branch }_SATORIX_CANONICAL_URI_HOST"]
56
+ end
57
+
58
+
59
+ def canonical_domain_information
60
+ "\nThe above routes will all be redirected to the default URL:\n\n\t#{ canonical_uri }\n\n" if canonical_uri?
61
+ end
62
+
63
+
64
+ def canonical_domain_protocol
65
+ ENV["AEEV_#{ current_branch }_SATORIX_CANONICAL_URI_PROTOCOL"]
66
+ end
67
+
68
+
69
+ def canonical_uri
70
+ "#{ canonical_domain_protocol }://#{ canonical_domain }" if canonical_uri?
71
+ end
72
+
73
+
74
+ def canonical_uri?
75
+ [canonical_domain, canonical_domain_protocol].all? { |x| x.to_s !~ only_whitespace }
76
+ end
77
+
78
+
79
+ def crt_for_ddev_id(ddev_id)
80
+ ENV["#{ env_var_crt_prefix }#{ user_defined_tls?(ddev_id) ? ddev_id : 'DEFAULT' }"]
81
+ end
82
+
83
+
84
+ def custom_crt_for_ddev_id?(ddev_id)
85
+ ENV["#{ env_var_crt_prefix }#{ ddev_id }"].to_s !~ only_whitespace
86
+ end
87
+
88
+
89
+ def custom_key_for_ddev_id?(ddev_id)
90
+ ENV["#{ env_var_key_prefix }#{ ddev_id }"].to_s !~ only_whitespace
91
+ end
92
+
93
+
94
+ def defined_and_internal_routes
95
+ defined_routes.merge flynn_internal_routes
96
+ end
97
+
98
+
99
+ def defined_routes
100
+ {}.tap do |routes|
101
+ invalid_keys = flynn_internal_routes.keys
102
+ ENV.each do |key, value|
103
+ next unless key.start_with?(env_var_domain_prefix)
104
+ renamed_key = key.sub(env_var_domain_prefix, '')
105
+ message = "The user-defined route #{ key } conflicts with an internal route."
106
+ log_error_and_abort message if invalid_keys.include?(renamed_key)
107
+ routes[renamed_key] = value
108
+ end
109
+ end
110
+ end
111
+
112
+
113
+ def display_routing_information
114
+ log 'All application routes...'
115
+ log ''
116
+ log urlified_routes_for_display.map { |r| "\t#{ r }" }
117
+ log canonical_domain_information if canonical_uri?
118
+ end
119
+
120
+
121
+ def env_var_crt_prefix
122
+ "CRT_#{ current_branch }_"
123
+ end
124
+
125
+
126
+ def env_var_domain_prefix
127
+ "DDEV_#{ current_branch }_"
128
+ end
129
+
130
+
131
+ def env_var_key_prefix
132
+ "KEY_#{ current_branch }_"
133
+ end
134
+
135
+
136
+ def fc_route
137
+ run_command("flynn -a #{ project_name } route", quiet: true).chomp
138
+ end
139
+
140
+
141
+ def fc_route_add(domain)
142
+ run_command("flynn route add http #{ domain } --sticky").chomp
143
+ end
144
+
145
+
146
+ def fc_route_remove(route_id)
147
+ run_command "flynn route remove #{ route_id }"
148
+ end
149
+
150
+
151
+ def fc_route_update(route_id)
152
+ run_command "flynn route update #{ route_id } --tls-cert=crt --tls-key=key --sticky", quiet: true
153
+ end
154
+
155
+
156
+ def flynn_internal_routes
157
+ { 'FLYNN_INTERNAL' => "#{ project_name }.#{ domain_for_web_host }" }
158
+ end
159
+
160
+
161
+ def key_for_ddev_id(ddev_id)
162
+ ENV["#{ env_var_key_prefix }#{ user_defined_tls?(ddev_id) ? ddev_id : 'DEFAULT' }"]
163
+ end
164
+
165
+
166
+ def only_whitespace
167
+ /\A\s*\z/m
168
+ end
169
+
170
+
171
+ def remove_undefined_routes
172
+ routes_to_remove.each do |route|
173
+ log_header "Removing undefined route #{ route[routes_legend['ROUTE']] }..."
174
+ fc_route_remove route[routes_legend['ID']]
175
+ end
176
+ end
177
+
178
+
179
+ def route_ids(route)
180
+ [].tap do |route_ids|
181
+ routes_all_of('ROUTE').map { |r| r.sub(/https??:/i, '') }.each_with_index do |r, i|
182
+ route_ids << routes_all_of('ID')[i] if r == route
183
+ end
184
+ end
185
+ end
186
+
187
+
188
+ def routes
189
+ routes_with_legend.drop(1)
190
+ end
191
+
192
+
193
+ def routes_all_of(field)
194
+ routes.map { |a| a[routes_legend[field]] }
195
+ end
196
+
197
+
198
+ def routes_legend
199
+ {}.tap { |h| routes_with_legend.first.each_with_index { |route, index| h[route] = index } }
200
+ end
201
+
202
+
203
+ def routes_to_remove
204
+ routes.reject do |route|
205
+ defined_and_internal_routes.values.include? route[routes_legend['ROUTE']].partition(':').last
206
+ end
207
+ end
208
+
209
+
210
+ def routes_with_legend
211
+ fc_route.split("\n").map(&:split)
212
+ end
213
+
214
+
215
+ def service_for_route_id(route_id)
216
+ index = routes_all_of('ID').index(route_id)
217
+ routes_all_of('SERVICE')[index]
218
+ end
219
+
220
+
221
+ def setup_routes
222
+ configure_routes
223
+ remove_undefined_routes
224
+ display_routing_information
225
+ end
226
+
227
+
228
+ def urlified_routes
229
+ routes_all_of('ROUTE').map { |r| urlify_route r }
230
+ end
231
+
232
+
233
+ def urlified_routes_for_display
234
+ urlified_routes.map { |route| urlify_route_for_display route }.sort
235
+ end
236
+
237
+
238
+ def urlify_route(route)
239
+ route.sub(':', '://')
240
+ end
241
+
242
+
243
+ def urlify_route_for_display(route)
244
+ route =~ /^http:/ ? route : "#{ route.sub 'https', 'http' }, #{ route }"
245
+ end
246
+
247
+
248
+ def use_lets_encrypt?(ddev_id)
249
+ crt_for_ddev_id(ddev_id).to_s.gsub(/\W+/, '').casecmp('letsencrypt').zero? &&
250
+ key_for_ddev_id(ddev_id).to_s.gsub(/\W+/, '').casecmp('letsencrypt').zero?
251
+ end
252
+
253
+
254
+ def use_tls?(ddev_id)
255
+ [crt_for_ddev_id(ddev_id), key_for_ddev_id(ddev_id)].all? { |x| x.to_s !~ only_whitespace }
256
+ end
257
+
258
+
259
+ def user_defined_tls?(ddev_id)
260
+ custom_crt_for_ddev_id?(ddev_id) && custom_key_for_ddev_id?(ddev_id)
261
+ end
262
+
263
+ end
264
+ end
265
+ end
266
+ end
267
+ end
@@ -0,0 +1,52 @@
1
+ module Satorix
2
+ module CI
3
+ module Deploy
4
+ module Flynn
5
+ module Scale
6
+
7
+
8
+ def adjust_scale
9
+ cached_scale_options_to_set = scale_options_to_set
10
+ log "No scale specified in #{ defined_scale_key }. Displaying current scale:" if cached_scale_options_to_set.empty?
11
+ fc_scale cached_scale_options_to_set
12
+ end
13
+
14
+
15
+ def current_scale
16
+ scale_string_to_hash fc_scale
17
+ end
18
+
19
+
20
+ def defined_scale
21
+ scale_string_to_hash ENV[defined_scale_key].to_s
22
+ end
23
+
24
+
25
+ def defined_scale_key
26
+ "FLYNN_#{ current_branch }_SCALE"
27
+ end
28
+
29
+
30
+ def fc_scale(scale_options_to_set = nil)
31
+ run_command "flynn scale#{ " #{ scale_options_to_set }" unless scale_options_to_set.nil? || scale_options_to_set.empty? }".chomp
32
+ end
33
+
34
+
35
+ def scale_options_to_set
36
+ [].tap do |scale|
37
+ defined_scale.each { |job, workers| scale << "#{ job }=#{ workers }" }
38
+ (current_scale.keys - defined_scale.keys).each { |job| scale << "#{ job }=0" } unless defined_scale.empty?
39
+ end.join(' ')
40
+ end
41
+
42
+
43
+ def scale_string_to_hash(scale_string)
44
+ {}.tap { |jobs| scale_string.split.map { |x| x.partition('=') }.each { |x| jobs[x.first.to_sym] = x.last } }
45
+ end
46
+
47
+
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,132 @@
1
+ module Satorix
2
+ module CI
3
+ module Deploy
4
+ module Flynn
5
+
6
+
7
+ include Satorix
8
+ include Satorix::Shared::Console
9
+
10
+ require_relative 'flynn/environment_variables'
11
+ include EnvironmentVariables
12
+
13
+ require_relative 'flynn/resources'
14
+ include Resources
15
+
16
+ require_relative 'flynn/routes'
17
+ include Routes
18
+
19
+ require_relative 'flynn/scale'
20
+ include Scale
21
+
22
+
23
+ extend self
24
+
25
+
26
+ def go
27
+ log_bench('Adding a local reference to the remote Flynn cluster...') { add_cluster }
28
+ log_bench('Creating Flynn project...') { create_project }
29
+ log_bench('Adding Flynn remote to CI local git...') { add_remote }
30
+ log_bench('Adjusting Flynn environment variables...') { adjust_env_vars }
31
+ log_bench('Configuring Flynn inactive slug release count...') { configure_inactive_slug_releases }
32
+ log_bench('Setting resources...') { set_resources }
33
+ log_bench('Deploying to Flynn...') { deploy }
34
+ log_bench('Running Database migrations...') { run_database_migrations } if run_database_migrations?
35
+ log_bench('Scaling application processes...') { adjust_scale }
36
+ log_bench('Setting up routes...') { setup_routes }
37
+ end
38
+
39
+
40
+ def skip_buildpack
41
+ true
42
+ end
43
+
44
+
45
+ def add_cluster
46
+ run_command "flynn cluster add --force --default --tls-pin=#{ tls_pin } #{ cluster_name } #{ domain_for_cluster } #{ key }", filtered_text: [tls_pin, key]
47
+ end
48
+
49
+
50
+ def add_remote
51
+ run_command "flynn -a #{ project_name } remote add #{ cluster_name } -y"
52
+ end
53
+
54
+
55
+ def cluster_name
56
+ domain_for_cluster
57
+ end
58
+
59
+
60
+ def configure_inactive_slug_releases
61
+ run_command 'flynn meta set gc.max_inactive_slug_releases=2'
62
+ end
63
+
64
+
65
+ def create_project
66
+ if project_exists?
67
+ log "Skipping - Flynn project '#{ project_name }' already exists."
68
+ else
69
+ run_command "flynn create --remote=#{ cluster_name } -y #{ project_name }"
70
+ end
71
+ end
72
+
73
+
74
+ def deploy
75
+ run_command "git push #{ cluster_name } HEAD:refs/heads/master"
76
+ end
77
+
78
+
79
+ def domain_for_cluster
80
+ ENV["FLYNN_#{ current_branch }_DOMAIN"]
81
+ end
82
+
83
+
84
+ def domain_for_web_host
85
+ "#{ current_branch.downcase }.#{ hosting_namespace }"
86
+ end
87
+
88
+
89
+ def hosting_namespace
90
+ if ENV['SATORIX_HOSTING_NAMESPACE'].to_s !~ only_whitespace
91
+ ENV['SATORIX_HOSTING_NAMESPACE']
92
+ else
93
+ log_error_and_abort("Satorix configuration error: Missing SATORIX_HOSTING_NAMESPACE.\n\nPlease contact support.\n")
94
+ end
95
+
96
+ end
97
+
98
+
99
+ def key
100
+ ENV["FLYNN_#{ current_branch }_KEY"]
101
+ end
102
+
103
+
104
+ def project_exists?
105
+ `flynn apps`.split(/\R/).map { |a| a.split.last }.include? project_name
106
+ end
107
+
108
+
109
+ def run_database_migrations?
110
+ [rails_app?, django_app?].any?
111
+ end
112
+
113
+
114
+ def run_database_migrations
115
+ if desired_resource_provider_names.nil? || desired_resource_provider_names.empty?
116
+ log 'Skipping migrations, no database has been defined. Please see the resources section of this log for more information.'
117
+ else
118
+ run_command('flynn run --enable-log bundle exec rake db:migrate') if rails_app?
119
+ run_command('flynn run --enable-log python public/manage.py migrate') if django_app?
120
+ end
121
+ end
122
+
123
+
124
+ def tls_pin
125
+ ENV["FLYNN_#{ current_branch }_TLSPIN"]
126
+ end
127
+
128
+
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,159 @@
1
+ module Satorix
2
+ module CI
3
+ module Shared
4
+ module BuildpackManager
5
+
6
+
7
+ class Buildpack
8
+
9
+
10
+ require 'fileutils'
11
+ require 'uri'
12
+
13
+
14
+ include Satorix::Shared::Console
15
+
16
+
17
+ attr_accessor :commit_sha_short,
18
+ :url
19
+
20
+
21
+ def go
22
+ log_error_and_abort 'Buildpack.go should not be called directly - use BuildpackManager.go.'
23
+ end
24
+
25
+
26
+ def initialize(initialization_url)
27
+ self.url, self.commit_sha_short = initialization_url.to_s.strip.split('#')
28
+ end
29
+
30
+
31
+ def compile
32
+ log_bench "Building application using the #{ name } buildpack..." do
33
+ run_command [compile_binary_path,
34
+ Satorix.build_dir,
35
+ Satorix.paths[:cache],
36
+ Satorix.paths[:env]]
37
+ end
38
+ end
39
+
40
+
41
+ def compile_binary_path
42
+ bin_path = File.join(path, 'bin')
43
+ test_compile_path = File.join(bin_path, 'test-compile')
44
+ compile_path = File.join(bin_path, 'compile')
45
+ File.exist?(test_compile_path) ? test_compile_path : compile_path
46
+ end
47
+
48
+
49
+ def detected?
50
+ if BuildpackManager.custom_buildpacks?
51
+ log "Custom Buildpack: #{ name }."
52
+ else
53
+ command = [File.join(path, 'bin', 'detect'), Satorix.build_dir]
54
+ buildpack_name = run_command(command, quiet: true).chomp
55
+ log "Detected Framework: #{ buildpack_name }."
56
+ end
57
+ true
58
+ rescue SystemExit
59
+ # By design, buildpacks return a non-zero exit code
60
+ # (which raises a SystemExit exception) when not detected.
61
+ # This is not particularly exceptional, so we just return false.
62
+ false
63
+ end
64
+
65
+
66
+ def name
67
+ File.basename(URI.parse(url).path, ".git")
68
+ end
69
+
70
+
71
+ def path
72
+ File.join Satorix.paths[:buildpacks], name
73
+ end
74
+
75
+
76
+ def ensure_correctness
77
+ unless correct_version?
78
+ delete!
79
+ checkout
80
+ end
81
+ end
82
+
83
+
84
+ def delete!
85
+ FileUtils.rm_rf path
86
+ end
87
+
88
+
89
+ def exist?
90
+ Dir.exist? path
91
+ end
92
+
93
+
94
+ def checkout
95
+ log "Downloading Buildpack: #{ url }."
96
+ commit_sha_short ? checkout_specific_version : checkout_newest_version
97
+ end
98
+
99
+
100
+ def checkout_specific_version
101
+ run_command ['git', 'clone', '--quiet', '--no-checkout', url, path], quiet: true
102
+ Dir.chdir(path) { run_command ['git', 'checkout', '--quiet', commit_sha_short], quiet: true }
103
+ Dir.chdir(path) { run_command ['git', 'submodule', 'update', '--init', '--recursive'], quiet: true }
104
+ end
105
+
106
+
107
+ def checkout_newest_version
108
+ run_command ['git', 'clone', '--quiet', '--recursive', '--depth', '1', url, path], quiet: true
109
+ end
110
+
111
+
112
+ def correct_version?
113
+ exist? &&
114
+ commit_sha_short &&
115
+ commit_sha_short == current_commit_sha_short_on_disk
116
+ end
117
+
118
+
119
+ def commit_sha_length
120
+ commit_sha_short ? commit_sha_short.to_s.length : 7
121
+ end
122
+
123
+
124
+ def satorix_distrib_sha_version
125
+ IO.binread(satorix_distrib_sha_path).strip if satorix_distrib_sha?
126
+ end
127
+
128
+
129
+ def satorix_distrib_sha?
130
+ File.exist? satorix_distrib_sha_path
131
+ end
132
+
133
+
134
+ def satorix_distrib_sha_path
135
+ File.join(path, '.distrib-sha')
136
+ end
137
+
138
+
139
+ def git_sha_short
140
+ Dir.chdir(path) do
141
+ # Also works: `git show -s --format=%h`.strip
142
+ run_command(['git', 'rev-parse', "--short=#{ commit_sha_length }", 'HEAD'], quiet: true).strip
143
+ end
144
+ end
145
+
146
+
147
+ def current_commit_sha_short_on_disk
148
+ return '' unless exist?
149
+ satorix_distrib_sha_version || git_sha_short
150
+ end
151
+
152
+
153
+ end
154
+
155
+
156
+ end
157
+ end
158
+ end
159
+ end