satorix 0.0.1 → 1.5.3

Sign up to get free protection for your applications and to get access to all the features.
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