qb 0.3.25 → 0.4.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.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/ansible.cfg +10 -1
  4. data/exe/.qb_interop_receive +3 -10
  5. data/exe/qb +8 -2
  6. data/lib/python/qb/__init__.py +6 -0
  7. data/{roles/qb/ruby/rspec/setup/tasks/persistence.yml → lib/python/qb/ansible/__init__.py} +0 -0
  8. data/lib/python/qb/ansible/modules/__init__.py +0 -0
  9. data/lib/python/qb/ansible/modules/docker/__init__.py +0 -0
  10. data/lib/python/qb/ansible/modules/docker/client.py +177 -0
  11. data/lib/python/qb/ansible/modules/docker/image_manager.py +754 -0
  12. data/lib/python/qb/ipc/__init__.py +0 -0
  13. data/lib/python/qb/ipc/stdio/__init__.py +99 -0
  14. data/lib/python/qb/ipc/stdio/logging.py +151 -0
  15. data/lib/qb.rb +3 -3
  16. data/lib/qb/ansible/cmds/playbook.rb +5 -14
  17. data/lib/qb/ansible/env.rb +36 -6
  18. data/lib/qb/ansible/module.rb +396 -152
  19. data/lib/qb/ansible/module/response.rb +195 -0
  20. data/lib/qb/ansible/modules.rb +42 -0
  21. data/lib/qb/ansible/modules/docker/image.rb +273 -0
  22. data/lib/qb/cli.rb +5 -18
  23. data/lib/qb/cli/run.rb +2 -2
  24. data/lib/qb/data.rb +22 -0
  25. data/lib/qb/data/immutable.rb +39 -0
  26. data/lib/qb/docker.rb +2 -0
  27. data/lib/qb/docker/cli.rb +430 -0
  28. data/lib/qb/docker/image.rb +207 -0
  29. data/lib/qb/docker/image/name.rb +309 -0
  30. data/lib/qb/docker/image/tag.rb +113 -0
  31. data/lib/qb/docker/repo.rb +0 -0
  32. data/lib/qb/errors.rb +17 -3
  33. data/lib/qb/execution.rb +83 -0
  34. data/lib/qb/ipc.rb +48 -0
  35. data/lib/qb/ipc/stdio.rb +32 -0
  36. data/lib/qb/ipc/stdio/client.rb +267 -0
  37. data/lib/qb/ipc/stdio/server.rb +229 -0
  38. data/lib/qb/ipc/stdio/server/in_service.rb +18 -0
  39. data/lib/qb/ipc/stdio/server/log_service.rb +168 -0
  40. data/lib/qb/ipc/stdio/server/out_service.rb +20 -0
  41. data/lib/qb/ipc/stdio/server/service.rb +229 -0
  42. data/lib/qb/options.rb +360 -502
  43. data/lib/qb/options/option.rb +293 -115
  44. data/lib/qb/options/option/option_parser_concern.rb +228 -0
  45. data/lib/qb/options/types.rb +73 -0
  46. data/lib/qb/package.rb +0 -1
  47. data/lib/qb/package/version.rb +179 -58
  48. data/lib/qb/package/version/from.rb +192 -51
  49. data/lib/qb/package/version/leveled.rb +1 -1
  50. data/lib/qb/path.rb +3 -2
  51. data/lib/qb/repo/git.rb +9 -85
  52. data/lib/qb/role/default_dir.rb +2 -2
  53. data/lib/qb/role/errors.rb +2 -8
  54. data/lib/qb/util.rb +1 -2
  55. data/lib/qb/util/bundler.rb +73 -43
  56. data/lib/qb/util/decorators.rb +99 -0
  57. data/lib/qb/util/interop.rb +7 -8
  58. data/lib/qb/util/resource.rb +12 -13
  59. data/lib/qb/version.rb +10 -0
  60. data/library/path_facts +5 -10
  61. data/library/qb.module.rb +105 -0
  62. data/library/stream +6 -26
  63. data/load/ansible/module/autorun.rb +25 -0
  64. data/load/ansible/module/script.rb +123 -0
  65. data/load/rebundle.rb +39 -0
  66. data/plugins/filter/dict_filters.py +56 -0
  67. data/plugins/{filter_plugins/path_plugins.py → filter/path_filters.py} +0 -0
  68. data/plugins/{filter_plugins/ruby_interop_plugins.py → filter/ruby_interop_filters.py} +1 -17
  69. data/plugins/{filter_plugins/string_plugins.py → filter/string_filters.py} +1 -20
  70. data/plugins/{filter_plugins/version_plugins.py → filter/version_filters.py} +3 -18
  71. data/plugins/{lookup_plugins/every.py → lookup/every_lookups.py} +0 -0
  72. data/plugins/{lookup_plugins/resolve.py → lookup/resolve_lookups.py} +0 -0
  73. data/plugins/{lookup_plugins/version.py → lookup/version_lookups.py} +0 -16
  74. data/plugins/test/dict_tests.py +36 -0
  75. data/plugins/test/string_tests.py +36 -0
  76. data/qb.gemspec +7 -3
  77. data/roles/nrser.rb/library/set_fact_with_ruby.rb +3 -9
  78. data/roles/nrser.state_mate/library/state +3 -17
  79. data/roles/qb/call/meta/qb.yml +1 -1
  80. data/roles/qb/dev/ref/repo/git/meta/qb.yml +1 -1
  81. data/roles/qb/{ruby/rspec/setup → docker/mac/kubernetes}/defaults/main.yml +1 -1
  82. data/roles/qb/{ruby/rspec/setup → docker/mac/kubernetes}/meta/main.yml +3 -2
  83. data/roles/qb/{ruby/rspec/setup → docker/mac/kubernetes}/meta/qb.yml +12 -7
  84. data/roles/qb/docker/mac/kubernetes/tasks/main.yml +45 -0
  85. data/roles/qb/git/check/clean/meta/qb.yml +1 -1
  86. data/roles/qb/git/ignore/meta/qb +10 -3
  87. data/roles/qb/git/submodule/update/library/git_submodule_update +17 -27
  88. data/roles/qb/github/pages/setup/meta/qb.yml +1 -1
  89. data/roles/qb/labs/atom/apm/meta/qb.yml +1 -1
  90. data/roles/qb/osx/git/change_case/meta/qb.yml +1 -1
  91. data/roles/qb/osx/notif/meta/qb.yml +1 -1
  92. data/roles/qb/pkg/bump/library/bump +4 -16
  93. data/roles/qb/role/qb/defaults/main.yml +2 -0
  94. data/roles/qb/role/qb/meta/qb.yml +10 -5
  95. data/roles/qb/role/qb/templates/qb.yml.j2 +7 -2
  96. data/roles/qb/role/templates/library/module.rb.j2 +12 -23
  97. data/roles/qb/role/templates/meta/main.yml.j2 +14 -1
  98. data/roles/qb/ruby/bundler/meta/qb.yml +1 -1
  99. data/roles/qb/ruby/dependency/meta/qb.yml +1 -1
  100. data/roles/qb/ruby/gem/bin_stubs/meta/qb.yml +1 -1
  101. data/roles/qb/ruby/gem/bin_stubs/templates/console +8 -2
  102. data/roles/qb/ruby/gem/build/meta/qb.yml +1 -1
  103. data/roles/qb/ruby/gem/new/meta/qb.yml +1 -1
  104. data/roles/qb/ruby/nrser/rspex/generate/meta/qb.yml +5 -5
  105. data/roles/qb/ruby/nrser/rspex/issue/meta/qb.yml +1 -1
  106. data/roles/qb/ruby/yard/clean/meta/qb.yml +1 -1
  107. data/roles/qb/ruby/yard/config/library/yard.get_output_dir +5 -15
  108. data/roles/qb/ruby/yard/config/meta/qb.yml +1 -1
  109. data/roles/qb/ruby/yard/setup/meta/qb.yml +1 -1
  110. metadata +71 -22
  111. data/lib/qb/ansible_module.rb +0 -5
  112. data/lib/qb/util/stdio.rb +0 -187
  113. data/roles/qb/ruby/rspec/setup/tasks/main.yml +0 -4
@@ -3,9 +3,8 @@
3
3
  # Requirements
4
4
  # =======================================================================
5
5
 
6
- # stdlib
7
-
8
6
  # deps
7
+ require 'nrser'
9
8
 
10
9
  # package
11
10
  require_relative './cli/help'
@@ -15,23 +14,11 @@ require_relative './cli/setup'
15
14
  require_relative './cli/list'
16
15
 
17
16
 
18
- # Requirements
19
- # =======================================================================
20
-
21
- require 'nrser/refinements'
22
- using NRSER
23
-
24
-
25
- # Declarations
26
- # =======================================================================
27
-
28
- module QB; end
29
-
30
-
31
17
  # Definitions
32
18
  # =======================================================================
33
19
 
34
- module QB::CLI
20
+ module QB
21
+ module CLI
35
22
 
36
23
 
37
24
  # Constants
@@ -55,7 +42,7 @@ module QB::CLI
55
42
  # ============================================================================
56
43
 
57
44
  # Add {.logger} and {#logger} methods
58
- include SemanticLogger::Loggable
45
+ include NRSER::Log::Mixin
59
46
 
60
47
 
61
48
  # Module (Static) Methods
@@ -202,4 +189,4 @@ module QB::CLI
202
189
  end
203
190
 
204
191
 
205
- end # module QB::CLI
192
+ end; end # module QB::CLI
@@ -180,7 +180,7 @@ module QB::CLI
180
180
  }
181
181
 
182
182
  set_options.values.each do |option|
183
- playbook_role[option.var_name] = option.value
183
+ playbook_role[option.var_name] = option.value_data
184
184
  end
185
185
 
186
186
  play =
@@ -212,7 +212,7 @@ module QB::CLI
212
212
  'vars' => {
213
213
  'role' => role.name,
214
214
  'args' => set_options.map { |option|
215
- [option.var_name, option.value]
215
+ [option.var_name, option.value_data]
216
216
  }.to_h,
217
217
  }
218
218
  }
@@ -0,0 +1,22 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+
5
+ # Definitions
6
+ # =======================================================================
7
+
8
+ # This class doesn't do *anything*... at least not yet. It serves as a marker
9
+ # for classes that are data (which must be {NRSER::Props}).
10
+ #
11
+ # @example Testing Membership
12
+ # object.is_a? QB::Data
13
+ #
14
+ module QB
15
+ module Data
16
+ end; end # module QB::Data
17
+
18
+
19
+ # Post-Processing
20
+ # ========================================================================
21
+
22
+ require_relative './data/immutable'
@@ -0,0 +1,39 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ # Requirements
5
+ # =======================================================================
6
+
7
+ # Deps
8
+ # -----------------------------------------------------------------------
9
+
10
+ require 'nrser/props/immutable/hash'
11
+
12
+
13
+ # Project / Package
14
+ # -----------------------------------------------------------------------
15
+
16
+ require_relative '../data'
17
+
18
+
19
+ # Definitions
20
+ # =======================================================================
21
+
22
+ # Abstract base class for immutable data classes. Based off {Hamster::Hash}.
23
+ #
24
+ # Using {Hamster::Hash}
25
+ #
26
+ module QB
27
+ module Data
28
+ class Immutable < Hamster::Hash
29
+
30
+ # Mixins
31
+ # ========================================================================
32
+
33
+ # Mark as a "data" class. Maybe will add some functionality at some point...
34
+ include QB::Data
35
+
36
+ # Infrastructure for a prop'd class based on {Hamster::Hash}
37
+ include NRSER::Props::Immutable::Hash
38
+
39
+ end; end; end # class QB::Data::Immutable
@@ -0,0 +1,2 @@
1
+ require_relative './docker/cli'
2
+ require_relative './docker/image'
@@ -0,0 +1,430 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ # Requirements
5
+ # =======================================================================
6
+
7
+ # Stdlib
8
+ # -----------------------------------------------------------------------
9
+
10
+ # Deps
11
+ # -----------------------------------------------------------------------
12
+
13
+ # Project / Package
14
+ # -----------------------------------------------------------------------
15
+
16
+
17
+ # Refinements
18
+ # =======================================================================
19
+
20
+
21
+ # Declarations
22
+ # =======================================================================
23
+
24
+
25
+ # Definitions
26
+ # =======================================================================
27
+
28
+
29
+ # @todo document QB::Docker::CLI class.
30
+ module QB
31
+ module Docker
32
+ module CLI
33
+
34
+ # Mixins
35
+ # ========================================================================
36
+
37
+ include NRSER::Log::Mixin
38
+
39
+ extend ::MethodDecorators
40
+
41
+
42
+ # Classes
43
+ # ==========================================================================
44
+
45
+ class Error < QB::Error
46
+
47
+ # Make an instance from a {Cmds::Result}.
48
+ #
49
+ # @param [Cmds::Result] result
50
+ # Result of command that error'd.
51
+ #
52
+ # @return [self]
53
+ #
54
+ def self.from_result result
55
+ new \
56
+ ( "Command `#{ result.cmd.truncate 40 }` " +
57
+ "failed with exit status #{ result.status }" ),
58
+ status: result.status,
59
+ stderr: result.err,
60
+ stdout: result.out
61
+ end # .from_result
62
+
63
+ end # class Error
64
+
65
+
66
+ class ManifestNotFoundError < Error; end
67
+
68
+
69
+ # Class Methods
70
+ # ========================================================================
71
+
72
+ # @!group Utility Class Methods
73
+ # --------------------------------------------------------------------------
74
+
75
+ # Create a {Cmds} with formatting options defaulted for how (at least most)
76
+ # `docker` subcommands seem to work.
77
+ #
78
+ # @see https://www.rubydoc.info/gems/cmds
79
+ #
80
+ # @param [String] exe:
81
+ # Docker executable to stick in front of `template`.
82
+ #
83
+ # @param (see Cmds.new)
84
+ #
85
+ # @return [Cmds]
86
+ #
87
+ def self.cmd \
88
+ template,
89
+ exe: 'docker',
90
+ array_mode: :repeat,
91
+ dash_opt_names: true,
92
+ hash_join_string: '=',
93
+ long_opt_separator: ' ',
94
+ **options
95
+ Cmds.new \
96
+ "#{ exe.shellescape } #{ template }",
97
+ array_mode: array_mode,
98
+ dash_opt_names: dash_opt_names,
99
+ hash_join_string: hash_join_string,
100
+ long_opt_separator: long_opt_separator,
101
+ **options
102
+ end # .cmd
103
+
104
+
105
+ # Call {.cmd} with a template suitable for most `docker` subcommands.
106
+ #
107
+ # Format is
108
+ #
109
+ # docker NAME [OPTS] [ARGS]
110
+ #
111
+ # @see .cmd
112
+ #
113
+ # @param [Array<#to_s>] *args
114
+ # Positional CLI arguments.
115
+ #
116
+ # @param [Hash] **opts
117
+ # CLI options.
118
+ #
119
+ # @return [Cmds]
120
+ #
121
+ +QB::Util::Decorators::NoPropsInKwds
122
+ def self.sub_cmd name, *args, **opts
123
+ cmd \
124
+ "<%= sub_cmd %> <%= opts %> <%= *args %>",
125
+ args: args,
126
+ kwds: {
127
+ sub_cmd: name,
128
+ opts: opts,
129
+ }
130
+ end # .sub_cmd
131
+
132
+ # @!endgroup Utility Class Methods # ***************************************
133
+
134
+
135
+ # @!group Sub-Command Class Methods
136
+ # --------------------------------------------------------------------------
137
+
138
+ # Build a `docker images` {Cmds} instance.
139
+ #
140
+ # @param [Array<#to_s>] *args
141
+ # Specific image names to get.
142
+ #
143
+ # @param [nil | Symbol | String] format:
144
+ # The `--format` option. Pass `nil` to use default formatting.
145
+ #
146
+ # @param [Hash] **opts
147
+ # Other CLI options.
148
+ #
149
+ # @return [Cmds]
150
+ #
151
+ +QB::Util::Decorators::NoPropsInKwds
152
+ def self.images_cmd *args, format: :raw, **opts
153
+ sub_cmd :images, *args, format: format, **opts
154
+ end # .images
155
+
156
+
157
+ # Get a hash of image data from `docker images`.
158
+ #
159
+ # @note
160
+ # If you just want the raw `docker images` output use:
161
+ #
162
+ # QB::Docker::CLI.images_cmd.out!
163
+ #
164
+ # @param *args (see .images_cmd)
165
+ # @param format: (see .images_cmd)
166
+ # @param **opts (see .images_cmd)
167
+ #
168
+ # @param [Boolean] load:
169
+ # When `true`, will parse `created_at` to a {Time} and combine
170
+ # `repository` and `tag` to a {QB::Docker::Image::Name}.
171
+ #
172
+ # @param [Boolean] only_named:
173
+ # Filter out images that don't have a name or tag.
174
+ #
175
+ # @return [Array<HashWithIndifferentAccess>]
176
+ # Entry keys and values:
177
+ #
178
+ # - `repository: String`
179
+ #
180
+ # Direct from command output, may be `"<none>"`.
181
+ #
182
+ # - `tag: String`
183
+ #
184
+ # Direct from command output, may be `"<none>"`.
185
+ #
186
+ # - `image_id: String`
187
+ #
188
+ # Short (12 character) image SHA256. Unless you pass the `--no-trunc`
189
+ # option, then it's the full `sha256:...` version.
190
+ #
191
+ # - `created_at: String | Time`
192
+ #
193
+ # Time image was created (I think? Could be tag...)
194
+ #
195
+ # - `String` if `load` keyword is `false`.
196
+ # - `Time` if `load` keyword is `true`.
197
+ #
198
+ # - `virtual_size: String`
199
+ #
200
+ # Direct from command output, like `"1.41GB"`.
201
+ #
202
+ # - `name: void | QB::Docker::Image::Name`
203
+ #
204
+ # Combination of `repository` and `tag` parsed into a
205
+ # {QB::Docker::Image::Name}.
206
+ #
207
+ # Only present if `load` is `true` *and*
208
+ #
209
+ #
210
+ +QB::Util::Decorators::NoPropsInKwds
211
+ def self.images *args, load: true, only_named: true, **opts
212
+ hashes = images_cmd( *args, **opts ).
213
+ out!.
214
+ split( "\n\n" ).
215
+ map { |chunk|
216
+ chunk.lines.map { |line|
217
+ key, _, value = line.chomp.partition ': '
218
+
219
+ if key == 'created_at'
220
+ value = Time.parse value
221
+ end
222
+
223
+ [key, value]
224
+ }.
225
+ to_h.
226
+ with_indifferent_access
227
+ }
228
+
229
+ if only_named
230
+ hashes.reject! { |hash|
231
+ hash.values_at( :repository, :tag ).any? { |v| v == '<none>' }
232
+ }
233
+ end
234
+
235
+ if load
236
+ hashes.each { |hash|
237
+ values = hash.values_at :repository, :tag
238
+
239
+ unless values.any? { |v| v == '<none>' }
240
+ hash[:name] = QB::Docker::Image::Name.from_s values.join( ':' )
241
+ end
242
+ }
243
+ end
244
+
245
+ hashes
246
+ end
247
+
248
+
249
+ +QB::Util::Decorators::NoPropsInKwds
250
+ def self.rmi_cmd *args, **opts
251
+ sub_cmd :rmi, *args, **opts
252
+ end
253
+
254
+ singleton_class.send :alias_method, :remove_images_cmd, :rmi_cmd
255
+
256
+
257
+ +QB::Util::Decorators::NoPropsInKwds
258
+ def self.rmi *args, method: :stream!, **opts
259
+ rmi_cmd( *args, **opts ).public_send method
260
+ end
261
+
262
+ singleton_class.send :alias_method, :remove_images, :rmi
263
+
264
+
265
+ +QB::Util::Decorators::NoPropsInKwds
266
+ def self.inspect_image_cmd *args, **opts
267
+ sub_cmd :inspect, *args, **opts
268
+ end
269
+
270
+
271
+ +QB::Util::Decorators::NoPropsInKwds
272
+ def self.inspect_image *names_or_ids, **opts
273
+ inspect_cmd( *names_or_ids, **opts ).out!.thru { |s| JSON.load s }
274
+ end
275
+
276
+
277
+ +QB::Util::Decorators::NoPropsInKwds
278
+ def self.pull_cmd *args, **opts
279
+ sub_cmd :pull, *args, **opts
280
+ end
281
+
282
+
283
+ # Pull an image.
284
+ #
285
+ # @param [String | QB::Docker::Image::Name] name
286
+ #
287
+ +QB::Util::Decorators::NoPropsInKwds
288
+ def self.pull name, **opts
289
+ logger.info "Pulling #{ name }...",
290
+ name: name,
291
+ opts: opts
292
+
293
+ result = pull_cmd( name, **opts ).capture
294
+
295
+ if result.ok?
296
+ logger.info "Successfully pulled #{ name }."
297
+ else
298
+ logger.info "Failed to pull #{ name }",
299
+ stderr: result.err
300
+ end
301
+
302
+ result
303
+ end
304
+
305
+
306
+ +QB::Util::Decorators::NoPropsInKwds
307
+ def self.push_cmd name, **opts
308
+ sub_cmd :push, name, **opts
309
+ end
310
+
311
+
312
+ +QB::Util::Decorators::NoPropsInKwds
313
+ def self.push name, **opts
314
+ logger.info "Pushing `#{ name }`...", name: name, opts: opts
315
+
316
+ result = push_cmd( name, **opts ).capture
317
+ end
318
+
319
+
320
+ +QB::Util::Decorators::NoPropsInKwds
321
+ def self.build_cmd path_or_url, **opts
322
+ sub_cmd :build, path_or_url, **opts
323
+ end
324
+
325
+
326
+ # +QB::Util::Decorators::NoPropsInKwds
327
+ # def self.build path_or_url, **opts
328
+ #
329
+ # end
330
+
331
+
332
+ def self.tag_cmd current_name, new_name_or_tag
333
+ # Load whatever we have
334
+ current_name = QB::Docker::Image::Name.from current_name
335
+
336
+ new_name_or_tag = [
337
+ QB::Docker::Image::Name,
338
+ QB::Docker::Image::Tag,
339
+ ].try_find { |klass| klass.from new_name_or_tag }
340
+
341
+ new_name = if new_name_or_tag.is_a?( QB::Docker::Image::Name )
342
+ if new_name_or_tag.tag
343
+ new_name_or_tag
344
+ else
345
+ new_name_or_tag.merge tag: current_name.tag
346
+ end
347
+ else
348
+ current_name.merge tag: new_name_or_tag
349
+ end
350
+
351
+ sub_cmd :tag, current_name, new_name_or_tag
352
+ end
353
+
354
+
355
+ # @!endgroup Sub-Command Class Methods # ***********************************
356
+
357
+
358
+ # @!group Sugar Class Methods
359
+ # --------------------------------------------------------------------------
360
+ #
361
+ # Making common shit easier.
362
+ #
363
+
364
+ # Get just image names from `docker images`.
365
+ #
366
+ # @param *args (see .images)
367
+ # @return [Array<QB::Docker::Image::Name>]
368
+ #
369
+ +QB::Util::Decorators::NoPropsInKwds
370
+ def self.image_names *args, **opts
371
+ images( *args, load: true, only_named: true, **opts ).
372
+ map &:name.to_retriever
373
+ end # .image_names
374
+
375
+
376
+ # Is there an image with the name?
377
+ #
378
+ # @param [QB::Docker::Image::Name | String] name
379
+ # @return [Boolean]
380
+ #
381
+ +QB::Util::Decorators::NoPropsInKwds
382
+ def self.image_named? name, **opts
383
+ !images( name, load: false, only_named: false, **opts ).empty?
384
+ end
385
+
386
+
387
+ # Boolean version of {.pull}.
388
+ #
389
+ # @param name (see .pull)
390
+ #
391
+ # @return [Boolean]
392
+ # `true` if the pull succeeded.
393
+ #
394
+ +QB::Util::Decorators::NoPropsInKwds
395
+ def self.pull? name, **opts
396
+ pull( name, **opts ).ok?
397
+ end
398
+
399
+
400
+ # Pull an image by name or raise.
401
+ #
402
+ # @param name (see .pull)
403
+ #
404
+ # @todo
405
+ # Support `@DIGEST`
406
+ #
407
+ # @raise [QB::Docker::CLI::ManifestNotFoundError]
408
+ # If the name was not found in the repository.
409
+ #
410
+ # @raise [QB::Docker::CLI::Error]
411
+ # If anything else goes wrong.
412
+ #
413
+ +QB::Util::Decorators::NoPropsInKwds
414
+ def self.pull! name, **opts
415
+ result = pull name, **opts
416
+
417
+ if result.err =~ /manifest.*not\ found/
418
+ raise QB::Docker::CLI::ManifestNotFoundError.new \
419
+ "Failed to pull - manifest for #{ name } not found",
420
+ name: name,
421
+ cmd: result.cmd,
422
+ stderr: result.err
423
+ else
424
+ raise QB::Docker::CLI::Error.from_result( result )
425
+ end
426
+ end
427
+
428
+ # @!endgroup Sugar Class Methods # *****************************************
429
+
430
+ end; end; end # module QB::Docker::CLI