qb 0.3.8 → 0.3.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +4 -0
  3. data/exe/.qb_interop_receive +39 -0
  4. data/exe/qb +8 -5
  5. data/lib/qb.rb +64 -19
  6. data/lib/qb/cli/run.rb +1 -1
  7. data/lib/qb/github.rb +4 -0
  8. data/lib/qb/github/api.rb +43 -0
  9. data/lib/qb/github/issue.rb +83 -0
  10. data/lib/qb/github/repo_id.rb +127 -0
  11. data/lib/qb/github/resource.rb +59 -0
  12. data/lib/qb/github/types.rb +45 -0
  13. data/lib/qb/package.rb +131 -0
  14. data/lib/qb/package/gem.rb +2 -4
  15. data/lib/qb/package/version.rb +175 -2
  16. data/lib/qb/path.rb +2 -2
  17. data/lib/qb/repo.rb +137 -1
  18. data/lib/qb/repo/git.rb +55 -23
  19. data/lib/qb/repo/git/github.rb +137 -0
  20. data/lib/qb/role.rb +74 -30
  21. data/lib/qb/util.rb +1 -4
  22. data/lib/qb/util/docker_mixin.rb +30 -2
  23. data/lib/qb/util/interop.rb +68 -7
  24. data/lib/qb/util/logging.rb +215 -0
  25. data/lib/qb/util/stdio.rb +25 -9
  26. data/lib/qb/version.rb +16 -28
  27. data/library/stream +2 -2
  28. data/plugins/filter_plugins/ruby_interop_plugins.py +49 -31
  29. data/qb.gemspec +16 -4
  30. data/roles/qb.role/defaults/main.yml +6 -2
  31. data/roles/{qb.bump → qb/pkg/bump}/.qb-options.yml +0 -0
  32. data/roles/{qb.bump → qb/pkg/bump}/README.md +0 -0
  33. data/roles/{qb.bump → qb/pkg/bump}/defaults/main.yml +0 -0
  34. data/roles/{qb.bump → qb/pkg/bump}/library/bump +0 -0
  35. data/roles/{qb.bump → qb/pkg/bump}/meta/main.yml +0 -0
  36. data/roles/{qb.bump → qb/pkg/bump}/meta/qb.yml +0 -0
  37. data/roles/{qb.bump → qb/pkg/bump}/tasks/frontend/level/dev.yml +0 -0
  38. data/roles/{qb.bump → qb/pkg/bump}/tasks/frontend/level/rc.yml +0 -0
  39. data/roles/{qb.bump → qb/pkg/bump}/tasks/frontend/level/release.yml +0 -0
  40. data/roles/{qb.bump → qb/pkg/bump}/tasks/frontend/main.yml +0 -0
  41. data/roles/{qb.bump → qb/pkg/bump}/tasks/main.yml +1 -1
  42. data/roles/{qb.qb_role → qb/role/qb}/.qb-options.yml +0 -0
  43. data/roles/{qb.qb_role → qb/role/qb}/defaults/main.yml +0 -0
  44. data/roles/{qb.qb_role → qb/role/qb}/meta/main.yml +0 -0
  45. data/roles/{qb.qb_role → qb/role/qb}/meta/qb.yml +0 -0
  46. data/roles/{qb.qb_role → qb/role/qb}/tasks/main.yml +0 -0
  47. data/roles/{qb.qb_role → qb/role/qb}/templates/.gitkeep +0 -0
  48. data/roles/{qb.qb_role → qb/role/qb}/templates/qb.yml.j2 +0 -0
  49. data/roles/qb/test/rspec/spec/issue/defaults/main.yml +2 -0
  50. data/roles/qb/test/rspec/spec/issue/meta/main.yml +8 -0
  51. data/roles/qb/test/rspec/spec/issue/meta/qb.yml +67 -0
  52. data/roles/qb/test/rspec/spec/issue/tasks/main.yml +21 -0
  53. metadata +95 -26
@@ -403,28 +403,40 @@ class QB::Role
403
403
  end
404
404
 
405
405
 
406
- # @todo This was gonna be something at some point, but didn't get done.
406
+ # Do our best to figure out a role name from a path (that might not exist).
407
407
  #
408
- def self.guess_role_name dest
409
- raise "TODO: Not implemented!!!"
410
-
411
- path = QB::Util.resolve dest
408
+ # We needs this for when we're creating a role.
409
+ #
410
+ # @param [String | Pathname] path
411
+ #
412
+ #
413
+ # @return [String]
414
+ #
415
+ def self.default_role_name path
416
+ resolved_path = QB::Util.resolve path
412
417
 
413
- search_dirs = search_path.find_all { |pathname|
414
- path.fnmatch?( pathname / '**' )
418
+ # Find the first directory in the search path that contains the path,
419
+ # if any do.
420
+ #
421
+ # It *could* be in more than one in funky situations like overlapping
422
+ # search paths or link silliness, but that doesn't matter - we consider
423
+ # the first place we find it to be the relevant once, since the search
424
+ # path is most-important-first.
425
+ #
426
+ search_dir = search_path.find { |pathname|
427
+ resolved_path.fnmatch? ( pathname / '**' ).to_s
415
428
  }
416
429
 
417
- case search_dirs.length
418
- when 0
430
+ if search_dir.nil?
419
431
  # It's not in any of the search directories
420
432
  #
421
433
  # If it has 'roles' as a segment than use what's after the last occurrence
422
434
  # of that (unless there isn't anything).
423
435
  #
424
- segments = path.to_s.split File::SEPARATOR
436
+ segments = resolved_path.to_s.split File::SEPARATOR
425
437
 
426
438
  if index = segments.rindex( 'roles' )
427
- name_segs = segments[index..-1]
439
+ name_segs = segments[( index + 1 )..( -1 )]
428
440
 
429
441
  unless name_segs.empty?
430
442
  return File.join name_segs
@@ -432,16 +444,15 @@ class QB::Role
432
444
  end
433
445
 
434
446
  # Ok, that didn't work... just return the basename I guess...
435
- File.basename path
436
-
437
- when 1
438
-
439
- else
440
- # Multiple matches?!?!?
441
-
447
+ return File.basename resolved_path
442
448
 
443
449
  end
444
- end # #guess_role_name
450
+
451
+ # it's in the search path, return the relative path from the containing
452
+ # search dir to the resolved path (string version of it).
453
+ resolved_path.relative_path_from( search_dir ).to_s
454
+
455
+ end # #default_role_name
445
456
 
446
457
 
447
458
  # Instance Attributes
@@ -871,20 +882,53 @@ class QB::Role
871
882
  end
872
883
 
873
884
 
874
- # Get the {Gem::Requirement} parse of the `qb_requirement` key in
875
- # {#meta} (if it is defined), which specifies the required version of
876
- # `qb` for the role.
885
+ # Parsed tree structure of version requirements of the role from the
886
+ # `requirements` value in the QB meta data.
877
887
  #
878
- # @return [Gem::Requirement, nil]
879
- # The requirement if `required_qb_version` key is in {#meta}, else `nil`.
888
+ # @return [Hash]
889
+ # Tree where the leaves are {Gem::Requirement}.
880
890
  #
881
- def qb_requirement
882
- if meta['requirements'] &&
883
- meta['requirements']['gems'] &&
884
- meta['requirements']['gems']['qb']
885
- Gem::Requirement.new meta['requirements']['gems']['qb']
891
+ def requirements
892
+ @requirements ||= NRSER.map_leaves(
893
+ meta_or 'requirements', {'gems' => {}}
894
+ ) { |key_path, req_str|
895
+ Gem::Requirement.new req_str
896
+ }
897
+ end # #requirements
898
+
899
+
900
+ # Check the role's requirements.
901
+ #
902
+ # @return [nil]
903
+ #
904
+ # @raise [QB::AnsibleVersionError]
905
+ # If the version of Ansible found does not satisfy the role's requirements.
906
+ #
907
+ # @raise [QB::QBVersionError]
908
+ # If the the version of QB we're running does not satisfy the role's
909
+ # requirements.
910
+ #
911
+ def check_requirements
912
+ if ansible_req = requirements['ansible']
913
+ unless ansible_req.satisfied_by? QB.ansible_version
914
+ raise QB::AnsibleVersionError.squished <<-END
915
+ QB #{ QB::VERSION } requires Ansible #{ ansible_req },
916
+ found version #{ QB.ansible_version } at #{ `which ansible` }
917
+ END
918
+ end
886
919
  end
887
- end
920
+
921
+ if qb_req = requirements.dig( 'gems', 'qb' )
922
+ unless qb_req.satisfied_by? QB.gem_version
923
+ raise QB::QBVersionError.squished <<-END
924
+ Role #{ self } requires QB #{ qb_req },
925
+ using QB #{ QB.gem_version } from #{ QB::ROOT }.
926
+ END
927
+ end
928
+ end
929
+
930
+ nil
931
+ end # #check_requirements
888
932
 
889
933
 
890
934
  # Language Inter-Op
@@ -1,3 +1,4 @@
1
+ require_relative './util/logging'
1
2
  require_relative './util/stdio'
2
3
  require_relative './util/interop'
3
4
  require_relative './util/bundler'
@@ -23,10 +24,6 @@ module QB
23
24
 
24
25
  full_string_words = words full_string
25
26
 
26
- QB.debug "HERE",
27
- input_words: input_words,
28
- full_string_words: full_string_words
29
-
30
27
  full_string_words.each_with_index {|word, start_index|
31
28
  # compute the end index in full_string_words
32
29
  end_index = start_index + input_words.length - 1
@@ -4,14 +4,42 @@ module QB
4
4
  module Util
5
5
 
6
6
  # Mixin to help working with Docker.
7
- module DockerMixin
7
+ module DockerMixin
8
+
9
+ # Character limit for Docker image tags.
10
+ #
11
+ # @return [Fixnum]
12
+ #
8
13
  DOCKER_TAG_MAX_CHARACTERS = 128
14
+
9
15
 
10
- # Regexp to validate strings as Docker tags.
16
+ # Regexp to validate strings as Docker tags:
17
+ #
18
+ # 1. Must start with an ASCII alpha-numeric - `A-Z`, `a-z`, `0-9`.
19
+ #
20
+ # 2. The rest of the characters can be:
21
+ #
22
+ # 1. `A-Z`
23
+ # 2. `a-z`
24
+ # 3. `_`
25
+ # 4. `.`
26
+ # 5. `-`
27
+ #
28
+ # Note that it *can not* include `+`, so [Semver][] strings with
29
+ # build info after the `+` are not legal.
30
+ #
31
+ # 3. Must be {QB::Util::DockerMixin::DOCKER_TAG_MAX_CHARACTERS} in length
32
+ # or less.
33
+ #
34
+ # [Semver]: https://semver.org/
35
+ #
36
+ # @return [Regexp]
37
+ #
11
38
  DOCKER_TAG_VALID_RE = \
12
39
  /\A[A-Za-z0-9_][A-Za-z0-9_\.\-]{0,#{ DOCKER_TAG_MAX_CHARACTERS - 1}}\z/.
13
40
  freeze
14
41
 
42
+
15
43
  # Class methods to extend the receiver with when {QB::Util::DockerMixin}
16
44
  # is included.
17
45
  module ClassMethods
@@ -6,8 +6,24 @@ module QB
6
6
  module Util
7
7
 
8
8
  module Interop
9
+ include SemanticLogger::Loggable
10
+
9
11
  class << self
10
12
 
13
+ def send_to_instance data, method_name, *args
14
+ logger.debug "Starting #send_to_instance..."
15
+
16
+ obj = if data.is_a?( Hash ) &&
17
+ data.key?( NRSER::Meta::Props::DEFAULT_CLASS_KEY )
18
+ NRSER::Meta::Props.UNSAFE_load_instance_from_data data
19
+ else
20
+ data
21
+ end
22
+
23
+ obj.send method_name, *args
24
+ end # #send_to_instance
25
+
26
+
11
27
  # @todo Document receive method.
12
28
  #
13
29
  # @param [type] arg_name
@@ -16,28 +32,73 @@ module Interop
16
32
  # @return [return_type]
17
33
  # @todo Document return value.
18
34
  #
19
- def receive
35
+ def send_to_const name, method_name, *args
36
+ logger.debug "Starting #send_to_const..."
37
+
38
+ const = name.to_const
39
+
40
+ logger.debug "Found constant", const: const
41
+
42
+ const.public_send method_name, *args
43
+
44
+ end # #receive
45
+
46
+
47
+ # @todo Document receive method.
48
+ #
49
+ # @param [type] arg_name
50
+ # @todo Add name param description.
51
+ #
52
+ # @return [return_type]
53
+ # @todo Document return value.
54
+ #
55
+ def receive
56
+ logger.debug "Starting #receive..."
57
+
20
58
  # method body
21
59
  yaml = $stdin.read
22
60
 
23
61
  payload = YAML.load yaml
24
62
 
25
- data = payload.fetch 'data'
63
+ logger.debug "Parsed",
64
+ payload: payload
65
+
66
+ # data = payload.fetch 'data'
26
67
  method = payload.fetch 'method'
27
68
  args = payload['args'] || []
28
69
  kwds = payload['kwds'] || {}
29
70
  args << kwds.symbolize_keys unless kwds.empty?
30
71
 
31
- obj = if data.is_a?(Hash) && data.key?(NRSER::Meta::Props::CLASS_KEY)
32
- NRSER::Meta::Props.from_data data
72
+ result = if payload['data']
73
+ send_to_instance payload['data'], method, *args
74
+
75
+ elsif payload['const']
76
+ send_to_const payload['const'], method, *args
77
+
33
78
  else
34
- data
79
+ raise ArgumentError.new binding.erb <<-ERB
80
+ Expected payload to have 'data' or 'const' keys, neither found:
81
+
82
+ Payload:
83
+
84
+ <%= payload.pretty_inspect %>
85
+
86
+ Input YAML:
87
+
88
+ <%= yaml %>
89
+
90
+ ERB
35
91
  end
36
92
 
37
- result = obj.send method, *args
93
+ logger.debug "send succeeded", result: result
94
+
95
+ yaml = result.to_yaml # don't work: sort_keys: true, use_header: true
96
+
97
+ logger.debug "writing YAML:\n\n#{ yaml }"
38
98
 
39
- $stdout.write result.to_yaml
99
+ $stdout.write yaml
40
100
 
101
+ logger.debug "done."
41
102
  end # #receive
42
103
 
43
104
  end # class << self
@@ -0,0 +1,215 @@
1
+ # Requirements
2
+ # =======================================================================
3
+
4
+ # Stdlib
5
+ # -----------------------------------------------------------------------
6
+
7
+ # Deps
8
+ # -----------------------------------------------------------------------
9
+ require 'awesome_print'
10
+ require 'semantic_logger'
11
+
12
+ # Project / Package
13
+ # -----------------------------------------------------------------------
14
+
15
+
16
+ # Refinements
17
+ # =======================================================================
18
+
19
+
20
+ # Declarations
21
+ # =======================================================================
22
+
23
+ module QB; end
24
+ module QB::Util; end
25
+
26
+
27
+ # Definitions
28
+ # =======================================================================
29
+
30
+ # Utility methods to setup logging with [semantic_logger][].
31
+ #
32
+ # [semantic_logger]: http://rocketjob.github.io/semantic_logger/
33
+ #
34
+ module QB::Util::Logging
35
+ include SemanticLogger::Loggable
36
+
37
+
38
+ # @todo document Formatters module.
39
+ module Formatters
40
+
41
+ # Custom tweaked color formatter (for CLI output).
42
+ #
43
+ # - Turns on multiline output in Awesome Print by default.
44
+ #
45
+ class Color < SemanticLogger::Formatters::Color
46
+
47
+ # Constants
48
+ # ======================================================================
49
+
50
+
51
+ # Class Methods
52
+ # ======================================================================
53
+
54
+
55
+ # Attributes
56
+ # ======================================================================
57
+
58
+
59
+ # Constructor
60
+ # ======================================================================
61
+
62
+ # Instantiate a new `ColorFormatter`.
63
+ def initialize **options
64
+ super ap: { multiline: true },
65
+ color_map: SemanticLogger::Formatters::Color::ColorMap.new(
66
+ debug: SemanticLogger::AnsiColors::MAGENTA,
67
+ ),
68
+ **options
69
+ end # #initialize
70
+
71
+
72
+ # Instance Methods
73
+ # ======================================================================
74
+
75
+
76
+ # Upcase the log level.
77
+ #
78
+ # @return [String]
79
+ #
80
+ def level
81
+ "#{ color }#{ log.level.upcase }#{ color_map.clear }"
82
+ end
83
+
84
+
85
+ # Create the log entry text. Overridden to customize appearance -
86
+ # generally reduce amount of info and put payload on it's own line.
87
+ #
88
+ # We need to replace *two* super functions, the first being
89
+ # [SemanticLogger::Formatters::Color#call][]:
90
+ #
91
+ # def call(log, logger)
92
+ # self.color = color_map[log.level]
93
+ # super(log, logger)
94
+ # end
95
+ #
96
+ # [SemanticLogger::Formatters::Color#call]: https://github.com/rocketjob/semantic_logger/blob/v4.2.0/lib/semantic_logger/formatters/color.rb#L98
97
+ #
98
+ # which doesn't do all too much, and the next being it's super-method,
99
+ # [SemanticLogger::Formatters::Default#call][]:
100
+ #
101
+ # # Default text log format
102
+ # # Generates logs of the form:
103
+ # # 2011-07-19 14:36:15.660235 D [1149:ScriptThreadProcess] Rails -- Hello World
104
+ # def call(log, logger)
105
+ # self.log = log
106
+ # self.logger = logger
107
+ #
108
+ # [time, level, process_info, tags, named_tags, duration, name, message, payload, exception].compact.join(' ')
109
+ # end
110
+ #
111
+ # [SemanticLogger::Formatters::Default#call]: https://github.com/rocketjob/semantic_logger/blob/v4.2.0/lib/semantic_logger/formatters/default.rb#L64
112
+ #
113
+ # which does most the real assembly.
114
+ #
115
+ # @param [SemanticLogger::Log] log
116
+ # The log entry to format.
117
+ #
118
+ # See [SemanticLogger::Log](https://github.com/rocketjob/semantic_logger/blob/v4.2.0/lib/semantic_logger/log.rb)
119
+ #
120
+ # @param [SemanticLogger::Logger] logger
121
+ # The logger doing the logging (pretty sure, haven't checked).
122
+ #
123
+ # See [SemanticLogger::Logger](https://github.com/rocketjob/semantic_logger/blob/v4.2.0/lib/semantic_logger/logger.rb)
124
+ #
125
+ # @return [return_type]
126
+ # @todo Document return value.
127
+ #
128
+ def call log, logger
129
+ # SemanticLogger::Formatters::Color code
130
+ self.color = color_map[log.level]
131
+
132
+ # SemanticLogger::Formatters::Default code
133
+ self.log = log
134
+ self.logger = logger
135
+
136
+ [
137
+ # time, annoyingly noisy and don't really need for local CLI app
138
+ level,
139
+ process_info,
140
+ tags,
141
+ named_tags,
142
+ duration,
143
+ name,
144
+ ].compact.join( ' ' ) +
145
+ "\n" +
146
+ [
147
+ message,
148
+ payload,
149
+ exception,
150
+ ].compact.join(' ') +
151
+ "\n" # I like extra newline to space shit out
152
+
153
+ end # #call
154
+
155
+
156
+ end # class Color
157
+
158
+ end # module Formatters
159
+
160
+
161
+
162
+
163
+ # Module (Class) Methods
164
+ # =====================================================================
165
+
166
+ # Setup logging.
167
+ #
168
+ # @param [type] arg_name
169
+ # @todo Add name param description.
170
+ #
171
+ # @return [return_type]
172
+ # @todo Document return value.
173
+ #
174
+ def self.setup level: nil
175
+ if level.nil?
176
+ if ENV['QB_LOG_LEVEL']
177
+ level = ENV['QB_LOG_LEVEL'].to_sym
178
+ else
179
+ level = :info
180
+ end
181
+ end
182
+
183
+ SemanticLogger.default_level = level
184
+
185
+ @appender ||= SemanticLogger.add_appender(
186
+ io: $stderr,
187
+ formatter: Formatters::Color.new,
188
+ )
189
+
190
+ # Set ENV vars (that Ansible modules will have access to!)
191
+
192
+ ENV['QB_LOG_LEVEL'] = level.to_s
193
+
194
+ if level == :debug
195
+ ENV['QB_DEBUG'] = 'true'
196
+ logger.debug "debug logging is ON"
197
+ else
198
+ ENV.delete 'QB_DEBUG'
199
+ end
200
+
201
+ nil
202
+ end # .setup
203
+
204
+
205
+ def self.appender
206
+ @appender
207
+ end
208
+
209
+
210
+ end # module QB::Util::Logging
211
+
212
+
213
+
214
+ # Post-Processing
215
+ # =======================================================================