putpaws 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d38b16d5a4fc24915b8bde117f9fda98d0fb6418989eebf98385e86ad724dd32
4
- data.tar.gz: c3ee3c20989376d82da51e970aa23ab2995af32c39b0e37524c7fa915cf9a8f9
3
+ metadata.gz: d6d053460a5c78abad1e20f800e26aae412b0768b47a182380c9e876ebb616f9
4
+ data.tar.gz: bbb6cd4158d334b4029b17cb6141cf52e6e490317f9d438d2af0d43856876728
5
5
  SHA512:
6
- metadata.gz: e2d00b74e706f5c9192a455bd7919845c4afeb06d2c3db9cd17c2030689cdc2858cc439efb6dd360cc8b9d4eb3fe6fceb6e9a62fb361de1ff0b8d30be4be10c2
7
- data.tar.gz: 49fbf55b6659a02a563792d4ab406bbe1f583e84abf82eee10c5c52b12bbcb3da42d539fb1346dc10dd4a4a6ba70df427b8ddc5f1b5edf7c07e775c4b314fb72
6
+ metadata.gz: bdf0ff9f7a76d786532fe1b4a13991bd0542d2d584c9a1158996ff84f0479913062cc8b5ce9856a27fe3b7d83978e3c6d9e8816753733ac0a43db9e20ea9986b
7
+ data.tar.gz: b3714483e120918f2c777b1dd8f0b010efc4f26268a283a9f1d1a426f0ea0e1b214c033b2fb0e19d2c5b77a498a22f373a994c8ad16620a792993b528e6faa37
data/README.md CHANGED
@@ -49,12 +49,40 @@ Presets are searched in this order: project local (`.putpaws/provisioning/preset
49
49
  user global (directories in `PUTPAWS_PRESETS_PATH`, then `~/.putpaws/presets/`), and the ones bundled in the gem.
50
50
  Whichever you pick on `ready` is copied into the project so the project owns its snapshot.
51
51
 
52
+ Bundled presets (ECS Fargate, `awsvpc`):
53
+
54
+ - `rails` (default) ... one `app` container on port 8080. Assumes the Rails 8 default Dockerfile,
55
+ where [Thruster](https://github.com/basecamp/thruster) fronts Puma and does what an nginx sidecar used to
56
+ (HTTP/2, asset caching / compression, X-Sendfile); TLS terminates at the ALB.
57
+ Thruster listens on port 80 by default, but the Rails Dockerfile runs as a non-root user and
58
+ Fargate does not let non-root processes bind ports below 1024 (unlike local Docker, which sets
59
+ `net.ipv4.ip_unprivileged_port_start=0`), so the preset sets `HTTP_PORT=8080` and
60
+ `container_port: 8080`; point the ALB target group at 8080. The Dockerfile needs no change
61
+ (`EXPOSE 80` is metadata only). Older Rails apps work too by adding the `thruster` gem and
62
+ starting with `bin/thrust bin/rails server`, or by setting `container_port` to the Puma port
63
+ and dropping `HTTP_PORT`.
64
+ - `rails-nginx` ... same `app` task definition plus a `web` one with an nginx sidecar on port 80.
65
+ The sidecar is a stock nginx image with no proxy config, so point `nginx_image` at your own image
66
+ that proxies to `localhost:3000`. Keep this only when you need something Thruster does not do
67
+ (maintenance pages, custom rewrites, proxy-level auth).
68
+
69
+ Need another task definition (Ex: a batch worker)? Add an entry to `task_definitions` of the
70
+ copied preset with its own template; `up` creates it and writes an `application.json` entry for it.
71
+
52
72
  Security groups are either specified (one or more existing ones; putpaws never touches their rules)
53
73
  or a new one is created for the service when left empty on `ready`.
54
74
 
55
75
  When the service step completes, the new service is reflected into
56
76
  `.putpaws/application.json` and `.putpaws/infra.json` (diff is shown and confirmed),
57
77
  so `ecs:attach`, `ecs:shell`, `ecs:run` and `log:*` work immediately.
78
+ One entry is written per task definition of the preset, keyed by the task definition
79
+ family `{service_name}-{suffix}` (Ex: `awesome-api-staging-app` / `awesome-api-staging-web`),
80
+ each pointing at the `infra.target` of the same name. Only the entry of the preset's
81
+ `service_task_definition` carries `service` (for `ecs:deploy` / `ecs:attach`); the others have
82
+ `service: null` and are meant for one-off tasks (`ecs:run` / `ecs:shell` / scheduler),
83
+ so with `rails-nginx` `db:migrate` keeps running on the plain `app` task definition while the
84
+ service runs `web` (nginx + app). `infra.network` is written once as `{service_name}`.
85
+ Existing keys are never deleted; remove stale ones by hand.
58
86
  Secrets are handled by reference only: task definitions point to existing
59
87
  SSM parameters (Ex: `/{service_name}/RAILS_MASTER_KEY`) and putpaws never touches the values.
60
88
 
@@ -64,7 +92,7 @@ so `scheduler:deploy` works any time by just writing `.putpaws/schedule.json`.
64
92
  The CPU architecture is declared once as `cpu_architecture` (`ARM64` / `X86_64`) in the preset
65
93
  defaults (overridable per service via `overrides`), and applied consistently to both
66
94
  task definitions (`runtimePlatform`) and CodeBuild (environment type and image).
67
- The bundled preset defaults to `ARM64`; projects whose copied preset predates this setting
95
+ The bundled presets default to `ARM64`; projects whose copied preset predates this setting
68
96
  fall back to `X86_64` so nothing changes silently. When switching an existing service to ARM,
69
97
  run the CI build right after `up` so an ARM image exists before tasks roll
70
98
  (`devops.environment_type` / `environment_image` can override the derivation for staged migration).
@@ -225,19 +253,35 @@ gem 'putpaws'
225
253
 
226
254
  `.putpaws/application.json`
227
255
 
256
+ Keys are free-form names you pass to the CLI. The ones written by `putpaws up` follow
257
+ `{service_name}-{suffix}`: `-app` for one-off tasks (`service: null`), `-web` for the
258
+ long-running service.
259
+
228
260
  ```
229
261
  {
230
- "awesome-api-staging": {
262
+ "awesome-api-staging-app": {
231
263
  "region": "ap-northeast-1",
232
264
  "cluster": "cluster-staging",
233
265
  "service": null,
234
- "task_name_prefix": "awesome-api",
235
- "log_group_prefix": "/ecs/awesome/awesome-api-staging",
266
+ "task_name_prefix": "awesome-api-staging-app",
267
+ "log_group_prefix": "/ecs/awesome-api-staging",
268
+ "log_region": null,
269
+ "build_log_group_prefix": "/aws/codebuild/awesome-api-staging",
270
+ "build_project_name_prefix": "awesome-api-staging",
271
+ "network": "awesome-api-staging",
272
+ "target": "awesome-api-staging-app"
273
+ },
274
+ "awesome-api-staging-web": {
275
+ "region": "ap-northeast-1",
276
+ "cluster": "cluster-staging",
277
+ "service": "awesome-api-staging",
278
+ "task_name_prefix": "awesome-api-staging-web",
279
+ "log_group_prefix": "/ecs/awesome-api-staging",
236
280
  "log_region": null,
237
281
  "build_log_group_prefix": "/aws/codebuild/awesome-api-staging",
238
282
  "build_project_name_prefix": "awesome-api-staging",
239
- "network": "awesome-private-staging",
240
- "target": "awesome-staging"
283
+ "network": "awesome-api-staging",
284
+ "target": "awesome-api-staging-web"
241
285
  },
242
286
  "awesome-api-production": {
243
287
  "region": "ap-northeast-1",
@@ -8,6 +8,15 @@ module Putpaws
8
8
  # Reflects provisioned resources into .putpaws/application.json and
9
9
  # .putpaws/infra.json. Only the keys of the target service are touched.
10
10
  # Shows a diff and asks for confirmation before writing.
11
+ #
12
+ # One application entry and one infra.target entry are written per task
13
+ # definition of the preset, keyed by the task definition family
14
+ # (`<service_name>-<suffix>`). The entry whose suffix is the preset's
15
+ # `service_task_definition` carries `service` so that ecs:deploy /
16
+ # ecs:attach address the long-running service; the others are for
17
+ # one-off tasks (ecs:run / ecs:shell / scheduler) and have `service: null`.
18
+ # infra.network is written once per service as it does not depend on the
19
+ # task definition.
11
20
  class ConfigWriter
12
21
  attr_reader :config, :state, :prompt, :io
13
22
  def initialize(config:, state:, prompt:, io: $stdout)
@@ -17,17 +26,18 @@ module Putpaws
17
26
  @io = io
18
27
  end
19
28
 
20
- def application_entry
29
+ def application_entry(entry)
30
+ family = config.task_definition_family(entry[:suffix])
21
31
  {
22
32
  region: config.region,
23
33
  cluster: config.cluster_name,
24
- service: config.service_name,
25
- task_name_prefix: config.service_name,
34
+ service: service_entry?(entry) ? config.service_name : nil,
35
+ task_name_prefix: family,
26
36
  log_group_prefix: config.log_group,
27
37
  build_log_group_prefix: config.build_log_group,
28
38
  build_project_name_prefix: config.build_project_name,
29
39
  network: config.service_name,
30
- target: config.service_name,
40
+ target: family,
31
41
  }
32
42
  end
33
43
 
@@ -39,25 +49,32 @@ module Putpaws
39
49
  }
40
50
  end
41
51
 
42
- def target_entry
52
+ def target_entry(entry)
43
53
  {
44
54
  # Used by EventBridge Scheduler (scheduler:deploy).
45
55
  scheduler_role: config.roles[:scheduler_role_arn],
46
56
  cluster: state.resources[:cluster_arn],
47
57
  # Revision-less on purpose: always run the latest ACTIVE revision.
48
- task_definition: config.service_task_definition_family,
49
- container_name: config.service_container_name,
58
+ task_definition: config.task_definition_family(entry[:suffix]),
59
+ container_name: entry[:container_name] || 'app',
50
60
  }
51
61
  end
52
62
 
53
63
  def apply!
54
- write_key(path('application.json'), [config.service_name.to_sym], application_entry)
55
64
  write_key(path('infra.json'), [:network, config.service_name.to_sym], network_entry)
56
- write_key(path('infra.json'), [:target, config.service_name.to_sym], target_entry)
65
+ config.preset.task_definitions.each do |entry|
66
+ key = config.task_definition_family(entry[:suffix]).to_sym
67
+ write_key(path('application.json'), [key], application_entry(entry))
68
+ write_key(path('infra.json'), [:target, key], target_entry(entry))
69
+ end
57
70
  end
58
71
 
59
72
  private
60
73
 
74
+ def service_entry?(entry)
75
+ entry[:suffix] == config.preset.service_task_definition
76
+ end
77
+
61
78
  def path(basename)
62
79
  Pathname.new(config.path_prefix).join(basename)
63
80
  end
@@ -6,6 +6,8 @@ module Putpaws
6
6
  module Provision
7
7
  class Preset
8
8
  BUILTIN_DIR = File.expand_path('presets', __dir__)
9
+ # Preselected on `ready`: Rails 8 with Thruster, app container only.
10
+ DEFAULT = 'rails'
9
11
 
10
12
  def self.local_dir(path_prefix: '.putpaws')
11
13
  Pathname.new(path_prefix).join('provisioning', 'presets')
@@ -0,0 +1,35 @@
1
+ {
2
+ "defaults": {
3
+ "cpu_architecture": "ARM64",
4
+ "cpu": "256",
5
+ "memory": "512",
6
+ "desired_count": 1,
7
+ "container_port": 8080,
8
+ "log_retention_days": 30,
9
+ "image_tag": "latest",
10
+ "assign_public_ip": "DISABLED",
11
+ "env": {
12
+ "RAILS_ENV": "production",
13
+ "RACK_ENV": "production",
14
+ "RAILS_LOG_TO_STDOUT": "1",
15
+ "HTTP_PORT": "8080"
16
+ },
17
+ "secrets": {
18
+ "RAILS_MASTER_KEY": "{ssm_parameter_prefix}/RAILS_MASTER_KEY"
19
+ }
20
+ },
21
+ "task_definitions": [
22
+ { "suffix": "app", "template": "taskdef-app.json.erb", "container_name": "app" }
23
+ ],
24
+ "service_task_definition": "app",
25
+ "devops_defaults": {
26
+ "source_type": "GITHUB",
27
+ "environment_images": {
28
+ "ARM64": "aws/codebuild/amazonlinux-aarch64-standard:3.0",
29
+ "X86_64": "aws/codebuild/standard:7.0"
30
+ },
31
+ "compute_type": "BUILD_GENERAL1_SMALL",
32
+ "privileged_mode": true,
33
+ "buildspec": "buildspec.yml"
34
+ }
35
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "family": "<%= service_name %>-app",
3
+ "requiresCompatibilities": ["FARGATE"],
4
+ "networkMode": "awsvpc",
5
+ "runtimePlatform": {
6
+ "cpuArchitecture": "<%= cpu_architecture %>",
7
+ "operatingSystemFamily": "LINUX"
8
+ },
9
+ "cpu": "<%= cpu %>",
10
+ "memory": "<%= memory %>",
11
+ "executionRoleArn": "<%= execution_role_arn %>",
12
+ "taskRoleArn": "<%= task_role_arn %>",
13
+ "containerDefinitions": [
14
+ {
15
+ "name": "app",
16
+ "image": "<%= image %>",
17
+ "essential": true,
18
+ "portMappings": [
19
+ { "containerPort": <%= container_port %>, "protocol": "tcp" }
20
+ ],
21
+ "environment": <%= env_json %>,
22
+ "secrets": <%= secrets_json %>,
23
+ "logConfiguration": {
24
+ "logDriver": "awslogs",
25
+ "options": {
26
+ "awslogs-group": "<%= log_group %>",
27
+ "awslogs-region": "<%= region %>",
28
+ "awslogs-stream-prefix": "app"
29
+ }
30
+ }
31
+ }
32
+ ]
33
+ }
@@ -22,8 +22,10 @@ namespace :provision do
22
22
  end
23
23
 
24
24
  region = prompt.ask("Region:", default: 'ap-northeast-1')
25
- preset_choices = Putpaws::Provision::Preset.catalog.map{|e| ["#{e[:name]} (#{e[:source]})", e[:name]]}.to_h
26
- preset_name = prompt.select("Preset:", preset_choices)
25
+ preset_catalog = Putpaws::Provision::Preset.catalog
26
+ preset_choices = preset_catalog.map{|e| ["#{e[:name]} (#{e[:source]})", e[:name]]}.to_h
27
+ preset_default = (preset_catalog.index{|e| e[:name] == Putpaws::Provision::Preset::DEFAULT} || 0) + 1
28
+ preset_name = prompt.select("Preset:", preset_choices, default: preset_default)
27
29
 
28
30
  puts "--- Basic infra (existing resources; used to generate IAM policies) ---"
29
31
  vpc_id = prompt.ask("VPC ID:"){|q| q.required(true)}
@@ -1,3 +1,3 @@
1
1
  module Putpaws
2
- VERSION = "0.1.4"
2
+ VERSION = "0.1.6"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: putpaws
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - metheglin
@@ -182,6 +182,8 @@ files:
182
182
  - lib/putpaws/provision/presets/rails-nginx/preset.json
183
183
  - lib/putpaws/provision/presets/rails-nginx/templates/taskdef-app.json.erb
184
184
  - lib/putpaws/provision/presets/rails-nginx/templates/taskdef-web.json.erb
185
+ - lib/putpaws/provision/presets/rails/preset.json
186
+ - lib/putpaws/provision/presets/rails/templates/taskdef-app.json.erb
185
187
  - lib/putpaws/provision/provision_config.rb
186
188
  - lib/putpaws/provision/provision_task.rb
187
189
  - lib/putpaws/provision/resources/base.rb