qb 0.3.25 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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