convox_installer 2.0.0 → 3.0.1
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 +4 -4
- data/.rubocop.yml +1 -1
- data/.vscode/settings.json +3 -2
- data/Gemfile +1 -0
- data/README.md +110 -42
- data/examples/full_installation.rb +140 -46
- data/lib/convox/client.rb +196 -180
- data/lib/convox_installer/config.rb +8 -0
- data/lib/convox_installer/requirements.rb +15 -4
- data/lib/convox_installer/version.rb +1 -1
- data/lib/convox_installer.rb +10 -4
- data/spec/lib/convox/client_spec.rb +7 -7
- data/spec/lib/convox_installer/requirements_spec.rb +5 -6
- data/terraform/elasticache.tf.erb +46 -0
- data/terraform/rds.tf.erb +45 -0
- data/terraform/s3_bucket.tf.erb +73 -0
- metadata +6 -3
data/lib/convox/client.rb
CHANGED
@@ -4,12 +4,20 @@ require 'logger'
|
|
4
4
|
require 'json'
|
5
5
|
require 'fileutils'
|
6
6
|
require 'rubygems'
|
7
|
+
require 'os'
|
8
|
+
require 'erb'
|
7
9
|
|
8
10
|
module Convox
|
9
11
|
class Client
|
10
|
-
|
11
|
-
|
12
|
-
|
12
|
+
CONVOX_CONFIG_DIR = if OS.mac?
|
13
|
+
# Convox v3 moved this to ~/Library/Preferences/convox/ on Mac
|
14
|
+
File.expand_path('~/Library/Preferences/convox').freeze
|
15
|
+
else
|
16
|
+
File.expand_path('~/.convox').freeze
|
17
|
+
end
|
18
|
+
|
19
|
+
AUTH_FILE = File.join(CONVOX_CONFIG_DIR, 'auth')
|
20
|
+
CURRENT_FILE = File.join(CONVOX_CONFIG_DIR, 'current')
|
13
21
|
|
14
22
|
attr_accessor :logger, :config
|
15
23
|
|
@@ -57,17 +65,21 @@ module Convox
|
|
57
65
|
@config = options[:config] || {}
|
58
66
|
end
|
59
67
|
|
68
|
+
# Convox v3 creates a folder for each rack for the Terraform config
|
69
|
+
def rack_dir
|
70
|
+
stack_name = config.fetch(:stack_name)
|
71
|
+
File.join(CONVOX_CONFIG_DIR, 'racks', stack_name)
|
72
|
+
end
|
73
|
+
|
60
74
|
def backup_convox_host_and_rack
|
61
|
-
FileUtils.mkdir_p
|
75
|
+
FileUtils.mkdir_p CONVOX_CONFIG_DIR
|
62
76
|
|
63
|
-
|
64
|
-
|
65
|
-
next unless File.exist?(path)
|
77
|
+
path = File.join(CONVOX_CONFIG_DIR, 'current')
|
78
|
+
return unless File.exist?(path)
|
66
79
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
80
|
+
bak_file = "#{path}.bak"
|
81
|
+
logger.info "Moving existing #{path} to #{bak_file}..."
|
82
|
+
FileUtils.mv(path, bak_file)
|
71
83
|
end
|
72
84
|
|
73
85
|
def install_convox
|
@@ -76,8 +88,12 @@ module Convox
|
|
76
88
|
stack_name = config.fetch(:stack_name)
|
77
89
|
|
78
90
|
if rack_already_installed?
|
79
|
-
logger.info "There is already a Convox
|
80
|
-
|
91
|
+
logger.info "There is already a Convox rack named #{stack_name}. Using this rack."
|
92
|
+
logger.debug 'If you need to start over, you can run: ' \
|
93
|
+
"convox rack uninstall #{stack_name} " \
|
94
|
+
'(Make sure you export AWS_ACCESS_KEY_ID and ' \
|
95
|
+
"AWS_SECRET_ACCESS_KEY first.)\n" \
|
96
|
+
"If this fails, you can try deleting the rack directory: rm -rf #{rack_dir}"
|
81
97
|
return true
|
82
98
|
end
|
83
99
|
|
@@ -96,12 +112,22 @@ module Convox
|
|
96
112
|
'AWS_ACCESS_KEY_ID' => config.fetch(:aws_access_key_id),
|
97
113
|
'AWS_SECRET_ACCESS_KEY' => config.fetch(:aws_secret_access_key)
|
98
114
|
}
|
115
|
+
# Set proxy_protocol=true by default to forward client IPs
|
99
116
|
command = %(rack install aws \
|
100
|
-
|
101
|
-
"
|
102
|
-
"
|
117
|
+
"#{config.fetch(:stack_name)}" \
|
118
|
+
"node_type=#{config.fetch(:instance_type)}" \
|
119
|
+
"proxy_protocol=true" \
|
120
|
+
"region=#{config.fetch(:aws_region)}")
|
121
|
+
# us-east constantly has problems with the us-east-1c AZ:
|
122
|
+
# "Cannot create cluster 'ds-enterprise-cx3' because us-east-1c, the targeted
|
123
|
+
# availability zone, does not currently have sufficient capacity to support the cluster.
|
124
|
+
# Retry and choose from these availability zones:
|
125
|
+
# us-east-1a, us-east-1b, us-east-1d, us-east-1e, us-east-1f
|
126
|
+
if config.fetch(:aws_region) == 'us-east-1'
|
127
|
+
command += ' "availability_zones=us-east-1a,us-east-1b,us-east-1d,us-east-1e,us-east-1f"'
|
128
|
+
end
|
103
129
|
|
104
|
-
run_convox_command!(command, env)
|
130
|
+
run_convox_command!(command, env, rack_arg: false)
|
105
131
|
end
|
106
132
|
|
107
133
|
def rack_already_installed?
|
@@ -109,66 +135,51 @@ module Convox
|
|
109
135
|
|
110
136
|
return unless File.exist?(AUTH_FILE)
|
111
137
|
|
112
|
-
region = config.fetch(:aws_region)
|
138
|
+
# region = config.fetch(:aws_region)
|
113
139
|
stack_name = config.fetch(:stack_name)
|
140
|
+
return true if File.exist?(rack_dir)
|
114
141
|
|
115
|
-
auth.each do |
|
116
|
-
if
|
117
|
-
return true
|
118
|
-
end
|
142
|
+
auth.each do |rack_name, _password|
|
143
|
+
return true if rack_name == stack_name
|
119
144
|
end
|
120
145
|
false
|
121
146
|
end
|
122
147
|
|
123
|
-
|
148
|
+
# Auth for a detached rack is not saved in the auth file anymore.
|
149
|
+
# It can be found in the terraform state:
|
150
|
+
# ~/Library/Preferences/convox/racks/ds-enterprise-cx3/terraform.tfstate
|
151
|
+
# Under outputs/api/value. The API URL contains the convox username and API token as basic auth.
|
152
|
+
def validate_convox_rack_and_write_current!
|
124
153
|
require_config(%i[aws_region stack_name])
|
125
154
|
|
126
|
-
unless
|
127
|
-
raise "Could not find
|
155
|
+
unless rack_already_installed?
|
156
|
+
raise "Could not find rack terraform directory at: #{rack_dir}"
|
128
157
|
end
|
129
158
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
matching_host = nil
|
135
|
-
auth.each do |host, _password|
|
136
|
-
if host.match?(/^#{stack}-\d+\.#{region}\.elb\.amazonaws\.com$/)
|
137
|
-
matching_host = host
|
138
|
-
match_count += 1
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
if match_count == 1
|
143
|
-
write_host(matching_host)
|
144
|
-
return matching_host
|
145
|
-
end
|
146
|
-
|
147
|
-
error_message = if match_count > 1
|
148
|
-
'Found multiple matching hosts for '
|
149
|
-
else
|
150
|
-
'Could not find matching authentication for '
|
151
|
-
end
|
152
|
-
error_message += "region: #{region}, stack: #{stack}"
|
153
|
-
raise error_message
|
159
|
+
# Tells the Convox CLI to use our terraform stack
|
160
|
+
stack_name = config.fetch(:stack_name)
|
161
|
+
write_current(stack_name)
|
162
|
+
stack_name
|
154
163
|
end
|
155
164
|
|
156
|
-
def
|
157
|
-
logger.debug "Setting convox
|
158
|
-
|
165
|
+
def write_current(rack_name)
|
166
|
+
logger.debug "Setting convox rack to #{rack_name} (in #{CURRENT_FILE})..."
|
167
|
+
current_hash = { name: rack_name, type: 'terraform' }
|
168
|
+
File.open(CURRENT_FILE, 'w') { |f| f.puts current_hash.to_json }
|
159
169
|
end
|
160
170
|
|
161
|
-
def
|
171
|
+
def validate_convox_rack_api!
|
162
172
|
require_config(%i[
|
163
173
|
aws_region
|
164
174
|
stack_name
|
165
175
|
instance_type
|
166
176
|
])
|
167
177
|
logger.debug 'Validating that convox rack has the correct attributes...'
|
178
|
+
# Convox 3 racks no longer return info about region or type. (These are blank strings.)
|
168
179
|
{
|
169
180
|
provider: 'aws',
|
170
|
-
region: config.fetch(:aws_region),
|
171
|
-
type: config.fetch(:instance_type),
|
181
|
+
# region: config.fetch(:aws_region),
|
182
|
+
# type: config.fetch(:instance_type),
|
172
183
|
name: config.fetch(:stack_name)
|
173
184
|
}.each do |k, v|
|
174
185
|
convox_value = convox_rack_data[k.to_s]
|
@@ -184,8 +195,22 @@ module Convox
|
|
184
195
|
def convox_rack_data
|
185
196
|
@convox_rack_data ||= begin
|
186
197
|
logger.debug 'Fetching convox rack attributes...'
|
187
|
-
|
188
|
-
|
198
|
+
command = "convox api get /system --rack #{config.fetch(:stack_name)}"
|
199
|
+
logger.debug "+ #{command}"
|
200
|
+
# It can take a while for the API to be ready.
|
201
|
+
start_time = Time.now
|
202
|
+
convox_output = nil
|
203
|
+
loop do
|
204
|
+
convox_output = `#{command}`
|
205
|
+
break if $CHILD_STATUS.success?
|
206
|
+
|
207
|
+
if Time.now - start_time > 360
|
208
|
+
raise 'Could not connect to Convox rack API!'
|
209
|
+
end
|
210
|
+
|
211
|
+
logger.debug 'Waiting for Convox rack API to be ready... (can take a few minutes)'
|
212
|
+
sleep 5
|
213
|
+
end
|
189
214
|
|
190
215
|
JSON.parse(convox_output)
|
191
216
|
end
|
@@ -199,9 +224,10 @@ module Convox
|
|
199
224
|
|
200
225
|
logger.info "Creating app: #{app_name}..."
|
201
226
|
logger.info '=> Documentation: ' \
|
202
|
-
'https://docs.convox.com/
|
227
|
+
'https://docs.convox.com/reference/cli/apps/'
|
203
228
|
|
204
|
-
|
229
|
+
# NOTE: --wait flags were removed in Convox 3. It now waits by default.
|
230
|
+
run_convox_command! "apps create #{app_name}"
|
205
231
|
|
206
232
|
retries = 0
|
207
233
|
loop do
|
@@ -232,7 +258,7 @@ module Convox
|
|
232
258
|
app_name = config.fetch(:convox_app_name)
|
233
259
|
|
234
260
|
logger.debug "Looking for existing #{app_name} app..."
|
235
|
-
convox_output = `convox api get /apps`
|
261
|
+
convox_output = `convox api get /apps --rack #{config.fetch(:stack_name)}`
|
236
262
|
raise 'convox command failed!' unless $CHILD_STATUS.success?
|
237
263
|
|
238
264
|
apps = JSON.parse(convox_output)
|
@@ -247,128 +273,117 @@ module Convox
|
|
247
273
|
end
|
248
274
|
|
249
275
|
# Create the s3 bucket, and also apply a CORS configuration
|
250
|
-
|
276
|
+
# Convox v3 update - They removed support for S3 resources, so we have to do
|
277
|
+
# in terraform now (which is actually pretty nice!)
|
278
|
+
def add_s3_bucket
|
251
279
|
require_config(%i[s3_bucket_name])
|
252
|
-
bucket_name = config.fetch(:s3_bucket_name)
|
253
|
-
if s3_bucket_exists?
|
254
|
-
logger.info "#{bucket_name} S3 bucket already exists!"
|
255
|
-
else
|
256
|
-
logger.info "Creating S3 bucket resource (#{bucket_name})..."
|
257
|
-
run_convox_command! 'rack resources create s3 ' \
|
258
|
-
"--name \"#{bucket_name}\" " \
|
259
|
-
'--wait'
|
260
|
-
|
261
|
-
retries = 0
|
262
|
-
loop do
|
263
|
-
break if s3_bucket_exists?
|
264
280
|
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
end
|
269
|
-
logger.debug 'Waiting for S3 bucket to be ready...'
|
270
|
-
sleep 3
|
271
|
-
retries += 1
|
272
|
-
end
|
273
|
-
|
274
|
-
logger.debug '=> S3 bucket created!'
|
281
|
+
unless config.key? :s3_bucket_cors_rule
|
282
|
+
logger.debug 'No CORS rule provided in config: s3_bucket_cors_rule (optional)'
|
283
|
+
return
|
275
284
|
end
|
276
285
|
|
277
|
-
|
286
|
+
write_terraform_template('s3_bucket')
|
278
287
|
end
|
279
288
|
|
280
|
-
def
|
281
|
-
require_config(%i[
|
282
|
-
|
283
|
-
logger.debug "Looking up S3 bucket resource: #{bucket_name}"
|
284
|
-
`convox api get /resources/#{bucket_name} 2>/dev/null`
|
285
|
-
$CHILD_STATUS.success?
|
289
|
+
def add_rds_database
|
290
|
+
require_config(%i[database_username database_password])
|
291
|
+
write_terraform_template('rds')
|
286
292
|
end
|
287
293
|
|
288
|
-
def
|
289
|
-
|
290
|
-
|
291
|
-
bucket_name = config.fetch(:s3_bucket_name)
|
292
|
-
logger.debug "Fetching S3 bucket resource details for #{bucket_name}..."
|
293
|
-
|
294
|
-
response = `convox api get /resources/#{bucket_name}`
|
295
|
-
raise 'convox command failed!' unless $CHILD_STATUS.success?
|
296
|
-
|
297
|
-
bucket_data = JSON.parse(response)
|
298
|
-
s3_url = bucket_data['url']
|
299
|
-
matches = s3_url.match(
|
300
|
-
%r{^s3://(?<access_key_id>[^:]*):(?<secret_access_key>[^@]*)@(?<bucket_name>.*)$}
|
301
|
-
)
|
302
|
-
|
303
|
-
match_keys = %i[access_key_id secret_access_key bucket_name]
|
304
|
-
unless matches && match_keys.all? { |k| matches[k].present? }
|
305
|
-
raise "#{s3_url} is an invalid S3 URL!"
|
306
|
-
end
|
294
|
+
def add_elasticache_cluster
|
295
|
+
write_terraform_template('elasticache')
|
296
|
+
end
|
307
297
|
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
}
|
298
|
+
def write_terraform_template(name)
|
299
|
+
template_path = File.join(__dir__, "../../terraform/#{name}.tf.erb")
|
300
|
+
unless File.exist?(template_path)
|
301
|
+
raise "Could not find terraform template at: #{template_path}"
|
313
302
|
end
|
303
|
+
|
304
|
+
template = ERB.new(File.read(template_path))
|
305
|
+
template_output = template.result(binding)
|
306
|
+
|
307
|
+
tf_file_path = File.join(rack_dir, "#{name}.tf")
|
308
|
+
logger.debug "Writing terraform config to #{tf_file_path}..."
|
309
|
+
File.open(tf_file_path, 'w') { |f| f.puts template_output }
|
314
310
|
end
|
315
311
|
|
316
|
-
def
|
317
|
-
|
318
|
-
|
319
|
-
|
312
|
+
def apply_terraform_update!
|
313
|
+
logger.info 'Applying terraform update...'
|
314
|
+
command = if ENV['DEBUG_TERRAFORM']
|
315
|
+
'terraform plan'
|
316
|
+
else
|
317
|
+
'terraform apply -auto-approve'
|
318
|
+
end
|
319
|
+
logger.debug "+ #{command}"
|
320
320
|
|
321
|
-
|
322
|
-
|
323
|
-
|
321
|
+
env = {
|
322
|
+
'AWS_ACCESS_KEY_ID' => config.fetch(:aws_access_key_id),
|
323
|
+
'AWS_SECRET_ACCESS_KEY' => config.fetch(:aws_secret_access_key)
|
324
|
+
}
|
325
|
+
Dir.chdir(rack_dir) do
|
326
|
+
system env, command
|
327
|
+
raise 'terraform command failed!' unless $CHILD_STATUS.success?
|
324
328
|
end
|
325
|
-
|
326
|
-
|
327
|
-
bucket_name = s3_bucket_details[:name]
|
328
|
-
|
329
|
-
logger.debug "Looking up existing CORS policy for #{bucket_name}"
|
330
|
-
existing_cors_policy_string =
|
331
|
-
`AWS_ACCESS_KEY_ID=#{access_key_id} \
|
332
|
-
AWS_SECRET_ACCESS_KEY=#{secret_access_key} \
|
333
|
-
aws s3api get-bucket-cors --bucket #{bucket_name} 2>/dev/null`
|
334
|
-
if $CHILD_STATUS.success? && existing_cors_policy_string.present?
|
335
|
-
# Sort all the nested arrays so that the equality operator works
|
336
|
-
existing_cors_policy = JSON.parse(existing_cors_policy_string)
|
337
|
-
cors_policy_json = JSON.parse(cors_policy_string)
|
338
|
-
[existing_cors_policy, cors_policy_json].each do |policy_json|
|
339
|
-
next unless policy_json.is_a?(Hash) && policy_json['CORSRules']
|
340
|
-
|
341
|
-
policy_json['CORSRules'].each do |rule|
|
342
|
-
rule['AllowedHeaders']&.sort!
|
343
|
-
rule['AllowedMethods']&.sort!
|
344
|
-
rule['AllowedOrigins']&.sort!
|
345
|
-
end
|
346
|
-
end
|
329
|
+
end
|
347
330
|
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
331
|
+
def terraform_state
|
332
|
+
tf_state_file = File.join(rack_dir, 'terraform.tfstate')
|
333
|
+
JSON.parse(File.read(tf_state_file))
|
334
|
+
end
|
335
|
+
|
336
|
+
def terraform_resource(resource_type, resource_name)
|
337
|
+
resource = terraform_state['resources'].find do |resource|
|
338
|
+
resource['type'] == resource_type && resource['name'] == resource_name
|
352
339
|
end
|
340
|
+
return resource if resource
|
353
341
|
|
354
|
-
|
355
|
-
|
342
|
+
raise "Could not find #{resource_type} resource named #{resource_name} in terraform state!"
|
343
|
+
end
|
356
344
|
|
357
|
-
|
345
|
+
def s3_bucket_details
|
346
|
+
require_config(%i[s3_bucket_name])
|
358
347
|
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
--cors-configuration "file://cors-policy.json"`
|
364
|
-
unless $CHILD_STATUS.success?
|
365
|
-
raise 'Something went wrong while setting the S3 bucket CORS policy!'
|
366
|
-
end
|
348
|
+
s3_bucket = terraform_resource('aws_s3_bucket', 'docs_s3_bucket')
|
349
|
+
bucket_attributes = s3_bucket['instances'][0]['attributes']
|
350
|
+
access_key = terraform_resource('aws_iam_access_key', 'docspring_user_access_key')
|
351
|
+
key_attributes = access_key['instances'][0]['attributes']
|
367
352
|
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
353
|
+
{
|
354
|
+
access_key_id: key_attributes['id'],
|
355
|
+
secret_access_key: key_attributes['secret'],
|
356
|
+
name: bucket_attributes['bucket']
|
357
|
+
}
|
358
|
+
end
|
359
|
+
|
360
|
+
def rds_details
|
361
|
+
require_config(%i[database_username database_password])
|
362
|
+
|
363
|
+
database = terraform_resource('aws_db_instance', 'rds_database')
|
364
|
+
database_attributes = database['instances'][0]['attributes']
|
365
|
+
|
366
|
+
username = database_attributes['username']
|
367
|
+
password = database_attributes['password']
|
368
|
+
endpoint = database_attributes['endpoint']
|
369
|
+
postgres_url = "postgres://#{username}:#{password}@#{endpoint}/app"
|
370
|
+
{
|
371
|
+
postgres_url: postgres_url
|
372
|
+
}
|
373
|
+
end
|
374
|
+
|
375
|
+
def elasticache_details
|
376
|
+
require_config(%i[s3_bucket_name])
|
377
|
+
|
378
|
+
# Just ensure that the bucket exists in the state
|
379
|
+
cluster = terraform_resource('aws_elasticache_cluster', 'elasticache_cluster')
|
380
|
+
cluster_attributes = cluster['instances'][0]['attributes']
|
381
|
+
cache_node = cluster_attributes['cache_nodes'][0]
|
382
|
+
redis_url = "redis://#{cache_node['address']}:#{cache_node['port']}/0"
|
383
|
+
|
384
|
+
{
|
385
|
+
redis_url: redis_url
|
386
|
+
}
|
372
387
|
end
|
373
388
|
|
374
389
|
def add_docker_registry!
|
@@ -377,7 +392,7 @@ module Convox
|
|
377
392
|
registry_url = config.fetch(:docker_registry_url)
|
378
393
|
|
379
394
|
logger.debug 'Looking up existing Docker registries...'
|
380
|
-
registries_response = `convox api get /registries`
|
395
|
+
registries_response = `convox api get /registries --rack #{config.fetch(:stack_name)}`
|
381
396
|
unless $CHILD_STATUS.success?
|
382
397
|
raise 'Something went wrong while fetching the list of registries!'
|
383
398
|
end
|
@@ -391,36 +406,37 @@ module Convox
|
|
391
406
|
|
392
407
|
logger.info "Adding Docker Registry: #{registry_url}..."
|
393
408
|
logger.info '=> Documentation: ' \
|
394
|
-
'https://docs.convox.com/
|
409
|
+
'https://docs.convox.com/configuration/private-registries/'
|
395
410
|
|
396
411
|
`convox registries add "#{registry_url}" \
|
397
412
|
"#{config.fetch(:docker_registry_username)}" \
|
398
|
-
"#{config.fetch(:docker_registry_password)}"
|
413
|
+
"#{config.fetch(:docker_registry_password)}" \
|
414
|
+
--rack #{config.fetch(:stack_name)}`
|
399
415
|
return if $CHILD_STATUS.success?
|
400
416
|
|
401
417
|
raise "Something went wrong while adding the #{registry_url} registry!"
|
402
418
|
end
|
403
419
|
|
404
420
|
def default_service_domain_name
|
405
|
-
require_config(%i[convox_app_name
|
406
|
-
|
407
|
-
@default_service_domain_name ||= begin
|
408
|
-
convox_domain = convox_rack_data['domain']
|
409
|
-
elb_name_and_region = convox_domain[/([^.]*\.[^.]*)\..*/, 1]
|
410
|
-
unless elb_name_and_region.present?
|
411
|
-
raise 'Something went wrong while parsing the ELB name and region! ' \
|
412
|
-
"(#{elb_name_and_region})"
|
413
|
-
end
|
414
|
-
app = config.fetch(:convox_app_name)
|
415
|
-
service = config.fetch(:default_service)
|
421
|
+
require_config(%i[convox_app_name])
|
416
422
|
|
417
|
-
|
418
|
-
|
419
|
-
|
423
|
+
app_name = config.fetch(:convox_app_name)
|
424
|
+
default_service = config[:default_service] || 'web'
|
425
|
+
|
426
|
+
convox_api_url = terraform_state['outputs']['api']['value']
|
427
|
+
convox_router_host = convox_api_url.split('@').last.sub(/^api\./, '')
|
428
|
+
|
429
|
+
[default_service, app_name, convox_router_host].join('.').downcase
|
420
430
|
end
|
421
431
|
|
422
|
-
def run_convox_command!(cmd, env = {})
|
432
|
+
def run_convox_command!(cmd, env = {}, rack_arg: true)
|
433
|
+
# Always include the rack as an argument, to
|
434
|
+
# make sure that 'convox switch' doesn't affect any commands
|
423
435
|
command = "convox #{cmd}"
|
436
|
+
if rack_arg
|
437
|
+
command = "#{command} --rack #{config.fetch(:stack_name)}"
|
438
|
+
end
|
439
|
+
logger.debug "+ #{command}"
|
424
440
|
system env, command
|
425
441
|
raise "Error running: #{command}" unless $CHILD_STATUS.success?
|
426
442
|
end
|
@@ -38,6 +38,12 @@ module ConvoxInstaller
|
|
38
38
|
{
|
39
39
|
key: :aws_secret_access_key,
|
40
40
|
title: 'AWS Secret Access Key'
|
41
|
+
},
|
42
|
+
# Short random ID used to ensure that resources are always unique
|
43
|
+
{
|
44
|
+
key: :random_id,
|
45
|
+
value: -> { SecureRandom.hex(4) },
|
46
|
+
hidden: true
|
41
47
|
}
|
42
48
|
].freeze
|
43
49
|
|
@@ -76,6 +82,8 @@ module ConvoxInstaller
|
|
76
82
|
|
77
83
|
highline.say 'Please double check all of these configuration details.'
|
78
84
|
|
85
|
+
break if ENV['AUTOSTART_CONVOX_INSTALLATION']
|
86
|
+
|
79
87
|
agree = highline.agree(
|
80
88
|
'Would you like to start the Convox installation?' \
|
81
89
|
" (press 'n' to correct any settings)"
|
@@ -34,6 +34,14 @@ module ConvoxInstaller
|
|
34
34
|
}
|
35
35
|
end
|
36
36
|
|
37
|
+
unless command_present? 'terraform'
|
38
|
+
@missing_packages << {
|
39
|
+
name: 'terraform',
|
40
|
+
brew: 'terraform',
|
41
|
+
docs: 'https://learn.hashicorp.com/tutorials/terraform/install-cli'
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
37
45
|
if @missing_packages.any?
|
38
46
|
logger.error 'This script requires the convox and AWS CLI tools.'
|
39
47
|
if OS.mac?
|
@@ -49,12 +57,15 @@ module ConvoxInstaller
|
|
49
57
|
end
|
50
58
|
|
51
59
|
client = Convox::Client.new
|
52
|
-
|
60
|
+
if client.convox_3_cli?
|
61
|
+
logger.debug "=> Convox CLI is version 3.x.x (#{client.cli_version_string})"
|
62
|
+
return
|
63
|
+
end
|
53
64
|
|
54
|
-
logger.error 'This script requires Convox CLI version
|
65
|
+
logger.error 'This script requires Convox CLI version 3.x.x. ' \
|
55
66
|
"Your Convox CLI version is: #{client.cli_version_string}"
|
56
|
-
logger.error
|
57
|
-
'
|
67
|
+
logger.error "Please run 'brew update convox' or follow the instructions " \
|
68
|
+
'at https://docs.convox.com/getting-started/introduction'
|
58
69
|
quit!
|
59
70
|
end
|
60
71
|
|
data/lib/convox_installer.rb
CHANGED
@@ -32,18 +32,24 @@ module ConvoxInstaller
|
|
32
32
|
%w[
|
33
33
|
backup_convox_host_and_rack
|
34
34
|
install_convox
|
35
|
-
|
36
|
-
|
35
|
+
validate_convox_rack_and_write_current!
|
36
|
+
validate_convox_rack_api!
|
37
37
|
convox_rack_data
|
38
38
|
create_convox_app!
|
39
39
|
set_default_app_for_directory!
|
40
|
-
|
41
|
-
|
40
|
+
add_s3_bucket
|
41
|
+
add_rds_database
|
42
|
+
add_elasticache_cluster
|
43
|
+
apply_terraform_update!
|
44
|
+
terraform_state
|
42
45
|
s3_bucket_details
|
46
|
+
elasticache_details
|
47
|
+
rds_details
|
43
48
|
add_docker_registry!
|
44
49
|
default_service_domain_name
|
45
50
|
run_convox_command!
|
46
51
|
logger
|
52
|
+
rack_already_installed?
|
47
53
|
].each do |method|
|
48
54
|
define_method(method) do |*args|
|
49
55
|
client.send(method, *args)
|
@@ -100,11 +100,11 @@ RSpec.describe Convox::Client do
|
|
100
100
|
end
|
101
101
|
end
|
102
102
|
|
103
|
-
describe '#
|
103
|
+
describe '#validate_convox_rack_and_write_current!' do
|
104
104
|
it 'requires the correct config vars' do
|
105
105
|
client = described_class.new
|
106
106
|
expect do
|
107
|
-
client.
|
107
|
+
client.validate_convox_rack_and_write_current!
|
108
108
|
end.to raise_error('aws_region is missing from the config!')
|
109
109
|
end
|
110
110
|
|
@@ -120,7 +120,7 @@ RSpec.describe Convox::Client do
|
|
120
120
|
).and_return(false)
|
121
121
|
|
122
122
|
expect do
|
123
|
-
client.
|
123
|
+
client.validate_convox_rack_and_write_current!
|
124
124
|
end.to raise_error(/Could not find auth file at /)
|
125
125
|
end
|
126
126
|
|
@@ -138,10 +138,10 @@ RSpec.describe Convox::Client do
|
|
138
138
|
stack_name: 'convox-test'
|
139
139
|
}
|
140
140
|
)
|
141
|
-
expect(client).to receive(:
|
141
|
+
expect(client).to receive(:write_current).with(
|
142
142
|
'convox-test-697645520.us-west-2.elb.amazonaws.com'
|
143
143
|
)
|
144
|
-
expect(client.
|
144
|
+
expect(client.validate_convox_rack_and_write_current!).to(
|
145
145
|
eq('convox-test-697645520.us-west-2.elb.amazonaws.com')
|
146
146
|
)
|
147
147
|
end
|
@@ -161,7 +161,7 @@ RSpec.describe Convox::Client do
|
|
161
161
|
}
|
162
162
|
)
|
163
163
|
expect do
|
164
|
-
client.
|
164
|
+
client.validate_convox_rack_and_write_current!
|
165
165
|
end.to raise_error('Could not find matching authentication for ' \
|
166
166
|
'region: us-east-1, stack: convox-test')
|
167
167
|
end
|
@@ -182,7 +182,7 @@ RSpec.describe Convox::Client do
|
|
182
182
|
}
|
183
183
|
)
|
184
184
|
expect do
|
185
|
-
client.
|
185
|
+
client.validate_convox_rack_and_write_current!
|
186
186
|
end.to raise_error('Found multiple matching hosts for ' \
|
187
187
|
'region: us-west-2, stack: convox-test')
|
188
188
|
end
|