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
@@ -0,0 +1,195 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ # Requirements
5
+ # =======================================================================
6
+
7
+ # Deps
8
+ # -----------------------------------------------------------------------
9
+
10
+ require 'nrser/props/mutable/stash'
11
+
12
+ # Need the experimental {NRSER::Stash} class that lets us intercept hash
13
+ # writes.
14
+ require 'nrser/labs/stash'
15
+
16
+
17
+ # Refinements
18
+ # =======================================================================
19
+
20
+ using NRSER::Types
21
+
22
+
23
+ # Definitions
24
+ # =======================================================================
25
+
26
+
27
+ # Encapsulation of data that an Ansible module responds to Ansible with.
28
+ #
29
+ # Ansible calls this a module's "return value".
30
+ #
31
+ # @see http://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html
32
+ #
33
+ class QB::Ansible::Module::Response < NRSER::Stash
34
+
35
+ # Mixins
36
+ # ========================================================================
37
+
38
+ include NRSER::Props::Mutable::Stash
39
+
40
+
41
+ # @!group Props
42
+ # ==========================================================================
43
+
44
+ # "Common" Values
45
+ # --------------------------------------------------------------------------
46
+
47
+ # For those modules that implement backup=no|yes when manipulating files,
48
+ # a path to the backup file created.
49
+ #
50
+ # The file must exist if provided.
51
+ #
52
+ prop :backup_file,
53
+ type: t.file_path?
54
+
55
+
56
+ # A boolean indicating if the task had to make changes.
57
+ #
58
+ prop :changed,
59
+ type: t.bool,
60
+ default: false
61
+
62
+
63
+ # A boolean that indicates if the task was failed or not.
64
+ #
65
+ prop :failed,
66
+ type: t.bool,
67
+ default: false
68
+
69
+ # Ansible says "Information on how the module was invoked."
70
+ #
71
+ # I have no idea what that means and couldn't find anywhere it was being
72
+ # set in a quick search of their module sources.
73
+ #
74
+ prop :invocation,
75
+ type: t.any
76
+
77
+
78
+ # A string with a generic message relayed to the user.
79
+ #
80
+ prop :msg,
81
+ type: t.str?
82
+
83
+
84
+ # A return (exit) code, if one makes sense, I guess.
85
+ #
86
+ # Ansible says:
87
+ #
88
+ # > Some modules execute command line utilities or are geared for executing
89
+ # > commands directly (raw, shell, command, etc), this field contains
90
+ # > ‘return code’ of these utilities.
91
+ #
92
+ prop :rc,
93
+ type: t.unsigned?
94
+
95
+
96
+ # "Internal Use" (Consumed by Ansible)
97
+ # --------------------------------------------------------------------------
98
+
99
+ # This key should contain a dictionary which will be appended to the facts
100
+ # assigned to the host.
101
+ #
102
+ # These will be directly accessible and don’t require using a registered
103
+ # variable.
104
+ #
105
+ prop :ansible_facts,
106
+ aliases: [ :facts ],
107
+ type: t.hash_( keys: t.non_empty_str ),
108
+ default: -> { HashWithIndifferentAccess.new },
109
+ from_data: :with_indifferent_access.to_proc
110
+
111
+
112
+ # This key can contain traceback information caused by an exception in a
113
+ # module. It will only be displayed [to the user] on high verbosity (-vvv).
114
+ #
115
+ # Unclear what the value type is, guessing string...
116
+ #
117
+ prop :exception,
118
+ type: t.str?
119
+
120
+
121
+ # @!attribute [rw] warnings
122
+ # This key contains a list of strings that will be presented to the user.
123
+ #
124
+ # @return [Array<String>]
125
+ # Non-empty string warning messages to return to Ansible.
126
+ #
127
+ prop :warnings,
128
+ type: t.array( t.non_empty_str ),
129
+ default: -> { [] }
130
+
131
+
132
+ # @!attribute [rw] depreciations
133
+ # This key contains a list of dictionaries that will be presented to the
134
+ # user. Keys of the dictionaries are msg and version, values are string,
135
+ # value for the version key can be an empty string.
136
+ #
137
+ # @return [Array<{ msg: String, version: String }>]
138
+ #
139
+ prop :depreciations,
140
+ type: t.array(
141
+ t.shape( msg: t.non_empty_str, version: t.non_empty_str )
142
+ ),
143
+ default: ->{ [] }
144
+
145
+
146
+ metadata.freeze
147
+
148
+ # @!endgroup Props # *******************************************************
149
+
150
+
151
+ # Constructor
152
+ # ======================================================================
153
+
154
+ # Instantiate a new `QB::Ansible::Module::Response`.
155
+ def initialize values = {}
156
+ super()
157
+ initialize_props values
158
+ end # #initialize
159
+
160
+
161
+ # Instance Methods
162
+ # ======================================================================
163
+
164
+ # Uses {Symbol} keys, and they can not be empty. Will convert non-empty
165
+ # strings to their symbols.
166
+ #
167
+ # @param [Symbol | String] key
168
+ # @return [Symbol]
169
+ #
170
+ def convert_key key
171
+ t.match key,
172
+ t.non_empty_str, :to_sym.to_proc,
173
+ t.non_empty_sym, key
174
+ end
175
+
176
+
177
+ # Create a new response to represent a failure, copying the stuff that makes
178
+ # sense to keep from this one.
179
+ #
180
+ # @param [type] arg_name
181
+ # @todo Add name param description.
182
+ #
183
+ # @return [return_type]
184
+ # @todo Document return value.
185
+ #
186
+ def to_failure msg:, warnings: [], depreciations: [], **values
187
+ self.class.new \
188
+ failed: true,
189
+ msg: msg,
190
+ warnings: (self.warnings + warnings),
191
+ depreciations: (self.depreciations + depreciations)
192
+ end # #to_failure
193
+
194
+
195
+ end # class QB::Ansible::Module::Response
@@ -0,0 +1,42 @@
1
+
2
+ module QB
3
+ module Ansible
4
+ # QB's built-in Ansible modules (that are written in Ruby).
5
+ #
6
+ # Putting them here seems better than in `//library` 'cause we want to support
7
+ # composing modules, something Ansible either totally doesn't support or at
8
+ # least doesn't advertise or encourage. This means we want our Ruby modules
9
+ # in the load path, and here seemed like a decent spot.
10
+ #
11
+ # None of these are required with `require 'qb'` - they need to be required
12
+ # individually.
13
+ #
14
+ # I created a "super module" to run them without needed to create executables
15
+ # for each one:
16
+ #
17
+ # - name: >-
18
+ # Run me some QB module...
19
+ #
20
+ # qb.module:
21
+ #
22
+ # # The module's relative path from `//lib/qb/ansible/modules`
23
+ # #
24
+ # # You can also use "relative" class name like `Docker::Image` or
25
+ # # "absolute" like `::QB::Some::Other::Module` to reach classes
26
+ # # *not* in `//lib/qb/ansible/modules`
27
+ # #
28
+ # name: docker/image
29
+ #
30
+ # # The arguments for the module
31
+ # args:
32
+ # path: /path/to/image/source
33
+ # # ...
34
+ #
35
+ # Check out `//library/qb.module.rb` for the source.
36
+ #
37
+ # This will also let us do other "super-level" stuff like provide common
38
+ # result-value-to-fact binding or whatever (just an idea).
39
+ #
40
+ #
41
+ #
42
+ module Modules; end; end; end
@@ -0,0 +1,273 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+
5
+ # Requirements
6
+ # ========================================================================
7
+
8
+ # Project / Package
9
+ # ------------------------------------------------------------------------
10
+
11
+ require 'qb'
12
+ require 'qb/docker/image'
13
+
14
+
15
+ # Namespace
16
+ # ========================================================================
17
+
18
+ module QB
19
+ module Ansible
20
+ module Modules
21
+ module Docker
22
+
23
+
24
+ # Definitions
25
+ # =======================================================================
26
+
27
+ # Build a Docker image if needed. Features to deal with version-tagging.
28
+ #
29
+ class Image < QB::Ansible::Module
30
+
31
+ # Arguments
32
+ # ========================================================================
33
+
34
+ arg :name,
35
+ type: QB::Docker::Image::Name
36
+
37
+ arg :path,
38
+ type: t.dir_path
39
+ # FIXME ( from_s: ->( s ) { Pathname.new( s ).expand_path } )
40
+
41
+ arg :from_image,
42
+ type: QB::Docker::Image::Name
43
+
44
+ arg :fact_name,
45
+ type: t.non_empty_str?
46
+
47
+ arg :build_arg,
48
+ type: t.hash_,
49
+ aliases: [ :build_args, :buildargs ],
50
+ reader: {
51
+ build_args: false,
52
+ buildargs: false,
53
+ },
54
+ default: ->{ {} }
55
+
56
+ arg :include_from_image_build,
57
+ type: t.bool,
58
+ default: true
59
+
60
+ # @!attribute [r] now
61
+ # The time to use as now. Defaults to get the current time, but here so
62
+ # that it can be provided externally so if you're doing a bunch of work
63
+ # you can make all the timestamps sync up. Pls don't abuse.
64
+ #
65
+ # @return [Time]
66
+ # UTC {Time} to use as now.
67
+ #
68
+ arg :now,
69
+ type: Time,
70
+ default: ->{ Time.now.utc }
71
+
72
+
73
+ # Helpers
74
+ # ========================================================================
75
+
76
+ # 'openresty/openresty:1.11.2.4-xenial'
77
+ # => repository: 'openresty'
78
+ # name: 'openresty'
79
+ # tag: {
80
+ # major: 1,
81
+ # minor: 11,
82
+ # patch: 2,
83
+ # revision: [4],
84
+ # prerelease: ['xenial'],
85
+ # }
86
+ # => ['openresty--openresty', 1, 11, 2, 4, 'xenial']
87
+ #
88
+ # which eventually becomes the Docker tag
89
+ #
90
+ # <semver>_openresty.openresty.1.11.2.4.xenial.<build_info?>
91
+ #
92
+ def build_segments_for_from_image
93
+ return [] unless include_from_image_build
94
+
95
+ [
96
+ [
97
+ (from_image.repository == 'beiarea' ? nil : from_image.repository),
98
+ from_image.name,
99
+ ].
100
+ compact.
101
+ map { |name| name.gsub( '_', '-' ) }.
102
+ join( '-' ),
103
+ *from_image.tag.version.to_a.flatten,
104
+ ]
105
+ end
106
+
107
+
108
+ def git
109
+ lazy_var :@git do
110
+ QB::Repo::Git.from_path path
111
+ end
112
+ end
113
+
114
+
115
+ def source_base_version
116
+ lazy_var :@source_base_version do
117
+ QB::Package::Version.from_string (path.to_pn / 'VERSION').read
118
+ end
119
+ end
120
+
121
+
122
+ def source_dev_version
123
+ lazy_var :@source_dev_version do
124
+ source_base_version.build_version \
125
+ branch: git.branch,
126
+ ref: git.head_short,
127
+ dirty: !git.clean?
128
+ # time: now
129
+ end
130
+ end
131
+
132
+
133
+ def source_version
134
+ lazy_var :@source_version do
135
+ if source_base_version.dev?
136
+ source_dev_version
137
+ else
138
+ source_non_dev_version
139
+ end
140
+ end
141
+ end
142
+
143
+
144
+ def source_non_dev_version
145
+ tag = "v#{ source_base_version.semver }"
146
+
147
+ # We check...
148
+ #
149
+ # 1. Repo is clean
150
+ unless git.clean?
151
+ raise "Can't build #{ source_base_version.level } version from " \
152
+ "dirty repo"
153
+ end
154
+
155
+ # 2. Tag exists
156
+ unless git.tags.include? tag
157
+ raise "Tag #{ tag } not found"
158
+ end
159
+
160
+ # 3. Tag points to current commit
161
+ tag_commit = Cmds.
162
+ new( "git rev-list -n 1 %s", chdir: path ).
163
+ out!( tag ).
164
+ chomp
165
+
166
+ unless tag_commit == git.head
167
+ raise "Repo is not at tag #{ tag } commit #{ tag_commit }"
168
+ end
169
+
170
+ # Ok, we just use the version in the file!
171
+ source_base_version
172
+ end
173
+
174
+
175
+ def image_version
176
+ lazy_var :@image_version do
177
+ source_version.merge \
178
+ build: [
179
+ *build_segments_for_from_image,
180
+ *source_version.build
181
+ ]
182
+ end
183
+ end
184
+
185
+
186
+ def image_name
187
+ lazy_var :@image_name do
188
+ name.merge \
189
+ source: nil, # NEED this!
190
+ tag: QB::Docker::Image::Tag.from_s(
191
+ image_version.docker_tag
192
+ )
193
+ end
194
+ end
195
+
196
+
197
+ def repo_clean?
198
+ lazy_var :@repo_clean do
199
+ git.clean?
200
+ end
201
+ end
202
+
203
+
204
+ def repo_dirty?
205
+ !repo_clean?
206
+ end
207
+
208
+
209
+ def force?
210
+ source_base_version.dev? && repo_dirty?
211
+ end
212
+
213
+
214
+ # Execution
215
+ # ==========================================================================
216
+
217
+ # Entry point for the module. invoked by {#run!}.
218
+ #
219
+ # @return [nil | {Symbol => #to_json}]
220
+ # when returning:
221
+ #
222
+ # - `nil`: module will successfully exit with no additional changes.
223
+ #
224
+ # - `{Symbol => #to_json}`: Hash will be merged over @facts that
225
+ # are returned by the module to be set in the Ansible runtime and
226
+ # the module will exit successfully.
227
+ #
228
+ def main
229
+ logger.info \
230
+ "Starting `_image`...",
231
+ path: path,
232
+ from_image: from_image.to_s,
233
+ build_arg: build_arg,
234
+ fact_name: fact_name
235
+
236
+
237
+ QB::Docker::Image.ensure_present! \
238
+ name: image_name,
239
+ pull: !image_version.dev?,
240
+ build: {
241
+ path: path,
242
+ build_arg: {
243
+ from_image: from_image.string,
244
+ image_version: image_version.semver,
245
+ **build_arg.to_options,
246
+ },
247
+ },
248
+ # Don't push dev images
249
+ push: !image_version.dev?,
250
+ force: force?
251
+
252
+ response[:image] = {
253
+ name: image_name,
254
+ version: image_version,
255
+ }
256
+
257
+ if fact_name
258
+ response.facts[fact_name] = response[:image]
259
+ end
260
+
261
+ return nil
262
+ end # #main
263
+
264
+ end # class Image
265
+
266
+
267
+ # /Namespace
268
+ # ========================================================================
269
+
270
+ end # module Docker
271
+ end # module Modules
272
+ end # module Ansible
273
+ end # module QB