swarmkit-client 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.
data/README.md ADDED
@@ -0,0 +1,257 @@
1
+ # swarmkit-client
2
+
3
+ A compact Ruby client for SwarmKit's internal gRPC API: **65 RPCs, 11 services**,
4
+ full protobuf types, TLS, deadlines and streaming. This connects directly to
5
+ SwarmKit; it is separate from Docker Engine's HTTP API.
6
+
7
+ ## Install
8
+
9
+ Ruby 3.2 or newer. From this checkout:
10
+
11
+ ```sh
12
+ bundle install
13
+ bundle exec rake build
14
+ gem install pkg/swarmkit-client-0.1.0.gem
15
+ ```
16
+
17
+ Or use `gem 'swarmkit-client', path: '../swarmkit-client'` in another project's Gemfile.
18
+
19
+ ## CLI
20
+
21
+ Installing the gem adds `swarmkit-client`. In this checkout, use
22
+ `bundle exec ruby bin/swarmkit-client` before installation.
23
+
24
+ ```sh
25
+ swarmkit-client --help
26
+ swarmkit-client methods # All 65 RPCs; offline
27
+ swarmkit-client methods control # RPCs for one service
28
+ swarmkit-client describe control get_service # Request/response fields, nested types and enums; offline
29
+
30
+ swarmkit-client health check '{"service":"ControlAPI"}'
31
+ swarmkit-client control list_services '{"filters":{"names":["grib_grib_loader"]}}'
32
+ swarmkit-client control get_service '{"service_id":"service-id"}'
33
+ swarmkit-client --timeout 30 watch watch '{"entries":[{"kind":"service","action":7}]}'
34
+ ```
35
+
36
+ Syntax: `swarmkit-client [options] SERVICE METHOD [JSON|@FILE|-]`. Service and
37
+ method names match the Ruby client. Omitted JSON means `{}`; use `@request.json`
38
+ to read a file or `-` to read stdin. Client-streaming RPCs accept a JSON array.
39
+ Input follows protobuf JSON, accepting snake_case or lowerCamelCase field names;
40
+ unknown fields are rejected before connecting.
41
+
42
+ Connection flags are `--target`, `--ca`, `--cert`, `--key`, `--server-name` and
43
+ `--timeout`; defaults match the [Ruby client](#connection-options). For a forwarded socket:
44
+
45
+ ```sh
46
+ swarmkit-client --target unix:/tmp/swarmkit.sock --ca ./swarm-root-ca.crt \
47
+ control get_service @request.json
48
+ ```
49
+
50
+ Each response is one JSON line on stdout, flushed immediately for streams.
51
+ Output uses protobuf JSON with snake_case names, string-encoded 64-bit integers
52
+ and omitted default scalar fields. Errors go to stderr without echoing request
53
+ values or server error details. Exit codes: `0` success (including a closed output
54
+ pipe), `1` RPC/I/O failure, `2` invalid input, `130` interruption. Reaching a stream
55
+ deadline returns `1`, with gRPC status `4`; already received lines remain valid.
56
+ Streams are cancelled and the channel closed on completion, error or Ctrl-C.
57
+
58
+ The CLI emits diagnostic JSON with credential-bearing fields redacted, including
59
+ registry auth, passwords, tokens, private/unlock keys, opaque data, environment,
60
+ command arguments, labels and options. These become `"[REDACTED]"`. Use the Ruby
61
+ API for complete objects; CLI output is unsuitable for read/modify/write updates.
62
+ Explicit mutation commands run directly, for example
63
+ `swarmkit-client control update_service @update.json`; the request must include
64
+ the complete spec and current version as shown [below](#update-a-service).
65
+
66
+ ## Read services
67
+
68
+ Run on a Swarm manager with permission to access its control socket and root CA:
69
+
70
+ ```ruby
71
+ require 'swarmkit-client'
72
+
73
+ api = Swarmkit::API
74
+ Swarmkit::Client.open do |client|
75
+ request = api::ListServicesRequest.new(filters: {names: ['grib_grib_loader']})
76
+ client.control.list_services(request).services.each do |service|
77
+ puts "#{service.id}: #{service.spec.task.container&.image}"
78
+ end
79
+ status = client.health.check(api::HealthCheckRequest.new(service: 'ControlAPI'))
80
+ puts status.status # :SERVING
81
+ end
82
+ ```
83
+
84
+ `Client.open` closes the shared channel even if the block raises. For a persistent
85
+ connection, use `Client.new` and call `close` when finished. All service stubs
86
+ share that connection; close it only after its callers and streams finish.
87
+
88
+ Internal responses can contain stored registry credentials, configuration data
89
+ and tokens. Select the fields you need; avoid logging complete protobuf objects.
90
+
91
+ ## Connection options
92
+
93
+ | Keyword | Default | Meaning |
94
+ |---|---|---|
95
+ | `target` | `unix:/var/run/docker/swarm/control.sock` | Native gRPC target, including TCP `host:port` |
96
+ | `ca` | `/var/lib/docker/swarm/certificates/swarm-root-ca.crt` | Path to trusted CA PEM |
97
+ | `cert`, `key` | `nil` | Paths to client certificate and private key PEM; provide both for mutual TLS |
98
+ | `server_name` | `swarm-manager` | Expected certificate SAN and authority; `nil` uses the target hostname |
99
+ | `timeout` | `10` | Positive finite default deadline duration in seconds, including streams |
100
+ | `channel_args` | `{}` | Native gRPC channel settings; default receive limit is 16 MiB |
101
+
102
+ The inspected Docker 29.2.0 manager uses **TLS even on its Unix socket**.
103
+ `server_name` selects the identity to verify against the trusted CA; verification
104
+ stays enabled. Through an SSH Unix-socket forward, supply the forwarded socket
105
+ as `target` and a local copy of the public root CA as `ca`.
106
+
107
+ Remote TCP example, with paths to authorized Swarm certificates:
108
+
109
+ ```ruby
110
+ client = Swarmkit::Client.new(
111
+ target: 'manager.example:2377', ca: '/secure/swarm-root-ca.crt',
112
+ cert: '/secure/swarm-node.crt', key: '/secure/swarm-node.key'
113
+ )
114
+ begin
115
+ # Use client.control, client.watch, etc.
116
+ ensure
117
+ client.close
118
+ end
119
+ ```
120
+
121
+ The remote manager enforces certificate roles, organization and revocation.
122
+ Socket access uses a different authorization path. `raft` and `raft_membership`
123
+ are registered only on the network server in the inspected build. Having a
124
+ client method does not make that RPC available or authorized on every endpoint.
125
+
126
+ ## API and types
127
+
128
+ The [complete reference](docs/api.md) lists **every method, request parameter,
129
+ response property, nested type, field number, default, oneof and enum value**.
130
+
131
+ | Client accessor | RPCs | Purpose |
132
+ |---|---:|---|
133
+ | `control` | 43 | Nodes, tasks, services, networks, clusters, secrets, configs, volumes and custom objects |
134
+ | `dispatcher` | 6 | Worker sessions, assignments and status updates |
135
+ | `ca` | 2 | Cluster certificates and root CA |
136
+ | `node_ca` | 2 | Node certificate issuance and status |
137
+ | `raft` | 3 | Raft transport and member address lookup |
138
+ | `raft_membership` | 2 | Raft membership changes |
139
+ | `logs` | 1 | Log subscriptions |
140
+ | `log_broker` | 2 | Log subscription delivery and publishing |
141
+ | `watch` | 1 | Store event subscriptions |
142
+ | `health` | 1 | Named service health |
143
+ | `resource_allocator` | 2 | Network attachment allocation |
144
+
145
+ Stubs follow native [Ruby gRPC](https://grpc.io/docs/languages/ruby/basics/):
146
+ snake_case RPC names, typed request objects and typed responses. Construct
147
+ nested messages from hashes, use symbols for named enums and integers for enum
148
+ bitmasks such as Watch action `7`. Protobuf field names are preserved; Go's custom
149
+ names from `gogoproto` options do not rename the Ruby API.
150
+
151
+ ```ruby
152
+ request = Swarmkit::API::GetServiceRequest.new(service_id: 'service-id')
153
+ response = client.control.get_service(request, deadline: Time.now + 5)
154
+ service = response.service
155
+
156
+ # Inspect field definitions without reading or dumping object values.
157
+ Swarmkit::API::GetServiceRequest.descriptor.each do |field|
158
+ puts "#{field.name}: #{field.type}"
159
+ end
160
+ ```
161
+
162
+ All SwarmKit types are under `Swarmkit::API`, including nested constants such as
163
+ `ContainerSpec::PullOptions`. Imported protobuf/raft types can be obtained from
164
+ `Swarmkit::API::POOL.lookup('raftpb.Message').msgclass`; nested hashes normally
165
+ make this unnecessary. The private descriptor pool prevents collisions with
166
+ other gems that load SwarmKit definitions.
167
+
168
+ Native gRPC options (`deadline`, `metadata`, `return_op`, `parent`, `credentials`)
169
+ pass through unchanged. Failures raise native `GRPC::BadStatus` subclasses such
170
+ as `GRPC::NotFound`, `GRPC::PermissionDenied` and `GRPC::DeadlineExceeded`.
171
+ The client adds no application-level retries. A mutation may have committed
172
+ before a deadline error; read the current state before deciding to retry.
173
+
174
+ ## Update a service
175
+
176
+ This writes to the manager when called. Read the complete current spec and use
177
+ its **`meta.version`** for optimistic concurrency:
178
+
179
+ ```ruby
180
+ Swarmkit::Client.open do |client|
181
+ api = Swarmkit::API
182
+ service = client.control.get_service(api::GetServiceRequest.new(service_id: 'service-id')).service
183
+ service.spec.task.container.image = 'registry.example/app:v2'
184
+ client.control.update_service(api::UpdateServiceRequest.new(
185
+ service_id: service.id, service_version: service.meta.version, spec: service.spec
186
+ ))
187
+ end
188
+ ```
189
+
190
+ The bundled descriptor contains the full upstream schema, including registry
191
+ auth, maps and oneofs. It is not the reduced read projection used by the earlier
192
+ incident diagnostic script. Do not build updates from that reduced projection.
193
+
194
+ ## Watch and cancel
195
+
196
+ ```ruby
197
+ Swarmkit::Client.open do |client|
198
+ request = Swarmkit::API::WatchRequest.new(entries: [{kind: 'service', action: 7}])
199
+ operation = client.watch.watch(request, deadline: Time.now + 30, return_op: true)
200
+ begin
201
+ operation.execute.each do |message|
202
+ next if message.events.empty? # Initial readiness response.
203
+
204
+ puts "Store version: #{message.version.index}"
205
+ message.events.each { |event| puts event.action }
206
+ end
207
+ rescue GRPC::DeadlineExceeded
208
+ # The requested observation period ended.
209
+ ensure
210
+ operation.cancel
211
+ end
212
+ end
213
+ ```
214
+
215
+ Save event `version` to resume with `resume_from: {index: version}`. Compacted
216
+ history requires a fresh snapshot. `include_old_object` applies to live updates,
217
+ not replay. Always cancel an operation when stopping consumption early.
218
+ Client-streaming RPCs such as `log_broker.publish_logs` accept an Enumerable of
219
+ typed request messages. Their use belongs to the corresponding SwarmKit protocol.
220
+
221
+ ## Development and release
222
+
223
+ The layout, RSpec approach, JUnit reports, Rake tasks and publish-on-main workflow
224
+ follow [artyomb/dry-stack](https://github.com/artyomb/dry-stack). The client and CLI
225
+ share descriptor-based bindings without hand-written wrappers for individual
226
+ RPCs. The CLI uses the standard library's OptionParser and JSON.
227
+
228
+ ```sh
229
+ bundle exec rake # RuboCop + RSpec; results/rspec.xml
230
+ bundle exec rake spec # Local TLS fixtures; no production access
231
+ bundle exec rake build # Checks, then pkg/swarmkit-client-VERSION.gem
232
+ bundle exec rake schema # Fetch checksum-pinned sources and compile
233
+ bundle exec rake docs # Regenerate the complete field reference
234
+ ```
235
+
236
+ Specs cover TLS Unix sockets, mutual TLS TCP, certificate verification, complete
237
+ read/update messages, status errors, deadlines, both stream directions and
238
+ cancellation. Update tests use an in-process fixture, not a production manager.
239
+ CLI specs also cover discovery, schema output, input sources, redaction,
240
+ streaming, exit codes and executable startup.
241
+
242
+ GitHub Actions checks Ruby 3.2 and 3.4 on pull requests and pushes to `main`.
243
+ After successful checks, a push to `main` runs `rake push`, using repository secret
244
+ `RUBYGEMS_API_KEY` through `GEM_HOST_API_KEY`. Configure that secret and ensure the
245
+ gem name is available before enabling publication. Bump `lib/version.rb` for
246
+ each published version; RubyGems rejects an existing version. `rake push` also
247
+ publishes locally when explicitly invoked.
248
+
249
+ Schema: [moby/swarmkit `17b8d222e7dd`](https://github.com/moby/swarmkit/tree/17b8d222e7dd),
250
+ the revision pinned by the inspected Docker 29.2.0 build. This is an internal,
251
+ version-sensitive API. `schema/sources.json` pins source URLs and SHA-256 values;
252
+ `grpc-tools` 1.85.0 supplies protoc 35.1. Source comments are stripped from the
253
+ runtime descriptor. To change revisions, update the manifest URLs/checksums,
254
+ rebuild schema and docs, then run the specs against the intended Docker version.
255
+ No compiler or network access is needed to load the installed gem.
256
+
257
+ Apache-2.0; bundled schema attributions and dependency licenses are in [NOTICE](NOTICE).
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/swarmkit-client/cli'
5
+
6
+ exit Swarmkit::CLI.new.run(ARGV)