dip 8.0.0 → 8.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +107 -0
- data/lib/dip/cli.rb +10 -1
- data/lib/dip/config.rb +55 -4
- data/lib/dip/version.rb +1 -1
- data/schema.json +224 -0
- metadata +38 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f0797622a68dc23c6c015addd9a97dfe57d9c5cca6b4981054dbac9f5f7602c9
|
4
|
+
data.tar.gz: bdb1277acfa498846fdd737a13058dc1dfc272fbd7196d21e4cbfeefaf13b3a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 826f6e63b2cd65d20f6bfc00b545a7b5f60d6068786df01f841d92de1179d69fcced9605f65a3e4bfbcace583e5a20e7e8a660359ff2922685a02fae60c8c5c3
|
7
|
+
data.tar.gz: 5e583069000503b70df8a8466605d905c4937b931b53734de57486662a93a1f1991a51363e46687eeb4664879ece0ae1c9db56f9a76b57a6e5c478fa5e8d4ee9
|
data/README.md
CHANGED
@@ -238,6 +238,92 @@ services:
|
|
238
238
|
|
239
239
|
The container will run using the same user ID as your host machine.
|
240
240
|
|
241
|
+
### Modules
|
242
|
+
|
243
|
+
Modules are defined as array in `modules` section of dip.yml, modules are stored in `.dip` subdirectory of dip.yml directory.
|
244
|
+
|
245
|
+
The main purpose of modules is to improve maintainability for a group of projects.
|
246
|
+
Imagine having multiple gems which are managed with dip, each of them has the same commands, so to change one command in dip you need to update all gems individualy.
|
247
|
+
|
248
|
+
With `modules` you can define a group of modules for dip.
|
249
|
+
|
250
|
+
For example having setup as this:
|
251
|
+
|
252
|
+
```yml
|
253
|
+
# ./dip.yml
|
254
|
+
modules:
|
255
|
+
- sasts
|
256
|
+
- rails
|
257
|
+
|
258
|
+
...
|
259
|
+
```
|
260
|
+
|
261
|
+
```yml
|
262
|
+
# ./.dip/sasts.yml
|
263
|
+
interaction:
|
264
|
+
brakeman:
|
265
|
+
description: Check brakeman sast
|
266
|
+
command: docker run ...
|
267
|
+
```
|
268
|
+
|
269
|
+
```yml
|
270
|
+
# ./.dip/rails.yml
|
271
|
+
interaction:
|
272
|
+
annotate:
|
273
|
+
description: Run annotate command
|
274
|
+
service: backend
|
275
|
+
command: bundle exec annotate
|
276
|
+
```
|
277
|
+
|
278
|
+
Will be expanded to:
|
279
|
+
|
280
|
+
```yml
|
281
|
+
# resultant configuration
|
282
|
+
interaction:
|
283
|
+
brakeman:
|
284
|
+
description: Check brakeman sast
|
285
|
+
command: docker run ...
|
286
|
+
annotate:
|
287
|
+
description: Run annotate command
|
288
|
+
service: backend
|
289
|
+
command: bundle exec annotate
|
290
|
+
```
|
291
|
+
|
292
|
+
Imagine `.dip` to be a submodule so it can be managed only in one place.
|
293
|
+
|
294
|
+
If you want to override module command, you can redefine it in dip.yml
|
295
|
+
|
296
|
+
```yml
|
297
|
+
# ./dip.yml
|
298
|
+
modules:
|
299
|
+
- sasts
|
300
|
+
|
301
|
+
interaction:
|
302
|
+
brakeman:
|
303
|
+
description: Check brakeman sast
|
304
|
+
command: docker run another-image ...
|
305
|
+
```
|
306
|
+
|
307
|
+
```yml
|
308
|
+
# ./.dip/sasts.yml
|
309
|
+
interaction:
|
310
|
+
brakeman:
|
311
|
+
description: Check brakeman sast
|
312
|
+
command: docker run some-image ...
|
313
|
+
```
|
314
|
+
|
315
|
+
Will be expanded to:
|
316
|
+
|
317
|
+
```yml
|
318
|
+
# resultant configuration
|
319
|
+
interaction:
|
320
|
+
brakeman:
|
321
|
+
description: Check brakeman sast
|
322
|
+
command: docker run another-image ...
|
323
|
+
```
|
324
|
+
|
325
|
+
Nested modules are not supported.
|
326
|
+
|
241
327
|
### dip run
|
242
328
|
|
243
329
|
Run commands defined within the `interaction` section of dip.yml
|
@@ -386,6 +472,27 @@ services:
|
|
386
472
|
user: "1000:1000"
|
387
473
|
```
|
388
474
|
|
475
|
+
### dip validate
|
476
|
+
|
477
|
+
Validates your dip.yml configuration against the JSON schema. The schema validation helps ensure your configuration is correct and follows the expected format.
|
478
|
+
|
479
|
+
```sh
|
480
|
+
dip validate
|
481
|
+
```
|
482
|
+
|
483
|
+
The validator will check:
|
484
|
+
|
485
|
+
- Required properties are present
|
486
|
+
- Property types are correct
|
487
|
+
- Values match expected patterns
|
488
|
+
- No unknown properties are used
|
489
|
+
|
490
|
+
If validation fails, you'll get detailed error messages indicating what needs to be fixed.
|
491
|
+
|
492
|
+
You can skip validation by setting `DIP_SKIP_VALIDATION` environment variable.
|
493
|
+
|
494
|
+
Add `# yaml-language-server: $schema=https://raw.githubusercontent.com/bibendi/dip/refs/heads/master/schema.json` to the top of your dip.yml to get schema validation in VSCode. Read more about [YAML Language Server](https://github.com/redhat-developer/vscode-yaml?tab=readme-ov-file#associating-schemas).
|
495
|
+
|
389
496
|
## Changelog
|
390
497
|
|
391
498
|
https://github.com/bibendi/dip/releases
|
data/lib/dip/cli.rb
CHANGED
@@ -5,7 +5,7 @@ require "dip/run_vars"
|
|
5
5
|
|
6
6
|
module Dip
|
7
7
|
class CLI < Thor
|
8
|
-
TOP_LEVEL_COMMANDS = %w[help version ls compose up stop down run provision ssh infra console]
|
8
|
+
TOP_LEVEL_COMMANDS = %w[help version ls compose up stop down run provision ssh infra console validate]
|
9
9
|
|
10
10
|
class << self
|
11
11
|
# Hackery. Take the run method away from Thor so that we can redefine it.
|
@@ -117,6 +117,15 @@ module Dip
|
|
117
117
|
end
|
118
118
|
end
|
119
119
|
|
120
|
+
desc "validate", "Validate the dip.yml file against the schema"
|
121
|
+
def validate
|
122
|
+
Dip.config.validate
|
123
|
+
puts "dip.yml is valid"
|
124
|
+
rescue Dip::Error => e
|
125
|
+
warn "Validation failed: #{e.message}"
|
126
|
+
exit 1
|
127
|
+
end
|
128
|
+
|
120
129
|
require_relative "cli/ssh"
|
121
130
|
desc "ssh", "ssh-agent container commands"
|
122
131
|
subcommand :ssh, Dip::CLI::SSH
|
data/lib/dip/config.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require "yaml"
|
4
4
|
require "erb"
|
5
5
|
require "pathname"
|
6
|
+
require "json-schema"
|
6
7
|
|
7
8
|
require "dip/version"
|
8
9
|
require "dip/ext/hash"
|
@@ -43,6 +44,10 @@ module Dip
|
|
43
44
|
file_path&.exist?
|
44
45
|
end
|
45
46
|
|
47
|
+
def modules_dir
|
48
|
+
file_path.dirname / ".dip"
|
49
|
+
end
|
50
|
+
|
46
51
|
private
|
47
52
|
|
48
53
|
attr_reader :override
|
@@ -90,6 +95,10 @@ module Dip
|
|
90
95
|
finder.file_path
|
91
96
|
end
|
92
97
|
|
98
|
+
def module_file(filename)
|
99
|
+
finder.modules_dir / "#{filename}.yml"
|
100
|
+
end
|
101
|
+
|
93
102
|
def exist?
|
94
103
|
finder.exist?
|
95
104
|
end
|
@@ -104,6 +113,24 @@ module Dip
|
|
104
113
|
end
|
105
114
|
end
|
106
115
|
|
116
|
+
def validate
|
117
|
+
raise Dip::Error, "Config file path is not set" if file_path.nil?
|
118
|
+
raise Dip::Error, "Config file not found: #{file_path}" unless File.exist?(file_path)
|
119
|
+
|
120
|
+
schema_path = File.join(File.dirname(__FILE__), "../../schema.json")
|
121
|
+
raise Dip::Error, "Schema file not found: #{schema_path}" unless File.exist?(schema_path)
|
122
|
+
|
123
|
+
data = YAML.load_file(file_path)
|
124
|
+
schema = JSON.parse(File.read(schema_path))
|
125
|
+
JSON::Validator.validate!(schema, data)
|
126
|
+
rescue Psych::SyntaxError => e
|
127
|
+
raise Dip::Error, "Invalid YAML syntax in config file: #{e.message}"
|
128
|
+
rescue JSON::Schema::ValidationError => e
|
129
|
+
data_display = data ? data.to_yaml.gsub("\n", "\n ") : "nil"
|
130
|
+
error_message = "Schema validation failed: #{e.message}\nInput data:\n #{data_display}"
|
131
|
+
raise Dip::Error, error_message
|
132
|
+
end
|
133
|
+
|
107
134
|
private
|
108
135
|
|
109
136
|
attr_reader :work_dir
|
@@ -121,14 +148,38 @@ module Dip
|
|
121
148
|
|
122
149
|
unless Gem::Version.new(Dip::VERSION) >= Gem::Version.new(config.fetch(:version))
|
123
150
|
raise VersionMismatchError, "Your dip version is `#{Dip::VERSION}`, " \
|
124
|
-
|
125
|
-
|
151
|
+
"but config requires minimum version `#{config[:version]}`. " \
|
152
|
+
"Please upgrade your dip!"
|
126
153
|
end
|
127
154
|
|
155
|
+
base_config = {}
|
156
|
+
|
157
|
+
if (modules = config[:modules])
|
158
|
+
raise Dip::Error, "Modules should be specified as array" unless modules.is_a?(Array)
|
159
|
+
|
160
|
+
modules.each do |m|
|
161
|
+
file = module_file(m)
|
162
|
+
raise Dip::Error, "Could not find module `#{m}`" unless file.exist?
|
163
|
+
|
164
|
+
module_config = self.class.load_yaml(file)
|
165
|
+
raise Dip::Error, "Nested modules are not supported" if module_config[:modules]
|
166
|
+
|
167
|
+
base_config.deep_merge!(module_config)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
base_config.deep_merge!(config)
|
172
|
+
|
128
173
|
override_finder = ConfigFinder.new(work_dir, override: true)
|
129
|
-
|
174
|
+
base_config.deep_merge!(self.class.load_yaml(override_finder.file_path)) if override_finder.exist?
|
175
|
+
|
176
|
+
@config = CONFIG_DEFAULTS.merge(base_config)
|
177
|
+
|
178
|
+
unless ENV.key?("DIP_SKIP_VALIDATION")
|
179
|
+
validate
|
180
|
+
end
|
130
181
|
|
131
|
-
@config
|
182
|
+
@config
|
132
183
|
end
|
133
184
|
|
134
185
|
def config_missing_error(config_key)
|
data/lib/dip/version.rb
CHANGED
data/schema.json
ADDED
@@ -0,0 +1,224 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "http://json-schema.org/draft-06/schema#",
|
3
|
+
"title": "Dip Configuration Schema",
|
4
|
+
"description": "Schema for the dip.yml configuration file",
|
5
|
+
"type": "object",
|
6
|
+
"additionalProperties": false,
|
7
|
+
"definitions": {
|
8
|
+
"environment_vars": {
|
9
|
+
"type": "object",
|
10
|
+
"description": "Defines environment variables",
|
11
|
+
"additionalProperties": {
|
12
|
+
"type": "string"
|
13
|
+
},
|
14
|
+
"examples": [
|
15
|
+
{ "RAILS_ENV": "development" },
|
16
|
+
{ "DATABASE_URL": "postgres://user:password@db:5432/myapp_development" },
|
17
|
+
{ "PORT": "${PORT:-3000}" }
|
18
|
+
]
|
19
|
+
},
|
20
|
+
"interaction_command": {
|
21
|
+
"type": "object",
|
22
|
+
"description": "Configuration for an interaction command",
|
23
|
+
"additionalProperties": false,
|
24
|
+
"properties": {
|
25
|
+
"description": {
|
26
|
+
"type": "string",
|
27
|
+
"description": "Describes the command",
|
28
|
+
"examples": ["Run Rails commands", "Connect to PostgreSQL database"]
|
29
|
+
},
|
30
|
+
"service": {
|
31
|
+
"type": "string",
|
32
|
+
"description": "Specifies the service associated with the command",
|
33
|
+
"examples": ["web", "frontend", "db"]
|
34
|
+
},
|
35
|
+
"command": {
|
36
|
+
"type": "string",
|
37
|
+
"description": "Represents the command to be executed",
|
38
|
+
"examples": ["bundle exec rails", "npm", "psql -h db -U user myapp_development"]
|
39
|
+
},
|
40
|
+
"default_args": {
|
41
|
+
"type": "string",
|
42
|
+
"description": "Default arguments for the command",
|
43
|
+
"examples": ["server -p 3000 -b 0.0.0.0"]
|
44
|
+
},
|
45
|
+
"environment": {
|
46
|
+
"$ref": "#/definitions/environment_vars"
|
47
|
+
},
|
48
|
+
"compose": {
|
49
|
+
"type": "object",
|
50
|
+
"description": "Allows specifying Docker Compose options",
|
51
|
+
"additionalProperties": false,
|
52
|
+
"properties": {
|
53
|
+
"method": {
|
54
|
+
"type": "string",
|
55
|
+
"description": "Specifies the Docker Compose method (e.g., up, run)",
|
56
|
+
"examples": ["run", "up"]
|
57
|
+
},
|
58
|
+
"compose_method": {
|
59
|
+
"type": "string",
|
60
|
+
"description": "Specifies an alternative Docker Compose method to use in compose commands",
|
61
|
+
"examples": ["up"]
|
62
|
+
},
|
63
|
+
"run_options": {
|
64
|
+
"type": "array",
|
65
|
+
"items": {
|
66
|
+
"type": "string"
|
67
|
+
},
|
68
|
+
"description": "Options to pass to the 'docker-compose run' command",
|
69
|
+
"examples": [["service-ports", "rm"]]
|
70
|
+
},
|
71
|
+
"profiles": {
|
72
|
+
"type": "array",
|
73
|
+
"items": {
|
74
|
+
"type": "string"
|
75
|
+
},
|
76
|
+
"description": "Docker Compose profiles to use",
|
77
|
+
"examples": [["web", "development"], ["frontend"], ["test"]]
|
78
|
+
}
|
79
|
+
}
|
80
|
+
},
|
81
|
+
"shell": {
|
82
|
+
"type": "boolean",
|
83
|
+
"description": "Enables or disables shell interpolation"
|
84
|
+
},
|
85
|
+
"entrypoint": {
|
86
|
+
"type": "string",
|
87
|
+
"description": "Specifies the command entrypoint"
|
88
|
+
},
|
89
|
+
"runner": {
|
90
|
+
"type": "string",
|
91
|
+
"description": "Specifies the runner (e.g., docker_compose, kubectl)"
|
92
|
+
},
|
93
|
+
"subcommands": {
|
94
|
+
"type": "object",
|
95
|
+
"description": "Contains subcommands with the same structure as main commands",
|
96
|
+
"patternProperties": {
|
97
|
+
"^[a-zA-Z0-9_]+$": {
|
98
|
+
"$ref": "#/definitions/interaction_command"
|
99
|
+
}
|
100
|
+
},
|
101
|
+
"minProperties": 1,
|
102
|
+
"additionalProperties": false
|
103
|
+
}
|
104
|
+
}
|
105
|
+
}
|
106
|
+
},
|
107
|
+
"properties": {
|
108
|
+
"version": {
|
109
|
+
"type": "string",
|
110
|
+
"description": "Specifies the minimum required version of Dip",
|
111
|
+
"examples": ["8.1.0"]
|
112
|
+
},
|
113
|
+
"compose": {
|
114
|
+
"type": "object",
|
115
|
+
"description": "Contains Docker Compose configuration",
|
116
|
+
"properties": {
|
117
|
+
"files": {
|
118
|
+
"type": "array",
|
119
|
+
"items": {
|
120
|
+
"type": "string"
|
121
|
+
},
|
122
|
+
"description": "Array of strings representing paths to Docker Compose files",
|
123
|
+
"examples": [["docker-compose.yml", "docker-compose.override.yml"]]
|
124
|
+
},
|
125
|
+
"project_name": {
|
126
|
+
"type": "string",
|
127
|
+
"description": "Specifies the project name for Docker Compose",
|
128
|
+
"examples": ["app"]
|
129
|
+
},
|
130
|
+
"command": {
|
131
|
+
"type": "string",
|
132
|
+
"description": "Specifies an alternative Docker Compose command",
|
133
|
+
"examples": ["docker compose"]
|
134
|
+
},
|
135
|
+
"method": {
|
136
|
+
"type": "string",
|
137
|
+
"description": "Specifies the Docker Compose method to use"
|
138
|
+
}
|
139
|
+
}
|
140
|
+
},
|
141
|
+
"interaction": {
|
142
|
+
"type": "object",
|
143
|
+
"description": "Defines the commands and their configurations",
|
144
|
+
"patternProperties": {
|
145
|
+
"^[a-zA-Z0-9_]+$": {
|
146
|
+
"$ref": "#/definitions/interaction_command"
|
147
|
+
}
|
148
|
+
},
|
149
|
+
"additionalProperties": false
|
150
|
+
},
|
151
|
+
"provision": {
|
152
|
+
"type": "array",
|
153
|
+
"items": {
|
154
|
+
"type": "string"
|
155
|
+
},
|
156
|
+
"description": "Lists the commands to be executed for provisioning",
|
157
|
+
"examples": [
|
158
|
+
[
|
159
|
+
"dip compose down --volumes",
|
160
|
+
"dip compose build",
|
161
|
+
"dip rails db:migrate",
|
162
|
+
"dip npm install"
|
163
|
+
]
|
164
|
+
]
|
165
|
+
},
|
166
|
+
"environment": {
|
167
|
+
"$ref": "#/definitions/environment_vars"
|
168
|
+
},
|
169
|
+
"kubectl": {
|
170
|
+
"type": "object",
|
171
|
+
"description": "Contains Kubernetes configuration",
|
172
|
+
"additionalProperties": false,
|
173
|
+
"properties": {
|
174
|
+
"namespace": {
|
175
|
+
"type": "string",
|
176
|
+
"description": "Specifies the Kubernetes namespace to use",
|
177
|
+
"examples": ["app"]
|
178
|
+
}
|
179
|
+
}
|
180
|
+
},
|
181
|
+
"modules": {
|
182
|
+
"type": "array",
|
183
|
+
"items": {
|
184
|
+
"type": "string"
|
185
|
+
},
|
186
|
+
"description": "Paths to module configuration files",
|
187
|
+
"examples": [["production"]]
|
188
|
+
},
|
189
|
+
"infra": {
|
190
|
+
"type": "object",
|
191
|
+
"description": "Contains infrastructure services configuration",
|
192
|
+
"additionalProperties": false,
|
193
|
+
"patternProperties": {
|
194
|
+
"^[a-zA-Z0-9_]+$": {
|
195
|
+
"type": "object",
|
196
|
+
"additionalProperties": false,
|
197
|
+
"properties": {
|
198
|
+
"git": {
|
199
|
+
"type": "string",
|
200
|
+
"pattern": "^(git@|git://|https?://)[\\w\\d\\.@:\\-/]+$",
|
201
|
+
"description": "Git repository URL for the infrastructure component",
|
202
|
+
"examples": ["https://github.com/mycompany/redis-config.git"]
|
203
|
+
},
|
204
|
+
"ref": {
|
205
|
+
"type": "string",
|
206
|
+
"description": "Specifies the Git reference (branch, tag, or commit) to use",
|
207
|
+
"examples": ["main"]
|
208
|
+
},
|
209
|
+
"path": {
|
210
|
+
"type": "string",
|
211
|
+
"description": "Local path to the infrastructure component",
|
212
|
+
"examples": ["./infra/elasticsearch"]
|
213
|
+
}
|
214
|
+
},
|
215
|
+
"oneOf": [
|
216
|
+
{ "required": ["git", "ref"] },
|
217
|
+
{ "required": ["path"] }
|
218
|
+
]
|
219
|
+
}
|
220
|
+
}
|
221
|
+
}
|
222
|
+
},
|
223
|
+
"required": ["version", "interaction"]
|
224
|
+
}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dip
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 8.
|
4
|
+
version: 8.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- bibendi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-11-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -30,6 +30,40 @@ dependencies:
|
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: '2'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: json-schema
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '5'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '5'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: public_suffix
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 2.0.2
|
54
|
+
- - "<"
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '6.0'
|
57
|
+
type: :runtime
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: 2.0.2
|
64
|
+
- - "<"
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '6.0'
|
33
67
|
- !ruby/object:Gem::Dependency
|
34
68
|
name: bundler
|
35
69
|
requirement: !ruby/object:Gem::Requirement
|
@@ -212,6 +246,7 @@ files:
|
|
212
246
|
- lib/dip/interaction_tree.rb
|
213
247
|
- lib/dip/run_vars.rb
|
214
248
|
- lib/dip/version.rb
|
249
|
+
- schema.json
|
215
250
|
homepage: https://github.com/bibendi/dip
|
216
251
|
licenses:
|
217
252
|
- MIT
|
@@ -232,7 +267,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
232
267
|
- !ruby/object:Gem::Version
|
233
268
|
version: '0'
|
234
269
|
requirements: []
|
235
|
-
rubygems_version: 3.
|
270
|
+
rubygems_version: 3.5.21
|
236
271
|
signing_key:
|
237
272
|
specification_version: 4
|
238
273
|
summary: Ruby gem CLI tool for better interacting Docker Compose files.
|