local-development-gateway 0.1.0
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 +7 -0
- data/README.md +173 -0
- data/bin/local-development-gateway +6 -0
- data/config/traefik.yml +11 -0
- data/docker-compose.yml +27 -0
- data/lib/local_development_gateway/version.rb +5 -0
- data/lib/local_development_gateway.rb +270 -0
- metadata +53 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d72f55ac50271b2247bd8eb5189f4b6c25c30a62a8cb0d451cecc38b050ea98e
|
|
4
|
+
data.tar.gz: ea4e0fda3f4f88cb39666580be77960eeab97994adfd599e3b4e2db02c96f5cc
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 6bbf54e670547b4ff3a4b332094493ad1c825df43a50d29079b6ebfe88d85922ad6a1d73877a8de28ce22fcf2974b48db28c70250b3389d77a062a6fd14a19d9
|
|
7
|
+
data.tar.gz: 74cfbf75708d8e46a47e19ffc4a94b732e502372edfc565702c98e7ab8177e98208e543d3be45c822a6ed76bbdd83cb56bf3b50931efb334c2d3516dff4228e7
|
data/README.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Local Development Gateway
|
|
2
|
+
|
|
3
|
+
One shared loopback-only gateway for browser-facing Docker development services.
|
|
4
|
+
|
|
5
|
+
It lets several local projects use stable hostnames instead of competing for browser ports. It is intended to run once at the beginning of a development day and remain running while projects start and stop.
|
|
6
|
+
|
|
7
|
+
## What problem this solves
|
|
8
|
+
|
|
9
|
+
Without a gateway, two projects commonly both try to publish a web service on port `8080` and a backend service on port `3000`. Only one can start.
|
|
10
|
+
|
|
11
|
+
With this gateway running, a project can publish its internal web service through a generated hostname instead:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
http://port-35053.traditional-knowledge.localhost
|
|
15
|
+
|
|
|
16
|
+
v
|
|
17
|
+
Local Development Gateway on 127.0.0.1:80
|
|
18
|
+
|
|
|
19
|
+
v
|
|
20
|
+
Traditional Knowledge web container on internal port 8080
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`port-35053` is derived from Traditional Knowledge's generated `WEB_PORT` value. It identifies the project instance; the browser reaches the web service through the gateway on port `80`, not directly on port `35053`.
|
|
24
|
+
|
|
25
|
+
The generated hostname is a local `*.localhost` name. It resolves to loopback and does not require a public domain, external DNS provider, or internet-facing listener.
|
|
26
|
+
|
|
27
|
+
## Start once per day
|
|
28
|
+
|
|
29
|
+
Use the small repository wrapper for the common lifecycle commands:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
bin/dev up
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Check the gateway:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
bin/dev status
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Follow its logs:
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
bin/dev logs
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Stop it only when no participating local project needs it:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
bin/dev down
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The wrapper also passes unrecognized arguments directly to `docker compose`, so
|
|
54
|
+
`bin/dev config` and `bin/dev up --force-recreate` remain available.
|
|
55
|
+
|
|
56
|
+
Port `80` on `127.0.0.1` must be free before starting. The gateway never binds to a non-loopback address.
|
|
57
|
+
|
|
58
|
+
## Install as a gem
|
|
59
|
+
|
|
60
|
+
Consuming projects do not need a gateway checkout. Add the released gem to
|
|
61
|
+
their bundle and pin the compatible minor version:
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
gem "local-development-gateway", "~> 0.1.0"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The gem packages the gateway Compose file and pinned Traefik configuration.
|
|
68
|
+
Its executable provides the same lifecycle commands:
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
bundle exec local-development-gateway up
|
|
72
|
+
bundle exec local-development-gateway status
|
|
73
|
+
bundle exec local-development-gateway logs
|
|
74
|
+
bundle exec local-development-gateway down
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`local-development-gateway` uses the installed asset path, the
|
|
78
|
+
`local-gateway` Compose project and network labels, and the pinned minimum Ruby
|
|
79
|
+
version declared by the gem. Upgrade the version constraint when a new
|
|
80
|
+
compatible release is published. The repository's `bin/dev` is only a thin
|
|
81
|
+
wrapper around this executable API.
|
|
82
|
+
|
|
83
|
+
## Use case: Traditional Knowledge beside WRAP
|
|
84
|
+
|
|
85
|
+
1. Start this gateway once.
|
|
86
|
+
2. Run `dev up` in Traditional Knowledge. It writes role-based generated values to `.env.development.local`, including `WEB_PORT`, `BACKEND_PORT`, and `WEB_HOSTNAME=port-<WEB_PORT>.traditional-knowledge.localhost`.
|
|
87
|
+
3. With the gateway running, Traditional Knowledge's web container joins `local-gateway`, its direct web publishing is removed, and the generated hostname routes through the gateway on `127.0.0.1:80`.
|
|
88
|
+
4. Open `WEB_HOSTNAME` from `.env.development.local`.
|
|
89
|
+
5. When WRAP adopts the same contract, start it normally. It can then use its own generated route labels and hostname through this same gateway.
|
|
90
|
+
|
|
91
|
+
Each project receives an independent hostname, while the gateway owns the single browser port. The generated `WEB_PORT` is an identity seed in gateway mode, not the browser-facing port.
|
|
92
|
+
|
|
93
|
+
Without the gateway, Traditional Knowledge publishes the same generated values directly, including `localhost:<WEB_PORT>` for the web service and `localhost:<BACKEND_PORT>` for the backend. `dev up` regenerates unavailable assignments; deleting `.env.development.local` forces a fresh assignment.
|
|
94
|
+
|
|
95
|
+
## Contract for participating projects
|
|
96
|
+
|
|
97
|
+
The gateway:
|
|
98
|
+
|
|
99
|
+
- listens only on `127.0.0.1:80`;
|
|
100
|
+
- creates the external Docker network `local-gateway`;
|
|
101
|
+
- discovers only services explicitly labelled for routing;
|
|
102
|
+
- has the Docker label `local-gateway=true` so a project can detect it.
|
|
103
|
+
|
|
104
|
+
A participating project writes role-based generated values to its local `.env.development.local`:
|
|
105
|
+
|
|
106
|
+
```dotenv
|
|
107
|
+
WEB_PORT=35053
|
|
108
|
+
BACKEND_PORT=38261
|
|
109
|
+
WEB_HOSTNAME=port-35053.traditional-knowledge.localhost
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
With the gateway available, its web service joins `local-gateway` and supplies explicit route labels. Use a project-prefixed identifier containing the published `BACKEND_PORT` for the internal router/service name; do not generate a separate `STACK_IDENTIFIER`:
|
|
113
|
+
|
|
114
|
+
```yaml
|
|
115
|
+
services:
|
|
116
|
+
web:
|
|
117
|
+
networks:
|
|
118
|
+
- default
|
|
119
|
+
- local-gateway
|
|
120
|
+
labels:
|
|
121
|
+
- "traefik.enable=true"
|
|
122
|
+
- "traefik.docker.network=local-gateway"
|
|
123
|
+
- "traefik.http.routers.<project>-<backend-port>.rule=Host(`<web-hostname>`)"
|
|
124
|
+
- "traefik.http.routers.<project>-<backend-port>.entrypoints=web"
|
|
125
|
+
- "traefik.http.routers.<project>-<backend-port>.service=<project>-<backend-port>"
|
|
126
|
+
- "traefik.http.services.<project>-<backend-port>.loadbalancer.server.port=8080"
|
|
127
|
+
|
|
128
|
+
networks:
|
|
129
|
+
local-gateway:
|
|
130
|
+
external: true
|
|
131
|
+
name: local-gateway
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Without the gateway, publish the same generated values directly:
|
|
135
|
+
|
|
136
|
+
```yaml
|
|
137
|
+
services:
|
|
138
|
+
web:
|
|
139
|
+
ports:
|
|
140
|
+
- "127.0.0.1:${WEB_PORT}:8080"
|
|
141
|
+
backend:
|
|
142
|
+
ports:
|
|
143
|
+
- "127.0.0.1:${BACKEND_PORT}:3000"
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Use a unique generated `WEB_PORT` and `BACKEND_PORT` for every running project instance. Never derive a destination port from the hostname; each route must name one explicit Docker service and internal port.
|
|
147
|
+
|
|
148
|
+
## Authentication callbacks
|
|
149
|
+
|
|
150
|
+
A wildcard hostname lets an identity provider allow one local callback pattern instead of every generated port. For Traditional Knowledge, configure:
|
|
151
|
+
|
|
152
|
+
```text
|
|
153
|
+
Allowed Callback URLs: http://*.traditional-knowledge.localhost/callback
|
|
154
|
+
Allowed Logout URLs: http://*.traditional-knowledge.localhost
|
|
155
|
+
Allowed Web Origins: http://*.traditional-knowledge.localhost
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Keep existing production and staging entries. Verify that the identity provider accepts wildcard hostnames in each field before relying on this pattern.
|
|
159
|
+
|
|
160
|
+
## Security boundary
|
|
161
|
+
|
|
162
|
+
The gateway publishes port `80` only to the local machine. It does not create a public endpoint.
|
|
163
|
+
|
|
164
|
+
It mounts the Docker socket so it can discover labelled containers. A read-only socket mount still exposes broad Docker metadata and control-plane access. Treat anyone who can modify this repository or its running gateway container as having significant access to local Docker. Do not run untrusted gateway images or route untrusted containers through it.
|
|
165
|
+
|
|
166
|
+
## Troubleshooting
|
|
167
|
+
|
|
168
|
+
| Symptom | Check |
|
|
169
|
+
| --- | --- |
|
|
170
|
+
| Gateway will not start | Confirm `127.0.0.1:80` is free. |
|
|
171
|
+
| Generated hostname returns `404` | Confirm the web container has the route labels and is attached to `local-gateway`. |
|
|
172
|
+
| A project uses normal ports unexpectedly | Confirm the gateway container is running and has `local-gateway=true`. |
|
|
173
|
+
| Authentication rejects the callback | Confirm the generated hostname matches the allowed wildcard callback pattern. |
|
data/config/traefik.yml
ADDED
data/docker-compose.yml
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: local-gateway
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
gateway:
|
|
5
|
+
image: traefik:v3.7.8
|
|
6
|
+
command:
|
|
7
|
+
- --configFile=/etc/traefik/traefik.yml
|
|
8
|
+
healthcheck:
|
|
9
|
+
test: ["CMD", "traefik", "healthcheck", "--ping"]
|
|
10
|
+
interval: 1s
|
|
11
|
+
timeout: 3s
|
|
12
|
+
retries: 30
|
|
13
|
+
start_period: 1s
|
|
14
|
+
labels:
|
|
15
|
+
local-gateway: "true"
|
|
16
|
+
ports:
|
|
17
|
+
- "127.0.0.1:80:80"
|
|
18
|
+
restart: unless-stopped
|
|
19
|
+
volumes:
|
|
20
|
+
- ./config/traefik.yml:/etc/traefik/traefik.yml:ro
|
|
21
|
+
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
22
|
+
networks:
|
|
23
|
+
- local-gateway
|
|
24
|
+
|
|
25
|
+
networks:
|
|
26
|
+
local-gateway:
|
|
27
|
+
name: local-gateway
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
require_relative "local_development_gateway/version"
|
|
5
|
+
|
|
6
|
+
module LocalDevelopmentGateway
|
|
7
|
+
PROJECT_NAME = "local-gateway"
|
|
8
|
+
NETWORK_NAME = "local-gateway"
|
|
9
|
+
SERVICE_NAME = "gateway"
|
|
10
|
+
GATEWAY_LABEL = "local-gateway"
|
|
11
|
+
ASSET_ROOT = File.expand_path("..", __dir__)
|
|
12
|
+
COMPOSE_FILE = File.join(ASSET_ROOT, "docker-compose.yml")
|
|
13
|
+
TRAEFIK_CONFIG_FILE = File.join(ASSET_ROOT, "config", "traefik.yml")
|
|
14
|
+
|
|
15
|
+
class Error < StandardError; end
|
|
16
|
+
|
|
17
|
+
class DockerError < Error
|
|
18
|
+
attr_reader :command, :output
|
|
19
|
+
|
|
20
|
+
def initialize(command, output)
|
|
21
|
+
@command = command
|
|
22
|
+
@output = output
|
|
23
|
+
super("Docker command failed: #{command.join(" ")}\n#{output}")
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class Docker
|
|
28
|
+
def call(*args, capture: true)
|
|
29
|
+
command = ["docker", *args]
|
|
30
|
+
if capture
|
|
31
|
+
stdout, stderr, status = Open3.capture3(*command)
|
|
32
|
+
raise DockerError.new(command, stderr.empty? ? stdout : stderr) unless status.success?
|
|
33
|
+
|
|
34
|
+
stdout
|
|
35
|
+
elsif system(*command)
|
|
36
|
+
true
|
|
37
|
+
else
|
|
38
|
+
raise DockerError.new(command, "")
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class Client
|
|
44
|
+
CONTAINER_FORMAT = "{{.ID}}\t{{.Label \"com.docker.compose.project\"}}\t{{.Label \"com.docker.compose.service\"}}\t{{.Label \"local-gateway\"}}"
|
|
45
|
+
|
|
46
|
+
def initialize(
|
|
47
|
+
runner: Docker.new,
|
|
48
|
+
timeout: 30,
|
|
49
|
+
poll_interval: 0.25,
|
|
50
|
+
sleeper: ->(seconds) { sleep seconds },
|
|
51
|
+
clock: -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) }
|
|
52
|
+
)
|
|
53
|
+
@runner = runner
|
|
54
|
+
@timeout = timeout
|
|
55
|
+
@poll_interval = poll_interval
|
|
56
|
+
@sleeper = sleeper
|
|
57
|
+
@clock = clock
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def ensure_running(*compose_args)
|
|
61
|
+
return :reused if compose_args.empty? && ready?
|
|
62
|
+
|
|
63
|
+
compose("up", "-d", "--remove-orphans", *compose_args)
|
|
64
|
+
wait_until_ready
|
|
65
|
+
:started
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
alias start ensure_running
|
|
69
|
+
|
|
70
|
+
def ready?
|
|
71
|
+
network_exists? && gateway_container_ids(status: "running").any? { |id| healthy_container?(id) }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def status
|
|
75
|
+
compose("ps", "--all")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def logs(*args, follow: false)
|
|
79
|
+
compose("logs", *(follow ? ["--follow"] : []), *args, capture: false)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def stop_if_unused
|
|
83
|
+
return :not_running unless gateway_exists?
|
|
84
|
+
return :in_use if non_gateway_containers_attached?
|
|
85
|
+
|
|
86
|
+
compose("down", "--remove-orphans")
|
|
87
|
+
:stopped
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def compose(*args, capture: true)
|
|
91
|
+
@runner.call(
|
|
92
|
+
"compose",
|
|
93
|
+
"--project-name", PROJECT_NAME,
|
|
94
|
+
"--file", COMPOSE_FILE,
|
|
95
|
+
*args,
|
|
96
|
+
capture: capture,
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
def wait_until_ready
|
|
103
|
+
deadline = @clock.call + @timeout
|
|
104
|
+
until ready?
|
|
105
|
+
raise Error, "Local Development Gateway did not become ready" if @clock.call >= deadline
|
|
106
|
+
|
|
107
|
+
@sleeper.call(@poll_interval)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def network_exists?
|
|
112
|
+
labels = @runner.call(
|
|
113
|
+
"network",
|
|
114
|
+
"inspect",
|
|
115
|
+
NETWORK_NAME,
|
|
116
|
+
"--format",
|
|
117
|
+
"{{index .Labels \"com.docker.compose.project\"}}\t{{index .Labels \"com.docker.compose.network\"}}",
|
|
118
|
+
)
|
|
119
|
+
labels.lines.any? { |line| line.strip == "#{PROJECT_NAME}\t#{NETWORK_NAME}" }
|
|
120
|
+
rescue DockerError
|
|
121
|
+
false
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def gateway_exists?
|
|
125
|
+
network_exists? && !gateway_container_ids.empty?
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def gateway_container_ids(status: nil)
|
|
129
|
+
args = ["ps"]
|
|
130
|
+
args << "--all" unless status
|
|
131
|
+
args.concat([
|
|
132
|
+
"--filter", "network=#{NETWORK_NAME}",
|
|
133
|
+
"--filter", "label=com.docker.compose.project=#{PROJECT_NAME}",
|
|
134
|
+
"--filter", "label=com.docker.compose.service=#{SERVICE_NAME}",
|
|
135
|
+
"--filter", "label=#{GATEWAY_LABEL}=true",
|
|
136
|
+
])
|
|
137
|
+
args.push("--filter", "status=#{status}") if status
|
|
138
|
+
output = @runner.call(*args, "--quiet")
|
|
139
|
+
output.lines.map(&:strip).reject(&:empty?)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def healthy_container?(id)
|
|
143
|
+
@runner.call("inspect", "--format", "{{.State.Health.Status}}", id).strip == "healthy"
|
|
144
|
+
rescue DockerError
|
|
145
|
+
false
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def attached_containers
|
|
149
|
+
output = @runner.call(
|
|
150
|
+
"ps",
|
|
151
|
+
"--all",
|
|
152
|
+
"--filter", "network=#{NETWORK_NAME}",
|
|
153
|
+
"--format", CONTAINER_FORMAT,
|
|
154
|
+
)
|
|
155
|
+
output.lines.filter_map do |line|
|
|
156
|
+
id, project, service, label = line.strip.split("\t", -1)
|
|
157
|
+
next if id.nil? || id.empty?
|
|
158
|
+
|
|
159
|
+
{ id: id, project: project, service: service, label: label }
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def non_gateway_containers_attached?
|
|
164
|
+
attached_containers.any? do |container|
|
|
165
|
+
!gateway_container?(container)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def gateway_container?(container)
|
|
170
|
+
container[:project] == PROJECT_NAME &&
|
|
171
|
+
container[:service] == SERVICE_NAME &&
|
|
172
|
+
container[:label] == "true"
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
class CLI
|
|
177
|
+
USAGE = <<~USAGE
|
|
178
|
+
Usage: local-development-gateway COMMAND [OPTIONS]
|
|
179
|
+
|
|
180
|
+
Commands:
|
|
181
|
+
up, ensure Start or reuse the shared gateway
|
|
182
|
+
status Show the gateway Compose status
|
|
183
|
+
ready Check gateway readiness
|
|
184
|
+
logs Follow gateway logs
|
|
185
|
+
down, stop Stop the gateway only when unused
|
|
186
|
+
|
|
187
|
+
Other commands are passed to Docker Compose using the packaged assets.
|
|
188
|
+
USAGE
|
|
189
|
+
|
|
190
|
+
def self.run(argv, client: Client.new, output: $stdout, error: $stderr)
|
|
191
|
+
new(argv, client: client, output: output, error: error).run
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def initialize(argv, client:, output:, error:)
|
|
195
|
+
@argv = argv.dup
|
|
196
|
+
@client = client
|
|
197
|
+
@output = output
|
|
198
|
+
@error = error
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def run
|
|
202
|
+
command = @argv.shift
|
|
203
|
+
return print_usage(0) if command.nil? || %w[help --help -h].include?(command)
|
|
204
|
+
|
|
205
|
+
case command
|
|
206
|
+
when "up", "ensure"
|
|
207
|
+
@client.ensure_running(*@argv)
|
|
208
|
+
when "status"
|
|
209
|
+
@output.write(@client.status)
|
|
210
|
+
when "ready"
|
|
211
|
+
return 0 if @client.ready?
|
|
212
|
+
|
|
213
|
+
@error.puts "Local Development Gateway is not ready"
|
|
214
|
+
return 1
|
|
215
|
+
when "logs"
|
|
216
|
+
@client.logs(*@argv, follow: true)
|
|
217
|
+
when "down", "stop"
|
|
218
|
+
report_stop(@client.stop_if_unused)
|
|
219
|
+
else
|
|
220
|
+
@output.write(@client.compose(command, *@argv))
|
|
221
|
+
end
|
|
222
|
+
0
|
|
223
|
+
rescue Error => error
|
|
224
|
+
@error.puts error.message
|
|
225
|
+
1
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
private
|
|
229
|
+
|
|
230
|
+
def print_usage(status)
|
|
231
|
+
@output.write(USAGE)
|
|
232
|
+
status
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def report_stop(result)
|
|
236
|
+
messages = {
|
|
237
|
+
not_running: "Local Development Gateway is not running",
|
|
238
|
+
in_use: "Leaving Local Development Gateway running because another container is attached",
|
|
239
|
+
stopped: "Stopped Local Development Gateway",
|
|
240
|
+
}
|
|
241
|
+
@output.puts messages.fetch(result)
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
class << self
|
|
246
|
+
def ensure_running(*args)
|
|
247
|
+
Client.new.ensure_running(*args)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def start(*args)
|
|
251
|
+
ensure_running(*args)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def ready?
|
|
255
|
+
Client.new.ready?
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def status
|
|
259
|
+
Client.new.status
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def logs(*args)
|
|
263
|
+
Client.new.logs(*args, follow: true)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def stop_if_unused
|
|
267
|
+
Client.new.stop_if_unused
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: local-development-gateway
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Marlen Brunner
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-07-28 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: Ruby API and CLI for starting, monitoring, and conditionally stopping
|
|
14
|
+
the shared Docker gateway.
|
|
15
|
+
email:
|
|
16
|
+
- marlen@icefoganalytics.com
|
|
17
|
+
executables:
|
|
18
|
+
- local-development-gateway
|
|
19
|
+
extensions: []
|
|
20
|
+
extra_rdoc_files: []
|
|
21
|
+
files:
|
|
22
|
+
- README.md
|
|
23
|
+
- bin/local-development-gateway
|
|
24
|
+
- config/traefik.yml
|
|
25
|
+
- docker-compose.yml
|
|
26
|
+
- lib/local_development_gateway.rb
|
|
27
|
+
- lib/local_development_gateway/version.rb
|
|
28
|
+
homepage: https://github.com/icefoganalytics/local-development-gateway
|
|
29
|
+
licenses:
|
|
30
|
+
- MIT
|
|
31
|
+
metadata:
|
|
32
|
+
bug_tracker_uri: https://github.com/icefoganalytics/local-development-gateway/issues
|
|
33
|
+
source_code_uri: https://github.com/icefoganalytics/local-development-gateway
|
|
34
|
+
post_install_message:
|
|
35
|
+
rdoc_options: []
|
|
36
|
+
require_paths:
|
|
37
|
+
- lib
|
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
39
|
+
requirements:
|
|
40
|
+
- - ">="
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '3.2'
|
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
requirements: []
|
|
49
|
+
rubygems_version: 3.5.22
|
|
50
|
+
signing_key:
|
|
51
|
+
specification_version: 4
|
|
52
|
+
summary: Shared loopback gateway lifecycle for local Docker development
|
|
53
|
+
test_files: []
|