convox_installer 2.0.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b9b7140386f111c59db16b8db7429b2fb3a5c1a48372a2559e2e2c5499d59dd2
4
- data.tar.gz: 6e94e2c459c01426f8b6d2adc7d232f630e9711722947d2899b20ea509089c47
3
+ metadata.gz: ea8a81c025b1a5046d3145beeee5cba92f8448cc8751182d911f3548b289c31a
4
+ data.tar.gz: 8eb61f167e06eb67bcd86eada0196d138995b3e61b237ce04f11891f6eb1426c
5
5
  SHA512:
6
- metadata.gz: 64741ea4bd8a4e03d30172d366976e7d18c99cd2dff1f27ceaf5106d86de56c46fc0302fed774669f5a513d3b5cd8212b3b94e54687a539b235e545b3a9c90cb
7
- data.tar.gz: 67966065258259f0b64c7572895ee884f4e34c55e4f9c1ddbff90cf431e8da78984706955b406655d8f0128f4edd21842559129aa5b7dde73a745a121ad44ce1
6
+ metadata.gz: db7f9e671230b4054a888397461056af9f8ee2d631e0d05fb4eda32a138ae947e7f7fdee8706ea3b9b695254d9fa8b5c2971222a68d8ba4ad8a1326917357f65
7
+ data.tar.gz: 8dd7d82786491eca18253d6dc405675fbe6c188c0276878ed352e3f9c17770aca9a4906b8819d061dd6940a203832e0808e9f1aa8427bddc9505d89974f39c69
data/.rubocop.yml CHANGED
@@ -8,7 +8,7 @@ Style/MixinUsage:
8
8
  Exclude:
9
9
  - examples/**/*
10
10
 
11
- Metrics/LineLength:
11
+ Layout/LineLength:
12
12
  Max: 100
13
13
 
14
14
  Style/StringLiterals:
@@ -1,4 +1,5 @@
1
1
  {
2
- "editor.formatOnSave": false,
3
- "editor.formatOnSaveTimeout": 5000
2
+ "editor.formatOnSave": true,
3
+ "editor.formatOnSaveTimeout": 5000,
4
+ "ruby.rubocop.executePath": "/usr/local/bin/rubocop-daemon-wrapper/"
4
5
  }
data/Gemfile CHANGED
@@ -9,6 +9,7 @@ group :test do
9
9
  gem 'pry-byebug', require: false
10
10
  gem 'rspec'
11
11
  gem 'rubocop'
12
+ gem 'rubocop-daemon'
12
13
  gem 'rubocop-rspec'
13
14
  gem 'vcr'
14
15
  gem 'webmock'
data/README.md CHANGED
@@ -1,10 +1,23 @@
1
1
  # Convox Installer
2
2
 
3
- A Ruby gem that makes it easier to build a Convox installation script. This is like Chef/Ansible/Terraform for your initial Convox setup.
3
+ A Ruby gem that makes it easier to build a Convox installation script. The main purpose of this gem is to make it easier to set up on-premise installations of your app for enterprise users.
4
4
 
5
- # Requirements: Convox v2
5
+ This gem provides a DSL so that you can write a script that walks your users through setting up Convox and getting your app and running, setting up S3 buckets, etc.
6
+
7
+ ## Requirements
8
+
9
+ - MacOS
10
+ - Convox v3 CLI
11
+ - Runtime integration installed in your AWS account. See: https://docs.convox.com/getting-started/introduction/
12
+
13
+ _Please let us know if you need to run this script on Linux. Linux support should not be too difficult to implement, but unfortunately we probably won't be able to support Windows._
14
+
15
+ ### Requires Convox >= 3
16
+
17
+ This version of `convox_installer` is only designed to work with Convox 3 and later. You can run `convox version` to check your version. Please install the Convox v3 CLI by following the instructions here: https://docs.convox.com/getting-started/introduction/
18
+
19
+ _If you want to set up a Convox v2 rack (deprecated), the last version of `convox_installer` that supports the v2 CLI is `1.0.9`. (Take a look at [the `convox2` branch](https://github.com/DocSpring/convox_installer/tree/convox2).)_
6
20
 
7
- This version of `convox_installer` is only designed to work with Convox v2. Please install the v2 CLI by following the instructions at their old documentation site: https://docsv2.convox.com/introduction/installation
8
21
  ## USE AT YOUR OWN RISK! THIS CODE IS PROVIDED WITHOUT ANY WARRANTIES OR GUARANTEES
9
22
 
10
23
  We have successfully set up a number of test and production deployments using this gem. Everything seems to work very well. The library also facilitates idempotency and crash-resistance, so you can easily re-run your installation script if something goes wrong. However, if anything goes wrong, then you can end up with a large AWS bill if you're not careful. If anything crashes then make sure you double-check everything in your AWS account and shut down any leftover resources. **USE THIS SOFTWARE AT YOUR OWN RISK.**
@@ -25,7 +38,7 @@ We have successfully set up a number of test and production deployments using th
25
38
 
26
39
  `convox_installer` is a Ruby gem that makes it much easier to build an installation script for `convox/rack` (the open source PaaS). The Convox CLI is awesome, but it's missing a nice way to script a full deployment. I originally wrote a bash script that made API calls and used [`jq`](https://stedolan.github.io/jq/) and `sed`, but this was very error-prone and it did not have good cross-platform support.
27
40
 
28
- I've rewritten this installation script in Ruby, which provides very good cross-platform support, and also allows me to write tests.
41
+ I've written this installation script in Ruby, which provides very good cross-platform support, and also allows me to write tests.
29
42
 
30
43
  ## Usage
31
44
 
@@ -37,7 +50,7 @@ require 'bundler/inline'
37
50
 
38
51
  gemfile do
39
52
  source 'https://rubygems.org'
40
- gem 'convox_installer'
53
+ gem 'convox_installer', '3.0.0'
41
54
  end
42
55
 
43
56
  require "convox_installer"
@@ -141,10 +154,6 @@ Makes sure that the `convox` and `aws` CLI tools are installed on this system. I
141
154
  Loads config from ENV vars, or from saved config at `./.installer_config.json`.
142
155
  If any config settings are missing, it prompts the user for input. Finally, it shows a summary of the config, and asks the user if they want to proceed with the installation. If the user enters `y` (or `yes`), the `prompt_for_config` method completes. If they enter `n` (or `no`), we loop over every setting and let them press "enter" to keep the current value, or provide a new value to correct any mistakes.
143
156
 
144
- #### `backup_convox_host_and_rack`
145
-
146
- If there are any existing files at `~/.convox/host` or `~/.convox/rack`, this method moves these to `~/.convox/host.bak` and `~/.convox/rack.bak`.
147
-
148
157
  #### `install_convox`
149
158
 
150
159
  - **Required Config:** `aws_region`, `aws_access_key_id`, `aws_secret_access_key`,
@@ -152,13 +161,13 @@ If there are any existing files at `~/.convox/host` or `~/.convox/rack`, this me
152
161
 
153
162
  Runs `convox rack install ...`. Has some validations to ensure that all required settings are present.
154
163
 
155
- #### `validate_convox_auth_and_write_host!`
164
+ #### `validate_convox_rack_and_write_current!`
156
165
 
157
- After running `install_convox`, call this method to ensure that the the `~/.convox/auth` file has been updated with the correct details (checks the rack name and AWS region.) Then it sets the rack host in `~/.convox/host` (if not already set.)
166
+ Ensures that the local machine contains a directory for the rack's terraform config, and sets the current rack for Convox CLI commands.
158
167
 
159
- #### `validate_convox_rack!`
168
+ #### `validate_convox_rack_api!`
160
169
 
161
- Calls `convox api get /system` to get the Rack details, then makes sure that everything is correct.
170
+ Makes an API request (`convox api get /system`) to get the rack details, and makes sure that everything is correct.
162
171
 
163
172
  #### `convox_rack_data`
164
173
 
@@ -174,10 +183,14 @@ Checks if the app already exists. If not, calls `convox apps create ... --wait`
174
183
 
175
184
  Writes the app name into `./.convox/app` (in the current directory.) The `convox` CLI reads this file, so you don't need to specify the `--app` flag for future commands.
176
185
 
177
- #### `create_s3_bucket!`
186
+ #### `add_s3_bucket`
187
+
188
+ Adds an S3 bucket to your Terraform config.
178
189
 
179
190
  - **Required Config:** `s3_bucket_name`
180
191
 
192
+ NOTE: This method just writes a new Terraform configuration file. You must run `apply_terraform_update!` to apply the changes and create the S3 bucket.
193
+
181
194
  Creates an S3 bucket from the `:s3_bucket_name` config setting. This is not a default setting, so you can add something like this to your custom `@prompts`:
182
195
 
183
196
  ```ruby
@@ -190,41 +203,97 @@ Creates an S3 bucket from the `:s3_bucket_name` config setting. This is not a de
190
203
 
191
204
  The `:value` `Proc` will generate a bucket name with a random suffix. (Avoids conflicts when you are setting up multiple deployments for your app.)
192
205
 
193
- `create_s3_bucket!` will also call `set_s3_bucket_cors_policy` automatically, so you don't need to call this manually.
194
-
195
- #### `set_s3_bucket_cors_policy`
206
+ You can also set a CORS policy for your S3 bucket. (`:s3_bucket_name`)
207
+ We set the `cors_rule` option for the `aws_s3_bucket` resource in the Terraform configuration. Example:
196
208
 
197
- - **Required Config:** `s3_bucket_name`
209
+ ```
210
+ cors_rule {
211
+ allowed_headers = ["*"]
212
+ allowed_methods = ["PUT", "POST"]
213
+ allowed_origins = ["https://s3-website-test.hashicorp.com"]
214
+ expose_headers = ["ETag"]
215
+ max_age_seconds = 3000
216
+ }
217
+ ```
198
218
 
199
- Set up a CORS policy for your S3 bucket. (`:s3_bucket_name`)
219
+ See: https://registry.terraform.io/providers/hashicorp/aws/3.33.0/docs/resources/s3_bucket#using-cors
200
220
 
201
- _Note: If the `:s3_bucket_cors_policy` setting is not provided, then this method does nothing._
221
+ _Note: If the `:s3_bucket_cors_rule` setting is not provided, then it is skipped._
202
222
 
203
- You should set `:s3_bucket_cors_policy` to a JSON string. Here's how I set this up in my own `install.rb` script:
223
+ Here's how we set up a CORS policy in our own `install.rb` script:
204
224
 
205
225
  ```ruby
206
- S3_BUCKET_CORS_POLICY = <<-JSON
207
- {
208
- "CORSRules": [
209
- {
210
- "AllowedOrigins": ["*"],
211
- "AllowedHeaders": ["Authorization", "cache-control", "x-requested-with"],
212
- "AllowedMethods": ["PUT", "POST", "GET"],
213
- "MaxAgeSeconds": 3000
214
- }
215
- ]
216
- }
217
- JSON
226
+ xxxxc = <<-TERRAFORM
227
+ cors_rule {
228
+ allowed_headers = ["Authorization", "cache-control", "x-requested-with"]
229
+ allowed_methods = ["PUT", "POST", "GET"]
230
+ allowed_origins = ["*"]
231
+ expose_headers = []
232
+ max_age_seconds = 3000
233
+ }
234
+ TERRAFORM
218
235
 
219
236
  @prompts = [
220
237
  {
221
- key: :s3_bucket_cors_policy,
222
- value: S3_BUCKET_CORS_POLICY,
238
+ key: :s3_bucket_cors_rule,
239
+ value: S3_BUCKET_CORS_RULE,
223
240
  hidden: true,
224
241
  }
225
242
  ]
226
243
  ```
227
244
 
245
+ #### `add_rds_database`
246
+
247
+ Adds an RDS database to your Terraform config.
248
+
249
+ - **Required Config:**
250
+ - `database_username`
251
+ - `database_password`
252
+ - **Optional Config:**
253
+ - `database_allocated_storage` _(default: 30)_
254
+ - `database_engine` _(default: 'postgres')_
255
+ - `database_engine_version` _(default: '14.2')_
256
+ - `database_instance_class` _(default: 'db.t3.medium')_
257
+ - `database_multi_az` _(default: true)_
258
+
259
+ #### `add_add_elasticache_cluster`
260
+
261
+ Adds an Elasticache cluster to your Terraform config.
262
+
263
+ - **Optional Config:**
264
+ - `engine` _(default: 'redis')_
265
+ - `engine_version` _(default: '6.2.5')_
266
+ - `node_type` _(default: 'cache.m3.medium')_
267
+ - `database_instance_class` _(default: 'db.t3.medium')_
268
+ - `num_cache_nodes` _(default: 1)_
269
+ - `port` _(default: 6379)_
270
+
271
+ _IMPORTANT: Make sure you specify a full version string (e.g. `6.2.5`), and not a partial version (e.g. `6.2`.) A partial version will cause Terraform to delete and recreate the cluster on every run._
272
+
273
+ #### `apply_terraform_update!`
274
+
275
+ Runs `terraform apply -auto-approve` to apply any changes to your Terraform configuration (add new resources, etc.)
276
+
277
+ #### `rds_details`
278
+
279
+ Returns information about the created RDS database resource.
280
+
281
+ ```ruby
282
+ {
283
+ postgres_url: "Full URL for the RDS database (including auth)",
284
+ }
285
+ ```
286
+
287
+ #### `elasticache_details`
288
+
289
+ Returns information about the created RDS database resource.
290
+
291
+ ```ruby
292
+ {
293
+ redis_url: "Full URL for the Redis cluster",
294
+ }
295
+ ```
296
+
228
297
  #### `s3_bucket_details`
229
298
 
230
299
  - **Required Config:** `s3_bucket_name`
@@ -249,13 +318,14 @@ Checks the list of registries to see if `docker_registry_url` has already been a
249
318
 
250
319
  #### `default_service_domain_name`
251
320
 
252
- - **Required Config:** `convox_app_name`, `default_service`
321
+ - **Required Config:** `convox_app_name`
322
+ - **Optional Config:** `default_service`
253
323
 
254
- Parses the rack router ELB name and region, and returns the default `convox.site` domain for your default service. (You can visit this URL in the browser to access your app.)
324
+ Finds the default `*.convox.cloud` URL for the web service. (You can visit this URL in the browser to access your app.)
255
325
 
256
- Example: `myapp-web.rackname-route-abcdfe123456-123456789.us-west-2.convox.site`
326
+ Example: `web.docspring.dc6bae48c2e36366.convox.cloud`
257
327
 
258
- Set a default service in your config prompts (e.g. `web`):
328
+ You can override the default service name in your config (e.g. `web`):
259
329
 
260
330
  ```ruby
261
331
  @prompts = [
@@ -20,18 +20,15 @@ include ConvoxInstaller
20
20
  MINIMAL_HEALTH_CHECK_PATH = '/health/site'
21
21
  COMPLETE_HEALTH_CHECK_PATH = '/health'
22
22
 
23
- S3_BUCKET_CORS_POLICY = <<~JSON
24
- {
25
- "CORSRules": [
26
- {
27
- "AllowedOrigins": ["*"],
28
- "AllowedHeaders": ["Authorization", "cache-control", "x-requested-with"],
29
- "AllowedMethods": ["PUT", "POST", "GET"],
30
- "MaxAgeSeconds": 3000
31
- }
32
- ]
23
+ S3_BUCKET_CORS_RULE = <<-TERRAFORM
24
+ cors_rule {
25
+ allowed_headers = ["Authorization", "cache-control", "x-requested-with"]
26
+ allowed_methods = ["PUT", "POST", "GET"]
27
+ allowed_origins = ["*"]
28
+ expose_headers = []
29
+ max_age_seconds = 3000
33
30
  }
34
- JSON
31
+ TERRAFORM
35
32
 
36
33
  @prompts = ConvoxInstaller::Config::DEFAULT_PROMPTS + [
37
34
  {
@@ -81,8 +78,18 @@ JSON
81
78
  value: -> { "app-uploads-#{SecureRandom.hex(4)}" }
82
79
  },
83
80
  {
84
- key: :s3_bucket_cors_policy,
85
- value: S3_BUCKET_CORS_POLICY,
81
+ key: :s3_bucket_cors_rule,
82
+ value: S3_BUCKET_CORS_RULE,
83
+ hidden: true
84
+ },
85
+ {
86
+ key: :database_username,
87
+ value: 'example_app',
88
+ hidden: true
89
+ },
90
+ {
91
+ key: :database_password,
92
+ value: -> { SecureRandom.hex(16) },
86
93
  hidden: true
87
94
  }
88
95
  ]
@@ -94,7 +101,7 @@ backup_convox_host_and_rack
94
101
  install_convox
95
102
 
96
103
  validate_convox_auth_and_write_host!
97
- validate_convox_rack!
104
+ validate_convox_rack_api!
98
105
 
99
106
  create_convox_app!
100
107
  set_default_app_for_directory!
@@ -129,7 +136,7 @@ env_command_params = env.map { |k, v| "#{k}=\"#{v}\"" }.join(' ')
129
136
  run_convox_command! "env set #{env_command_params}"
130
137
 
131
138
  puts '=> Initial deploy...'
132
- puts '-----> Documentation: https://docs.convox.com/deployment/builds'
139
+ puts '-----> Documentation: https://docs.convox.com/deployment/deploying-changes/'
133
140
  run_convox_command! 'deploy --wait'
134
141
 
135
142
  puts '=> Setting up the database...'
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
- CONVOX_DIR = File.expand_path('~/.convox').freeze
11
- AUTH_FILE = File.join(CONVOX_DIR, 'auth')
12
- HOST_FILE = File.join(CONVOX_DIR, 'host')
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 CONVOX_DIR
75
+ FileUtils.mkdir_p CONVOX_CONFIG_DIR
62
76
 
63
- %w[host rack].each do |f|
64
- path = File.join(CONVOX_DIR, f)
65
- next unless File.exist?(path)
77
+ path = File.join(CONVOX_CONFIG_DIR, 'current')
78
+ return unless File.exist?(path)
66
79
 
67
- bak_file = "#{path}.bak"
68
- logger.info "Moving existing #{path} to #{bak_file}..."
69
- FileUtils.mv(path, bak_file)
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 stack named #{stack_name} " \
80
- "in the #{region} AWS region. Using this rack. "
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
 
@@ -97,11 +113,19 @@ module Convox
97
113
  'AWS_SECRET_ACCESS_KEY' => config.fetch(:aws_secret_access_key)
98
114
  }
99
115
  command = %(rack install aws \
100
- --name "#{config.fetch(:stack_name)}" \
101
- "InstanceType=#{config.fetch(:instance_type)}" \
102
- "BuildInstance=")
116
+ "#{config.fetch(:stack_name)}" \
117
+ "node_type=#{config.fetch(:instance_type)}" \
118
+ "region=#{config.fetch(:aws_region)}")
119
+ # us-east constantly has problems with the us-east-1c AZ:
120
+ # "Cannot create cluster 'ds-enterprise-cx3' because us-east-1c, the targeted
121
+ # availability zone, does not currently have sufficient capacity to support the cluster.
122
+ # Retry and choose from these availability zones:
123
+ # us-east-1a, us-east-1b, us-east-1d, us-east-1e, us-east-1f
124
+ if config.fetch(:aws_region) == 'us-east-1'
125
+ command += ' "availability_zones=us-east-1a,us-east-1b,us-east-1d,us-east-1e,us-east-1f"'
126
+ end
103
127
 
104
- run_convox_command!(command, env)
128
+ run_convox_command!(command, env, rack_arg: false)
105
129
  end
106
130
 
107
131
  def rack_already_installed?
@@ -109,66 +133,51 @@ module Convox
109
133
 
110
134
  return unless File.exist?(AUTH_FILE)
111
135
 
112
- region = config.fetch(:aws_region)
136
+ # region = config.fetch(:aws_region)
113
137
  stack_name = config.fetch(:stack_name)
138
+ return true if File.exist?(rack_dir)
114
139
 
115
- auth.each do |host, _password|
116
- if host.match?(/^#{stack_name}-\d+\.#{region}\.elb\.amazonaws\.com$/)
117
- return true
118
- end
140
+ auth.each do |rack_name, _password|
141
+ return true if rack_name == stack_name
119
142
  end
120
143
  false
121
144
  end
122
145
 
123
- def validate_convox_auth_and_write_host!
146
+ # Auth for a detached rack is not saved in the auth file anymore.
147
+ # It can be found in the terraform state:
148
+ # ~/Library/Preferences/convox/racks/ds-enterprise-cx3/terraform.tfstate
149
+ # Under outputs/api/value. The API URL contains the convox username and API token as basic auth.
150
+ def validate_convox_rack_and_write_current!
124
151
  require_config(%i[aws_region stack_name])
125
152
 
126
- unless File.exist?(AUTH_FILE)
127
- raise "Could not find auth file at #{AUTH_FILE}!"
153
+ unless rack_already_installed?
154
+ raise "Could not find rack terraform directory at: #{rack_dir}"
128
155
  end
129
156
 
130
- region = config.fetch(:aws_region)
131
- stack = config.fetch(:stack_name)
132
-
133
- match_count = 0
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
157
+ # Tells the Convox CLI to use our terraform stack
158
+ stack_name = config.fetch(:stack_name)
159
+ write_current(stack_name)
160
+ stack_name
154
161
  end
155
162
 
156
- def write_host(host)
157
- logger.debug "Setting convox host to #{host} (in #{HOST_FILE})..."
158
- File.open(HOST_FILE, 'w') { |f| f.puts host }
163
+ def write_current(rack_name)
164
+ logger.debug "Setting convox rack to #{rack_name} (in #{CURRENT_FILE})..."
165
+ current_hash = { name: rack_name, type: 'terraform' }
166
+ File.open(CURRENT_FILE, 'w') { |f| f.puts current_hash.to_json }
159
167
  end
160
168
 
161
- def validate_convox_rack!
169
+ def validate_convox_rack_api!
162
170
  require_config(%i[
163
171
  aws_region
164
172
  stack_name
165
173
  instance_type
166
174
  ])
167
175
  logger.debug 'Validating that convox rack has the correct attributes...'
176
+ # Convox 3 racks no longer return info about region or type. (These are blank strings.)
168
177
  {
169
178
  provider: 'aws',
170
- region: config.fetch(:aws_region),
171
- type: config.fetch(:instance_type),
179
+ # region: config.fetch(:aws_region),
180
+ # type: config.fetch(:instance_type),
172
181
  name: config.fetch(:stack_name)
173
182
  }.each do |k, v|
174
183
  convox_value = convox_rack_data[k.to_s]
@@ -184,8 +193,22 @@ module Convox
184
193
  def convox_rack_data
185
194
  @convox_rack_data ||= begin
186
195
  logger.debug 'Fetching convox rack attributes...'
187
- convox_output = `convox api get /system`
188
- raise 'convox command failed!' unless $CHILD_STATUS.success?
196
+ command = "convox api get /system --rack #{config.fetch(:stack_name)}"
197
+ logger.debug "+ #{command}"
198
+ # It can take a while for the API to be ready.
199
+ start_time = Time.now
200
+ convox_output = nil
201
+ loop do
202
+ convox_output = `#{command}`
203
+ break if $CHILD_STATUS.success?
204
+
205
+ if Time.now - start_time > 360
206
+ raise 'Could not connect to Convox rack API!'
207
+ end
208
+
209
+ logger.debug 'Waiting for Convox rack API to be ready... (can take a few minutes)'
210
+ sleep 5
211
+ end
189
212
 
190
213
  JSON.parse(convox_output)
191
214
  end
@@ -199,9 +222,10 @@ module Convox
199
222
 
200
223
  logger.info "Creating app: #{app_name}..."
201
224
  logger.info '=> Documentation: ' \
202
- 'https://docs.convox.com/deployment/creating-an-application'
225
+ 'https://docs.convox.com/reference/cli/apps/'
203
226
 
204
- run_convox_command! "apps create #{app_name} --wait"
227
+ # NOTE: --wait flags were removed in Convox 3. It now waits by default.
228
+ run_convox_command! "apps create #{app_name}"
205
229
 
206
230
  retries = 0
207
231
  loop do
@@ -232,7 +256,7 @@ module Convox
232
256
  app_name = config.fetch(:convox_app_name)
233
257
 
234
258
  logger.debug "Looking for existing #{app_name} app..."
235
- convox_output = `convox api get /apps`
259
+ convox_output = `convox api get /apps --rack #{config.fetch(:stack_name)}`
236
260
  raise 'convox command failed!' unless $CHILD_STATUS.success?
237
261
 
238
262
  apps = JSON.parse(convox_output)
@@ -247,128 +271,117 @@ module Convox
247
271
  end
248
272
 
249
273
  # Create the s3 bucket, and also apply a CORS configuration
250
- def create_s3_bucket!
274
+ # Convox v3 update - They removed support for S3 resources, so we have to do
275
+ # in terraform now (which is actually pretty nice!)
276
+ def add_s3_bucket
251
277
  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
278
 
265
- if retries > 10
266
- raise "Something went wrong while creating the #{bucket_name} S3 bucket! " \
267
- '(Please wait a few moments and then restart the installation script.)'
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!'
279
+ unless config.key? :s3_bucket_cors_rule
280
+ logger.debug 'No CORS rule provided in config: s3_bucket_cors_rule (optional)'
281
+ return
275
282
  end
276
283
 
277
- set_s3_bucket_cors_policy
284
+ write_terraform_template('s3_bucket')
278
285
  end
279
286
 
280
- def s3_bucket_exists?
281
- require_config(%i[s3_bucket_name])
282
- bucket_name = config.fetch(:s3_bucket_name)
283
- logger.debug "Looking up S3 bucket resource: #{bucket_name}"
284
- `convox api get /resources/#{bucket_name} 2>/dev/null`
285
- $CHILD_STATUS.success?
287
+ def add_rds_database
288
+ require_config(%i[database_username database_password])
289
+ write_terraform_template('rds')
286
290
  end
287
291
 
288
- def s3_bucket_details
289
- require_config(%i[s3_bucket_name])
290
- @s3_bucket_details ||= begin
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
292
+ def add_elasticache_cluster
293
+ write_terraform_template('elasticache')
294
+ end
307
295
 
308
- {
309
- access_key_id: matches[:access_key_id],
310
- secret_access_key: matches[:secret_access_key],
311
- name: matches[:bucket_name]
312
- }
296
+ def write_terraform_template(name)
297
+ template_path = File.join(__dir__, "../../terraform/#{name}.tf.erb")
298
+ unless File.exist?(template_path)
299
+ raise "Could not find terraform template at: #{template_path}"
313
300
  end
301
+
302
+ template = ERB.new(File.read(template_path))
303
+ template_output = template.result(binding)
304
+
305
+ tf_file_path = File.join(rack_dir, "#{name}.tf")
306
+ logger.debug "Writing terraform config to #{tf_file_path}..."
307
+ File.open(tf_file_path, 'w') { |f| f.puts template_output }
314
308
  end
315
309
 
316
- def set_s3_bucket_cors_policy
317
- require_config(%i[aws_access_key_id aws_secret_access_key])
318
- access_key_id = config.fetch(:aws_access_key_id)
319
- secret_access_key = config.fetch(:aws_secret_access_key)
310
+ def apply_terraform_update!
311
+ logger.info 'Applying terraform update...'
312
+ command = if ENV['DEBUG_TERRAFORM']
313
+ 'terraform plan'
314
+ else
315
+ 'terraform apply -auto-approve'
316
+ end
317
+ logger.debug "+ #{command}"
320
318
 
321
- unless config.key? :s3_bucket_cors_policy
322
- logger.debug 'No CORS policy provided in config: s3_bucket_cors_policy'
323
- return
319
+ env = {
320
+ 'AWS_ACCESS_KEY_ID' => config.fetch(:aws_access_key_id),
321
+ 'AWS_SECRET_ACCESS_KEY' => config.fetch(:aws_secret_access_key)
322
+ }
323
+ Dir.chdir(rack_dir) do
324
+ system env, command
325
+ raise 'terraform command failed!' unless $CHILD_STATUS.success?
324
326
  end
325
- cors_policy_string = config.fetch(:s3_bucket_cors_policy)
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
327
+ end
347
328
 
348
- if existing_cors_policy == cors_policy_json
349
- logger.debug "=> CORS policy is already up to date for #{bucket_name}."
350
- return
351
- end
329
+ def terraform_state
330
+ tf_state_file = File.join(rack_dir, 'terraform.tfstate')
331
+ JSON.parse(File.read(tf_state_file))
332
+ end
333
+
334
+ def terraform_resource(resource_type, resource_name)
335
+ resource = terraform_state['resources'].find do |resource|
336
+ resource['type'] == resource_type && resource['name'] == resource_name
352
337
  end
338
+ return resource if resource
353
339
 
354
- begin
355
- logger.info "Setting CORS policy for #{bucket_name}..."
340
+ raise "Could not find #{resource_type} resource named #{resource_name} in terraform state!"
341
+ end
356
342
 
357
- File.open('cors-policy.json', 'w') { |f| f.puts cors_policy_string }
343
+ def s3_bucket_details
344
+ require_config(%i[s3_bucket_name])
358
345
 
359
- `AWS_ACCESS_KEY_ID=#{access_key_id} \
360
- AWS_SECRET_ACCESS_KEY=#{secret_access_key} \
361
- aws s3api put-bucket-cors \
362
- --bucket #{bucket_name} \
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
346
+ s3_bucket = terraform_resource('aws_s3_bucket', 'docs_s3_bucket')
347
+ bucket_attributes = s3_bucket['instances'][0]['attributes']
348
+ access_key = terraform_resource('aws_iam_access_key', 'docspring_user_access_key')
349
+ key_attributes = access_key['instances'][0]['attributes']
367
350
 
368
- logger.info "=> Successfully set CORS policy for #{bucket_name}."
369
- ensure
370
- FileUtils.rm_f 'cors-policy.json'
371
- end
351
+ {
352
+ access_key_id: key_attributes['id'],
353
+ secret_access_key: key_attributes['secret'],
354
+ name: bucket_attributes['bucket']
355
+ }
356
+ end
357
+
358
+ def rds_details
359
+ require_config(%i[database_username database_password])
360
+
361
+ database = terraform_resource('aws_db_instance', 'rds_database')
362
+ database_attributes = database['instances'][0]['attributes']
363
+
364
+ username = database_attributes['username']
365
+ password = database_attributes['password']
366
+ endpoint = database_attributes['endpoint']
367
+ postgres_url = "postgres://#{username}:#{password}@#{endpoint}/app"
368
+ {
369
+ postgres_url: postgres_url
370
+ }
371
+ end
372
+
373
+ def elasticache_details
374
+ require_config(%i[s3_bucket_name])
375
+
376
+ # Just ensure that the bucket exists in the state
377
+ cluster = terraform_resource('aws_elasticache_cluster', 'elasticache_cluster')
378
+ cluster_attributes = cluster['instances'][0]['attributes']
379
+ cache_node = cluster_attributes['cache_nodes'][0]
380
+ redis_url = "redis://#{cache_node['address']}:#{cache_node['port']}/0"
381
+
382
+ {
383
+ redis_url: redis_url
384
+ }
372
385
  end
373
386
 
374
387
  def add_docker_registry!
@@ -377,7 +390,7 @@ module Convox
377
390
  registry_url = config.fetch(:docker_registry_url)
378
391
 
379
392
  logger.debug 'Looking up existing Docker registries...'
380
- registries_response = `convox api get /registries`
393
+ registries_response = `convox api get /registries --rack #{config.fetch(:stack_name)}`
381
394
  unless $CHILD_STATUS.success?
382
395
  raise 'Something went wrong while fetching the list of registries!'
383
396
  end
@@ -391,36 +404,37 @@ module Convox
391
404
 
392
405
  logger.info "Adding Docker Registry: #{registry_url}..."
393
406
  logger.info '=> Documentation: ' \
394
- 'https://docs.convox.com/deployment/private-registries'
407
+ 'https://docs.convox.com/configuration/private-registries/'
395
408
 
396
409
  `convox registries add "#{registry_url}" \
397
410
  "#{config.fetch(:docker_registry_username)}" \
398
- "#{config.fetch(:docker_registry_password)}"`
411
+ "#{config.fetch(:docker_registry_password)}" \
412
+ --rack #{config.fetch(:stack_name)}`
399
413
  return if $CHILD_STATUS.success?
400
414
 
401
415
  raise "Something went wrong while adding the #{registry_url} registry!"
402
416
  end
403
417
 
404
418
  def default_service_domain_name
405
- require_config(%i[convox_app_name default_service])
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)
419
+ require_config(%i[convox_app_name])
416
420
 
417
- # Need to return downcase host so that `config.hosts` works with Rails applications
418
- "#{app}-#{service}.#{elb_name_and_region}.convox.site".downcase
419
- end
421
+ app_name = config.fetch(:convox_app_name)
422
+ default_service = config[:default_service] || 'web'
423
+
424
+ convox_api_url = terraform_state['outputs']['api']['value']
425
+ convox_router_host = convox_api_url.split('@').last.sub(/^api\./, '')
426
+
427
+ [default_service, app_name, convox_router_host].join('.').downcase
420
428
  end
421
429
 
422
- def run_convox_command!(cmd, env = {})
430
+ def run_convox_command!(cmd, env = {}, rack_arg: true)
431
+ # Always include the rack as an argument, to
432
+ # make sure that 'convox switch' doesn't affect any commands
423
433
  command = "convox #{cmd}"
434
+ if rack_arg
435
+ command = "#{command} --rack #{config.fetch(:stack_name)}"
436
+ end
437
+ logger.debug "+ #{command}"
424
438
  system env, command
425
439
  raise "Error running: #{command}" unless $CHILD_STATUS.success?
426
440
  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
- return if client.convox_2_cli?
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 2.x.x. ' \
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 'Please follow the instructions here to downgrade your ' \
57
- 'Convox CLI version: https://docsv2.convox.com/introduction/installation'
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ConvoxInstaller
4
- VERSION = '2.0.0'
4
+ VERSION = '3.0.0'
5
5
  end
@@ -32,18 +32,24 @@ module ConvoxInstaller
32
32
  %w[
33
33
  backup_convox_host_and_rack
34
34
  install_convox
35
- validate_convox_auth_and_write_host!
36
- validate_convox_rack!
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
- create_s3_bucket!
41
- set_s3_bucket_cors_policy
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)
@@ -138,7 +138,7 @@ RSpec.describe Convox::Client do
138
138
  stack_name: 'convox-test'
139
139
  }
140
140
  )
141
- expect(client).to receive(:write_host).with(
141
+ expect(client).to receive(:write_current).with(
142
142
  'convox-test-697645520.us-west-2.elb.amazonaws.com'
143
143
  )
144
144
  expect(client.validate_convox_auth_and_write_host!).to(
@@ -3,7 +3,7 @@
3
3
  require 'convox_installer'
4
4
 
5
5
  RSpec.describe ConvoxInstaller::Requirements do
6
- let(:convox_cli_version) { '20210208170413' }
6
+ let(:convox_cli_version) { '3.3.4' }
7
7
 
8
8
  before do
9
9
  allow_any_instance_of(
@@ -45,8 +45,8 @@ RSpec.describe ConvoxInstaller::Requirements do
45
45
  end
46
46
  end
47
47
 
48
- context 'with Convox CLI version 3.3.4' do
49
- let(:convox_cli_version) { '3.3.4' }
48
+ context 'with Convox CLI version 20210208170413' do
49
+ let(:convox_cli_version) { '20210208170413' }
50
50
 
51
51
  it 'shows the correct error message and quit' do
52
52
  req = described_class.new
@@ -55,11 +55,10 @@ RSpec.describe ConvoxInstaller::Requirements do
55
55
  expect(req).to receive(:quit!)
56
56
 
57
57
  expect(req.logger).to receive(:error).with(
58
- 'This script requires Convox CLI version 2.x.x. Your Convox CLI version is: 3.3.4'
58
+ 'This script requires Convox CLI version 3.x.x. Your Convox CLI version is: 20210208170413'
59
59
  )
60
60
  expect(req.logger).to receive(:error).with(
61
- 'Please follow the instructions here to downgrade your ' \
62
- 'Convox CLI version: https://docsv2.convox.com/introduction/installation'
61
+ "Please run 'brew update convox' or follow the instructions at https://docs.convox.com/getting-started/introduction"
63
62
  )
64
63
 
65
64
  req.ensure_requirements!
@@ -0,0 +1,46 @@
1
+ resource "aws_elasticache_cluster" "elasticache_cluster" {
2
+ cluster_id = "<%= config.fetch(:stack_name) %>-elasticache-<%= config.fetch(:random_id) %>"
3
+ engine = "<%= config[:elasticache_engine] || 'redis' %>"
4
+ engine_version = "<%= config[:elasticache_engine_version] || '6.x' %>"
5
+ node_type = "<%= config[:elasticache_node_type] || 'cache.t3.medium' %>"
6
+ num_cache_nodes = <%= config[:elasticache_num_cache_nodes] || 1 %>
7
+ port = <%= config[:elasticache_port] || 6379 %>
8
+
9
+ subnet_group_name = aws_elasticache_subnet_group.elasticache_subnet_group.name
10
+ security_group_ids = [aws_security_group.elasticache_security_group.id]
11
+
12
+ # Workaround for weird engine_version issue where 6.x works for creation, and fails for update
13
+ # See: https://github.com/hashicorp/terraform-provider-aws/issues/15625#issuecomment-727759811
14
+ # Fixed in version 3.38.0 of the Terraform AWS provider.
15
+ lifecycle {
16
+ ignore_changes = [engine_version]
17
+ }
18
+ }
19
+
20
+ resource "aws_elasticache_subnet_group" "elasticache_subnet_group" {
21
+ name = "<%= config.fetch(:stack_name) %>-elasticache-cluster-subnetgroup-<%= config.fetch(:random_id) %>"
22
+ subnet_ids = module.system.cluster.subnets
23
+ }
24
+
25
+ resource "aws_security_group" "elasticache_security_group" {
26
+ name = "<%= config.fetch(:stack_name) %>-elasticache-securitygroup-<%= config.fetch(:random_id) %>"
27
+
28
+ description = "Elasticache Security Group (Managed by Terraform)"
29
+ vpc_id = module.system.cluster.vpc
30
+
31
+ # Only Redis in
32
+ ingress {
33
+ from_port = 6379
34
+ to_port = 6379
35
+ protocol = "tcp"
36
+ cidr_blocks = ["10.1.0.0/16"]
37
+ }
38
+
39
+ # Allow all outbound traffic
40
+ egress {
41
+ from_port = 0
42
+ to_port = 0
43
+ protocol = "-1"
44
+ cidr_blocks = ["0.0.0.0/0"]
45
+ }
46
+ }
@@ -0,0 +1,45 @@
1
+ resource "aws_db_instance" "rds_database" {
2
+ allocated_storage = <%= config[:database_allocated_storage] || 30 %>
3
+ engine = "<%= config[:database_engine] || 'postgres' %>"
4
+ engine_version = "<%= config[:database_engine_version] || '14.2' %>"
5
+ instance_class = "<%= config[:database_instance_class] || 'db.t3.medium' %>"
6
+ name = "<%= config.fetch(:stack_name).gsub('-', '_') %>_database"
7
+ identifier = "<%= config.fetch(:stack_name) %>-rds-<%= config.fetch(:random_id) %>"
8
+ multi_az = <%= config[:database_multi_az] || true %>
9
+ username = "<%= config.fetch(:database_username) %>"
10
+ password = "<%= config.fetch(:database_password) %>"
11
+
12
+ final_snapshot_identifier = "<%= config.fetch(:stack_name) %>-rds-<%= config.fetch(:random_id) %>-final-snapshot"
13
+ skip_final_snapshot = false
14
+
15
+ db_subnet_group_name = aws_db_subnet_group.rds_subnet_group.name
16
+ vpc_security_group_ids = [aws_security_group.rds_security_group.id]
17
+ }
18
+
19
+ resource "aws_db_subnet_group" "rds_subnet_group" {
20
+ name = "<%= config.fetch(:stack_name) %>-rds-subnetgroup-<%= config.fetch(:random_id) %>"
21
+ subnet_ids = module.system.cluster.subnets
22
+ }
23
+
24
+ resource "aws_security_group" "rds_security_group" {
25
+ name = "<%= config.fetch(:stack_name) %>-rds-database-securitygroup-<%= config.fetch(:random_id) %>"
26
+
27
+ description = "RDS Security Group (Managed by Terraform)"
28
+ vpc_id = module.system.cluster.vpc
29
+
30
+ # Only Postgres in
31
+ ingress {
32
+ from_port = 5432
33
+ to_port = 5432
34
+ protocol = "tcp"
35
+ cidr_blocks = ["10.1.0.0/16"]
36
+ }
37
+
38
+ # Allow all outbound traffic
39
+ egress {
40
+ from_port = 0
41
+ to_port = 0
42
+ protocol = "-1"
43
+ cidr_blocks = ["0.0.0.0/0"]
44
+ }
45
+ }
@@ -0,0 +1,73 @@
1
+ # AWS provider version is 3.33.0
2
+ # https://registry.terraform.io/providers/hashicorp/aws/3.33.0
3
+
4
+ provider "aws" {
5
+ region = "<%= config[:aws_region] %>"
6
+ }
7
+
8
+ resource "aws_kms_key" "docs_kms_key" {
9
+ description = "This key is used to encrypt objects in the DocSpring S3 bucket"
10
+ deletion_window_in_days = 14
11
+ }
12
+
13
+ # Later versions of aws provider (e.g. 4.8.0) use separate resources for
14
+ # aws_s3_bucket_acl and aws_s3_bucket_cors_configuration.
15
+ # This will need to be updated in the future.
16
+ resource "aws_s3_bucket" "docs_s3_bucket" {
17
+ bucket = "<%= config.fetch(:stack_name) %>-<%= config.fetch(:s3_bucket_name) %>"
18
+ acl = "private"
19
+
20
+ server_side_encryption_configuration {
21
+ rule {
22
+ apply_server_side_encryption_by_default {
23
+ kms_master_key_id = aws_kms_key.docs_kms_key.arn
24
+ sse_algorithm = "aws:kms"
25
+ }
26
+ }
27
+ }
28
+
29
+ <%= config[:s3_bucket_cors_rule] %>
30
+ }
31
+
32
+ resource "aws_iam_user" "docspring_s3_user" {
33
+ name = "<%= config.fetch(:stack_name) %>-<%= config.fetch(:s3_bucket_name) %>"
34
+ }
35
+
36
+ resource "aws_iam_access_key" "docspring_user_access_key" {
37
+ user = aws_iam_user.docspring_s3_user.name
38
+ }
39
+
40
+ resource "aws_iam_user_policy" "docspring_user_s3_policy" {
41
+ name = "docspring_user_s3_policy"
42
+ user = aws_iam_user.docspring_s3_user.name
43
+
44
+ policy = jsonencode({
45
+ "Version": "2012-10-17",
46
+ "Statement": [
47
+ {
48
+ "Effect": "Allow",
49
+ "Action": [
50
+ "s3:PutObject",
51
+ "s3:PutObjectAcl",
52
+ "s3:GetObject",
53
+ "s3:GetObjectAcl",
54
+ "s3:DeleteObject"
55
+ ],
56
+ "Resource": [
57
+ "arn:aws:s3:::<%= config.fetch(:stack_name) %>-<%= config.fetch(:s3_bucket_name) %>/*",
58
+ ]
59
+ },
60
+ {
61
+ "Effect": "Allow",
62
+ "Action": [
63
+ "kms:Encrypt",
64
+ "kms:Decrypt",
65
+ "kms:ReEncrypt*",
66
+ "kms:GenerateDataKey*",
67
+ "kms:DescribeKey"
68
+ ],
69
+ "Resource": "*"
70
+ }
71
+ ]
72
+ })
73
+ }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: convox_installer
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Form Applications Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-04-02 00:00:00.000000000 Z
11
+ date: 2022-04-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -109,6 +109,9 @@ files:
109
109
  - spec/lib/convox_installer/config_spec.rb
110
110
  - spec/lib/convox_installer/requirements_spec.rb
111
111
  - spec/spec_helper.rb
112
+ - terraform/elasticache.tf.erb
113
+ - terraform/rds.tf.erb
114
+ - terraform/s3_bucket.tf.erb
112
115
  homepage: https://github.com/FormAPI/convox_installer
113
116
  licenses:
114
117
  - MIT