qb 0.3.8 → 0.3.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +4 -0
- data/exe/.qb_interop_receive +39 -0
- data/exe/qb +8 -5
- data/lib/qb.rb +64 -19
- data/lib/qb/cli/run.rb +1 -1
- data/lib/qb/github.rb +4 -0
- data/lib/qb/github/api.rb +43 -0
- data/lib/qb/github/issue.rb +83 -0
- data/lib/qb/github/repo_id.rb +127 -0
- data/lib/qb/github/resource.rb +59 -0
- data/lib/qb/github/types.rb +45 -0
- data/lib/qb/package.rb +131 -0
- data/lib/qb/package/gem.rb +2 -4
- data/lib/qb/package/version.rb +175 -2
- data/lib/qb/path.rb +2 -2
- data/lib/qb/repo.rb +137 -1
- data/lib/qb/repo/git.rb +55 -23
- data/lib/qb/repo/git/github.rb +137 -0
- data/lib/qb/role.rb +74 -30
- data/lib/qb/util.rb +1 -4
- data/lib/qb/util/docker_mixin.rb +30 -2
- data/lib/qb/util/interop.rb +68 -7
- data/lib/qb/util/logging.rb +215 -0
- data/lib/qb/util/stdio.rb +25 -9
- data/lib/qb/version.rb +16 -28
- data/library/stream +2 -2
- data/plugins/filter_plugins/ruby_interop_plugins.py +49 -31
- data/qb.gemspec +16 -4
- data/roles/qb.role/defaults/main.yml +6 -2
- data/roles/{qb.bump → qb/pkg/bump}/.qb-options.yml +0 -0
- data/roles/{qb.bump → qb/pkg/bump}/README.md +0 -0
- data/roles/{qb.bump → qb/pkg/bump}/defaults/main.yml +0 -0
- data/roles/{qb.bump → qb/pkg/bump}/library/bump +0 -0
- data/roles/{qb.bump → qb/pkg/bump}/meta/main.yml +0 -0
- data/roles/{qb.bump → qb/pkg/bump}/meta/qb.yml +0 -0
- data/roles/{qb.bump → qb/pkg/bump}/tasks/frontend/level/dev.yml +0 -0
- data/roles/{qb.bump → qb/pkg/bump}/tasks/frontend/level/rc.yml +0 -0
- data/roles/{qb.bump → qb/pkg/bump}/tasks/frontend/level/release.yml +0 -0
- data/roles/{qb.bump → qb/pkg/bump}/tasks/frontend/main.yml +0 -0
- data/roles/{qb.bump → qb/pkg/bump}/tasks/main.yml +1 -1
- data/roles/{qb.qb_role → qb/role/qb}/.qb-options.yml +0 -0
- data/roles/{qb.qb_role → qb/role/qb}/defaults/main.yml +0 -0
- data/roles/{qb.qb_role → qb/role/qb}/meta/main.yml +0 -0
- data/roles/{qb.qb_role → qb/role/qb}/meta/qb.yml +0 -0
- data/roles/{qb.qb_role → qb/role/qb}/tasks/main.yml +0 -0
- data/roles/{qb.qb_role → qb/role/qb}/templates/.gitkeep +0 -0
- data/roles/{qb.qb_role → qb/role/qb}/templates/qb.yml.j2 +0 -0
- data/roles/qb/test/rspec/spec/issue/defaults/main.yml +2 -0
- data/roles/qb/test/rspec/spec/issue/meta/main.yml +8 -0
- data/roles/qb/test/rspec/spec/issue/meta/qb.yml +67 -0
- data/roles/qb/test/rspec/spec/issue/tasks/main.yml +21 -0
- metadata +95 -26
data/lib/qb/role.rb
CHANGED
@@ -403,28 +403,40 @@ class QB::Role
|
|
403
403
|
end
|
404
404
|
|
405
405
|
|
406
|
-
#
|
406
|
+
# Do our best to figure out a role name from a path (that might not exist).
|
407
407
|
#
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
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
|
-
|
414
|
-
|
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
|
-
|
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 =
|
436
|
+
segments = resolved_path.to_s.split File::SEPARATOR
|
425
437
|
|
426
438
|
if index = segments.rindex( 'roles' )
|
427
|
-
name_segs = segments[index
|
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
|
436
|
-
|
437
|
-
when 1
|
438
|
-
|
439
|
-
else
|
440
|
-
# Multiple matches?!?!?
|
441
|
-
|
447
|
+
return File.basename resolved_path
|
442
448
|
|
443
449
|
end
|
444
|
-
|
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
|
-
#
|
875
|
-
#
|
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 [
|
879
|
-
#
|
888
|
+
# @return [Hash]
|
889
|
+
# Tree where the leaves are {Gem::Requirement}.
|
880
890
|
#
|
881
|
-
def
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
Gem::Requirement.new
|
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
|
-
|
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
|
data/lib/qb/util.rb
CHANGED
@@ -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
|
data/lib/qb/util/docker_mixin.rb
CHANGED
@@ -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
|
data/lib/qb/util/interop.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
32
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
+
# =======================================================================
|