local-development-gateway 0.2.2 → 0.2.4

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: 31c635514c35283b8ebab124d6a9f9ac8e17e8de5faaeb8573b0c73153b3efd4
4
- data.tar.gz: 946d3a12335cff8a00f10a5ac330de37b5f5ec509a33779e44cefc265b8c0554
3
+ metadata.gz: 769fae659862a5e04bbb1f4c1d572552afe3f777f4bbbd4ec007fa02860639c6
4
+ data.tar.gz: e704decf7f614cb01a2a74ae1601fa6e925da9c243b3b4015ea9760e8ec45ae0
5
5
  SHA512:
6
- metadata.gz: 04b07e9aeb612b723744759bebb4f8e19319ac885a9dff3163446eaebcaf9a424cb203c36c89649a2378d1db675c5a167aade5bc0f33e6c82ccb70c11d7bcec6
7
- data.tar.gz: 28528704687762d7a053b62f7c1fc1e7f5af76700e4c332efadaef32cb5c594e420af8bbd53803a1e3d0cecdafa3d892ceeee5edbac100aa388ea72e98a80de1
6
+ metadata.gz: 247d626136bee33123253a419d036812041b6e5df6094b14b262f5bc6be7ac629083aea1b717b64f8b29afce69b245b739fff47c0bcebc53c80b496351dbbb50
7
+ data.tar.gz: 6954a6378a4aecf2d6c3d6eef08d8be001abfd2fc5c1758314b37b777d795ea6e7e7f16ec647eb128b0930353128f8c6f7be25539b1444d629f2dddd35e2ec7a
data/README.md CHANGED
@@ -106,6 +106,21 @@ command that must not start a missing gateway, pass
106
106
  `ensure_running: false`; the block still runs and unused-gateway cleanup is
107
107
  attempted.
108
108
 
109
+ ### Installed versus active versions
110
+
111
+ `bundle exec local-development-gateway status` reports the invoking gem's installed version separately from each running shared service's active version, provenance, and mounted asset path. Healthy CLI `up`/`ensure` reuse also prints these diagnostics without recreating containers. Ruby callers can inspect `LocalDevelopmentGateway.status`; `ensure_running` still returns `:reused` for healthy services, and `ready?` remains a health check, not an artifact-version check.
112
+
113
+ For the database router, the active version comes from a record written by the Ruby process after loading the library at startup. For the HTTP gateway, it is the gateway configuration artifact version recorded in a container-creation label, not the Traefik image version. Missing records or labels are `unknown`. A legacy mount path containing a gem version is useful diagnostic evidence but does not prove which Ruby code or configuration the running process loaded. Neither version record is a content hash or proof that bind-mounted files have remained unchanged.
114
+
115
+ Installing a newer gem does not update already-running services. A known difference is reported as `mismatch`; legacy `unknown` provenance cannot establish that an upgrade is active. Coordinate restart timing with all consumers before explicitly updating only the database router:
116
+
117
+ ```sh
118
+ bundle exec local-development-gateway up --no-deps --force-recreate database-router
119
+ bundle exec local-development-gateway status
120
+ ```
121
+
122
+ Recreating the shared database router interrupts existing database client sessions across every consuming project/worktree. This command leaves the HTTP gateway and consumer database containers/volumes alone. If the HTTP gateway itself needs an update, separately coordinate and explicitly select `gateway` instead. Do not use `down` for a router-only update. After updating, confirm the selected service's active version is current and exercise an encrypted connection to the intended database; readiness alone is insufficient release verification.
123
+
109
124
  ## Worktree database services
110
125
 
111
126
  The gateway includes protocol drivers for encrypted SQL Server and PostgreSQL
@@ -133,6 +148,21 @@ substitutes for private aliases. Database cloning is independent of this
133
148
  hostname correction: it selects source/destination containers explicitly and
134
149
  connects locally.
135
150
 
151
+ ### Private database alias length
152
+
153
+ The complete `${COMPOSE_PROJECT_NAME}-<database-service>` alias is one DNS label. [DNS limits each label to 63 octets](https://www.rfc-editor.org/rfc/rfc1035.html#section-2.3.4); for these ASCII names, that is 63 characters. Budget the project name against the longest database service suffix in the stack: `63 - 1 - <service-name length>`.
154
+
155
+ | Database service | Appended suffix | Maximum project-name length |
156
+ | --- | --- | ---: |
157
+ | `db` | `-db` | 60 |
158
+ | `db_trav_com` | `-db_trav_com` | 51 |
159
+
160
+ A 60-character project name plus `-db` fits exactly; 61 characters produces a 64-character alias. With `-db_trav_com`, the corresponding boundary is 51/52 characters. These are DNS budgets, not a claim that Compose rejects oversized aliases: `docker compose config` can render an alias that exceeds the limit. Configuration rendering alone does not prove DNS resolution or database connectivity.
161
+
162
+ Use the effective project name selected by [Compose's native precedence](https://docs.docker.com/compose/how-tos/project-name/): `-p`, then `COMPOSE_PROJECT_NAME`, top-level `name:`, and the directory-derived default. Inspect the rendered `name`, database aliases, and application/job/test host settings through the consumer's documented wrapper and selected Compose files. Do not implement a separate project-name resolver, truncate/hash names automatically, or change existing short identities.
163
+
164
+ For a new disposable stack, explicitly choose a unique project name within the budget before creating resources. For an existing stack, changing its project name can select different project-scoped volumes and appear to lose its database. Preserve the current project identity and data; any necessary rename requires an explicit consumer-owned data migration, not an automatic gateway correction. Keep qualified aliases private and desktop gateway hostnames unchanged. The gem neither validates nor shortens consumer aliases; upgrading it does not repair an oversized name.
165
+
136
166
  ### Canonical development and test Compose pattern
137
167
 
138
168
  Merge these entries into the project's existing development base and gateway
@@ -294,7 +324,8 @@ retaining cancellation-aware PostgreSQL routing despite Traefik's STARTTLS suppo
294
324
 
295
325
  1. Give each concurrent checkout a distinct Compose project name and desktop
296
326
  route hostname. Qualify every API/job/test database host with
297
- `${COMPOSE_PROJECT_NAME}-<database-service>` using its existing driver setting.
327
+ `${COMPOSE_PROJECT_NAME}-<database-service>` using its existing driver setting,
328
+ within the [suffix-specific DNS budget](#private-database-alias-length).
298
329
  2. Advertise each qualified alias only on its private `default` network;
299
330
  preserve it through mapping-form network merges in base-only and gateway modes.
300
331
  3. Remove fixed database publications from development/test stacks; retain
@@ -419,6 +450,15 @@ It mounts the Docker socket so it can discover labelled containers. A read-only
419
450
  | A project uses normal ports unexpectedly | Confirm the gateway container is running and has `local-gateway=true`. |
420
451
  | Authentication rejects the callback | Confirm the generated hostname matches the allowed wildcard callback pattern. |
421
452
 
453
+ ## Release 0.2.4
454
+
455
+ - Document suffix-specific DNS length budgets for private database aliases and data-safe project-name handling. No alias validation, shortening, routing, or lifecycle behavior changes.
456
+
457
+ ## Release 0.2.3
458
+
459
+ - Status and healthy CLI reuse distinguish the installed gem version from each active shared service, reporting mismatches and unknown legacy provenance without restarting services.
460
+ - Scoped router update instructions explain shared database session interruption and keep restart timing under caller control. Health readiness and Ruby lifecycle return values remain unchanged.
461
+
422
462
  ## Release 0.2.2
423
463
 
424
464
  - Canonical SQL Server/PostgreSQL Compose integration supports concurrent
data/docker-compose.yml CHANGED
@@ -13,6 +13,7 @@ services:
13
13
  start_period: 1s
14
14
  labels:
15
15
  local-gateway: "true"
16
+ local-gateway.version: "${LOCAL_DEVELOPMENT_GATEWAY_VERSION:-unknown}"
16
17
  ports:
17
18
  - "127.0.0.1:80:80"
18
19
  restart: unless-stopped
@@ -29,7 +30,7 @@ services:
29
30
  - -I/app/lib
30
31
  - -rlocal_development_gateway
31
32
  - -e
32
- - LocalDevelopmentGateway::DatabaseRouter.run
33
+ - File.write("/tmp/local-development-gateway-version", LocalDevelopmentGateway::VERSION); LocalDevelopmentGateway::DatabaseRouter.run
33
34
  healthcheck:
34
35
  test:
35
36
  - CMD
@@ -43,6 +44,7 @@ services:
43
44
  start_period: 1s
44
45
  labels:
45
46
  local-gateway: "true"
47
+ local-gateway.version: "${LOCAL_DEVELOPMENT_GATEWAY_VERSION:-unknown}"
46
48
  ports:
47
49
  - "127.0.0.1:1433:1433"
48
50
  - "127.0.0.1:5432:5432"
@@ -44,6 +44,8 @@ preserves `default.aliases`. Configure all application/job/test contexts with
44
44
  their appropriate aliases, existing credentials, and intended database names;
45
45
  test contexts still select their own test database.
46
46
 
47
+ The README's [private-alias length contract](../../README.md#private-database-alias-length) defines the DNS budget for each service suffix. Native Compose project selection and existing data identities remain authoritative; the gateway does not shorten or rename consumer projects.
48
+
47
49
  Gateway-mode development and test stacks publish no database host ports.
48
50
  Container consumers work with the base alone. Desktop access without the
49
51
  gateway is an explicit project-local port-publishing overlay, not an inherited
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LocalDevelopmentGateway
4
- VERSION = "0.2.2"
4
+ VERSION = "0.2.4"
5
5
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "open3"
4
+ require "json"
4
5
  require "local_development_gateway/version"
5
6
 
6
7
  module LocalDevelopmentGateway
@@ -29,16 +30,16 @@ module LocalDevelopmentGateway
29
30
  end
30
31
 
31
32
  class Docker
32
- def call(*args, capture: true)
33
+ def call(*args, capture: true, env: {})
33
34
  command = ["docker", *args]
34
35
  if capture
35
- stdout, stderr, status = Open3.capture3(*command)
36
+ stdout, stderr, status = Open3.capture3(env, *command)
36
37
  unless status.success?
37
38
  raise DockerError.new(command, stderr.empty? ? stdout : stderr)
38
39
  end
39
40
 
40
41
  stdout
41
- elsif system(*command)
42
+ elsif system(env, *command)
42
43
  true
43
44
  else
44
45
  raise DockerError.new(command, "")
@@ -85,7 +86,17 @@ module LocalDevelopmentGateway
85
86
  end
86
87
 
87
88
  def status
88
- compose("ps", "--all")
89
+ output = compose("ps", "--all")
90
+ output << "\nInstalled gateway version: #{VERSION}\n"
91
+ SERVICE_NAMES.each do |service|
92
+ ids = gateway_container_ids(service: service, status: "running")
93
+ if ids.empty?
94
+ output << "#{service}: not running; active=unknown\n"
95
+ else
96
+ ids.each { |id| output << service_version_status(service, id) }
97
+ end
98
+ end
99
+ output
89
100
  end
90
101
 
91
102
  def logs(*args, follow: false)
@@ -122,12 +133,56 @@ module LocalDevelopmentGateway
122
133
  "--file",
123
134
  COMPOSE_FILE,
124
135
  *args,
125
- capture: capture
136
+ capture: capture,
137
+ env: {
138
+ "LOCAL_DEVELOPMENT_GATEWAY_VERSION" => VERSION
139
+ }
126
140
  )
127
141
  end
128
142
 
129
143
  private
130
144
 
145
+ def service_version_status(service, id)
146
+ container = JSON.parse(@runner.call("inspect", id)).fetch(0)
147
+ if service == "database-router"
148
+ version = router_version(id)
149
+ provenance = "process-start record"
150
+ asset_target = "/app/lib"
151
+ else
152
+ version = container.dig("Config", "Labels", "local-gateway.version")
153
+ provenance = "container-creation label"
154
+ asset_target = "/etc/traefik/traefik.yml"
155
+ end
156
+ version = "unknown" if version.nil? || version.empty?
157
+ comparison = version_comparison(version)
158
+ mount =
159
+ container
160
+ .fetch("Mounts", [])
161
+ .find { |entry| entry["Destination"] == asset_target }
162
+ source = "unknown"
163
+ source = mount.fetch("Source") if mount
164
+ "#{service} (#{id[0, 12]}): active=#{version}; #{comparison}; " \
165
+ "provenance=#{provenance}; mounted assets=#{source}\n"
166
+ end
167
+
168
+ def router_version(id)
169
+ @runner.call(
170
+ "exec",
171
+ id,
172
+ "cat",
173
+ "/tmp/local-development-gateway-version"
174
+ ).strip
175
+ rescue DockerError
176
+ nil
177
+ end
178
+
179
+ def version_comparison(version)
180
+ return "unknown" if version == "unknown"
181
+ return "current" if version == VERSION
182
+
183
+ "mismatch (installed=#{VERSION}); explicit service recreation required"
184
+ end
185
+
131
186
  def wait_until_ready
132
187
  deadline = @clock.call + @timeout
133
188
  until ready?
@@ -233,7 +288,7 @@ module LocalDevelopmentGateway
233
288
 
234
289
  Commands:
235
290
  up, ensure Start or reuse the shared gateway
236
- status Show the gateway Compose status
291
+ status Show Compose status and installed/active gateway versions
237
292
  ready Check gateway readiness
238
293
  logs Follow gateway logs
239
294
  down, stop Stop the gateway only when unused
@@ -260,7 +315,8 @@ module LocalDevelopmentGateway
260
315
 
261
316
  case command
262
317
  when "up", "ensure"
263
- @client.ensure_running(*@argv)
318
+ result = @client.ensure_running(*@argv)
319
+ @output.write(@client.status) if result == :reused
264
320
  when "status"
265
321
  @output.write(@client.status)
266
322
  when "ready"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: local-development-gateway
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marlen Brunner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-10-14 00:00:00.000000000 Z
11
+ date: 2026-10-22 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Ruby API and CLI for starting, monitoring, and conditionally stopping
14
14
  the shared Docker gateway.