qb 0.1.36 → 0.1.37
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ansible.cfg +1 -1
- data/bin/rake +3 -0
- data/dev/scratch/ansible_module/defaults/main.yml +2 -0
- data/dev/scratch/ansible_module/library/test +22 -0
- data/dev/scratch/ansible_module/meta/main.yml +8 -0
- data/dev/scratch/ansible_module/meta/qb.yml +44 -0
- data/dev/scratch/ansible_module/tasks/main.yml +9 -0
- data/dev/scratch/stdio/defaults/main.yml +4 -0
- data/dev/scratch/stdio/library/test +34 -0
- data/dev/scratch/stdio/meta/main.yml +8 -0
- data/dev/scratch/stdio/meta/qb.yml +55 -0
- data/dev/scratch/stdio/tasks/main.yml +5 -0
- data/exe/qb +14 -1
- data/lib/qb.rb +4 -9
- data/lib/qb/ansible_module.rb +65 -0
- data/lib/qb/options.rb +4 -5
- data/lib/qb/role.rb +63 -14
- data/lib/qb/util.rb +54 -0
- data/lib/qb/util/stdio.rb +86 -0
- data/lib/qb/version.rb +1 -1
- data/qb.gemspec +1 -0
- data/roles/qb.git_submodule_update/.qb-options.yml +3 -0
- data/roles/qb.git_submodule_update/README.md +5 -0
- data/roles/qb.git_submodule_update/defaults/main.yml +3 -0
- data/roles/qb.git_submodule_update/library/git_submodule_update +167 -0
- data/roles/qb.git_submodule_update/meta/main.yml +8 -0
- data/roles/qb.git_submodule_update/meta/qb.yml +44 -0
- data/roles/qb.git_submodule_update/tasks/main.yml +11 -0
- data/roles/qb.role/defaults/main.yml +1 -0
- data/roles/qb.role/meta/qb.yml +5 -0
- data/roles/qb.role/tasks/main.yml +11 -0
- data/roles/qb.role/templates/README.md.j2 +4 -0
- metadata +44 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cafbcb81c0a8179b326824b5b89203534604398c
|
4
|
+
data.tar.gz: 46823f3b27926e4c5652f4fac6b2932d3d74250a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b6d08dab932b56a417c31fd56dd3584d8cf6874f6f467dcd22e575806fe39e8894fa447905f18258023be19a0f2c0b17389d1bdb941397058b63f567180fdb8c
|
7
|
+
data.tar.gz: 3167ccdd9f411767e2302c58adf8d697e428c7515d5c30502e645fe4c43a2d707d34e99fe2c77850c45e77b0607f33dff6bde49744de9ea855ecc48cea16daa0
|
data/ansible.cfg
CHANGED
data/bin/rake
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# WANT_JSON
|
3
|
+
|
4
|
+
# init bundler in dev env
|
5
|
+
if ENV['QB_DEV_ENV']
|
6
|
+
ENV.each {|k, v|
|
7
|
+
if k.start_with? 'QB_DEV_ENV_'
|
8
|
+
ENV[k.sub('QB_DEV_ENV_', '')] = v
|
9
|
+
end
|
10
|
+
}
|
11
|
+
require 'bundler/setup'
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'qb'
|
15
|
+
|
16
|
+
class Test < QB::AnsibleModule
|
17
|
+
def main
|
18
|
+
changed! blah: "blow me: #{ @args['x'] }"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Test.new.run
|
@@ -0,0 +1,44 @@
|
|
1
|
+
---
|
2
|
+
# meta/qb.yml file for ansible_module
|
3
|
+
#
|
4
|
+
# qb settings for this role. see README.md for more info.
|
5
|
+
#
|
6
|
+
|
7
|
+
# description of the role to show in it's help output.
|
8
|
+
description: null
|
9
|
+
|
10
|
+
# prefix for role variables
|
11
|
+
var_prefix: null
|
12
|
+
|
13
|
+
# how to get a default for `dir` if it's not provided as the only
|
14
|
+
# positional argument. if a positional argument is provided it will
|
15
|
+
# override the method defined here.
|
16
|
+
#
|
17
|
+
# options:
|
18
|
+
#
|
19
|
+
# - null
|
20
|
+
# - require the value on the command line.
|
21
|
+
# - git_root
|
22
|
+
# - use the git root fof the directory that the `qb` command is invoked
|
23
|
+
# from. useful for 'project-centric' commands so they can be invoked
|
24
|
+
# from anywhere in the repo.
|
25
|
+
# - cwd
|
26
|
+
# - use the directory the `qb` command is invoked form.
|
27
|
+
# - {exe: PATH}
|
28
|
+
# - invoke an execuable, passing a JSON serialization of the options
|
29
|
+
# mapping their CLI names to values. path can be relative to role
|
30
|
+
# directory.
|
31
|
+
default_dir: cwd
|
32
|
+
|
33
|
+
# default user to become for play
|
34
|
+
default_user: null
|
35
|
+
|
36
|
+
# set to false to not save options in .qb-options.yml files
|
37
|
+
save_options: false
|
38
|
+
|
39
|
+
options: []
|
40
|
+
# - name: example
|
41
|
+
# description: an example of a variable.
|
42
|
+
# required: false
|
43
|
+
# type: boolean # boolean (default) | string
|
44
|
+
# short: e
|
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# WANT_JSON
|
3
|
+
|
4
|
+
# init bundler in dev env
|
5
|
+
if ENV['QB_DEV_ENV']
|
6
|
+
ENV.each {|k, v|
|
7
|
+
if k.start_with? 'QB_DEV_ENV_'
|
8
|
+
ENV[k.sub('QB_DEV_ENV_', '')] = v
|
9
|
+
end
|
10
|
+
}
|
11
|
+
require 'bundler/setup'
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'qb'
|
15
|
+
require 'pp'
|
16
|
+
|
17
|
+
class Test < QB::AnsibleModule
|
18
|
+
def main
|
19
|
+
QB.debug args: @args
|
20
|
+
|
21
|
+
if @args['count']
|
22
|
+
(1..10).each {|i|
|
23
|
+
puts i
|
24
|
+
sleep 1
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
raise "HERE" if @args['raise']
|
29
|
+
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
Test.new.run
|
@@ -0,0 +1,55 @@
|
|
1
|
+
---
|
2
|
+
# meta/qb.yml file for stdio
|
3
|
+
#
|
4
|
+
# qb settings for this role. see README.md for more info.
|
5
|
+
#
|
6
|
+
|
7
|
+
# description of the role to show in it's help output.
|
8
|
+
description: null
|
9
|
+
|
10
|
+
# prefix for role variables
|
11
|
+
var_prefix: null
|
12
|
+
|
13
|
+
# how to get a default for `dir` if it's not provided as the only
|
14
|
+
# positional argument. if a positional argument is provided it will
|
15
|
+
# override the method defined here.
|
16
|
+
#
|
17
|
+
# options:
|
18
|
+
#
|
19
|
+
# - null
|
20
|
+
# - require the value on the command line.
|
21
|
+
# - git_root
|
22
|
+
# - use the git root fof the directory that the `qb` command is invoked
|
23
|
+
# from. useful for 'project-centric' commands so they can be invoked
|
24
|
+
# from anywhere in the repo.
|
25
|
+
# - cwd
|
26
|
+
# - use the directory the `qb` command is invoked form.
|
27
|
+
# - {exe: PATH}
|
28
|
+
# - invoke an execuable, passing a JSON serialization of the options
|
29
|
+
# mapping their CLI names to values. path can be relative to role
|
30
|
+
# directory.
|
31
|
+
default_dir: cwd
|
32
|
+
|
33
|
+
# default user to become for play
|
34
|
+
default_user: null
|
35
|
+
|
36
|
+
# set to false to not save options in .qb-options.yml files
|
37
|
+
save_options: false
|
38
|
+
|
39
|
+
options:
|
40
|
+
# - name: example
|
41
|
+
# description: an example of a variable.
|
42
|
+
# required: false
|
43
|
+
# type: boolean # boolean (default) | string
|
44
|
+
# short: e
|
45
|
+
- name: raise
|
46
|
+
description: raise an error in main.
|
47
|
+
required: false
|
48
|
+
type: boolean
|
49
|
+
short: r
|
50
|
+
|
51
|
+
- name: count
|
52
|
+
description: count to 10 on stdout with 1 sec sleeps between
|
53
|
+
required: false
|
54
|
+
type: boolean
|
55
|
+
short: c
|
data/exe/qb
CHANGED
@@ -42,7 +42,7 @@ end
|
|
42
42
|
|
43
43
|
def set_debug! args
|
44
44
|
if DEBUG_ARGS.any? {|arg| args.include? arg}
|
45
|
-
|
45
|
+
ENV['QB_DEBUG'] = 'true'
|
46
46
|
debug "ON"
|
47
47
|
DEBUG_ARGS.each {|arg| args.delete arg}
|
48
48
|
end
|
@@ -78,11 +78,15 @@ def with_clean_env &block
|
|
78
78
|
].include?(k)
|
79
79
|
}
|
80
80
|
|
81
|
+
qb_env = ENV.select {|k, v| k.start_with? 'QB_'}
|
82
|
+
|
81
83
|
Bundler.with_clean_env do
|
82
84
|
# now that we're in a clean env, copy the Bundler env vars into
|
83
85
|
# 'QB_DEV_ENV_<NAME>' vars.
|
84
86
|
dev_env.each {|k, v| ENV["QB_DEV_ENV_#{ k }"] = v}
|
85
87
|
|
88
|
+
qb_env.each {|k, v| ENV[k] = v}
|
89
|
+
|
86
90
|
# and set QB_DEV_ENV=true
|
87
91
|
ENV['QB_DEV_ENV'] = 'true'
|
88
92
|
|
@@ -352,8 +356,17 @@ def main args
|
|
352
356
|
|
353
357
|
puts "COMMAND: #{ cmd }"
|
354
358
|
|
359
|
+
# boot up stdio services so that ansible modules can stream to our
|
360
|
+
# stdout and stderr to print stuff (including debug lines) in real-time
|
361
|
+
stdio_services = {'out' => $stdout, 'err' => $stderr}.map do |name, dest|
|
362
|
+
QB::Util::STDIO::Service.new(name, dest).tap {|s| s.open! }
|
363
|
+
end
|
364
|
+
|
355
365
|
status = Cmds.stream cmd
|
356
366
|
|
367
|
+
# close the stdio services
|
368
|
+
stdio_services.each {|s| s.close! }
|
369
|
+
|
357
370
|
if status != 0
|
358
371
|
puts "ERROR ansible-playbook failed."
|
359
372
|
end
|
data/lib/qb.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
require 'nrser/extras'
|
2
2
|
|
3
3
|
require "qb/version"
|
4
|
+
require "qb/util"
|
5
|
+
require 'qb/util/stdio'
|
6
|
+
require "qb/ansible_module"
|
4
7
|
|
5
8
|
module QB
|
6
9
|
ROOT = (Pathname.new(__FILE__).dirname + '..').expand_path
|
@@ -10,16 +13,8 @@ module QB
|
|
10
13
|
class Error < StandardError
|
11
14
|
end
|
12
15
|
|
13
|
-
# TODO this should be in an instance that is run instead of module global
|
14
|
-
# hack for now
|
15
|
-
@@debug = false
|
16
|
-
|
17
|
-
def self.debug= bool
|
18
|
-
@@debug = !!bool
|
19
|
-
end
|
20
|
-
|
21
16
|
def self.debug *args
|
22
|
-
return unless
|
17
|
+
return unless ENV['QB_DEBUG'] && args.length > 0
|
23
18
|
|
24
19
|
header = 'DEBUG'
|
25
20
|
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module QB
|
4
|
+
class AnsibleModule
|
5
|
+
def self.stringify_keys hash
|
6
|
+
hash.map {|k, v| [k.to_s, v]}.to_h
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@changed = false
|
11
|
+
@input_file = ARGV[0]
|
12
|
+
@input = File.read @input_file
|
13
|
+
@args = JSON.load @input
|
14
|
+
@facts = {}
|
15
|
+
|
16
|
+
# if QB_STDIO_ env vars are set send stdout and stderr
|
17
|
+
# to those sockets to print in the parent process
|
18
|
+
|
19
|
+
if ENV['QB_STDIO_OUT']
|
20
|
+
$stdout = UNIXSocket.new ENV['QB_STDIO_OUT']
|
21
|
+
end
|
22
|
+
|
23
|
+
if ENV['QB_STDIO_ERR']
|
24
|
+
$stderr = UNIXSocket.new ENV['QB_STDIO_ERR']
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def run
|
29
|
+
result = main
|
30
|
+
|
31
|
+
case result
|
32
|
+
when nil
|
33
|
+
# pass
|
34
|
+
when Hash
|
35
|
+
@facts.merge! result
|
36
|
+
else
|
37
|
+
raise "result of #main should be nil or Hash, found #{ result.inspect }"
|
38
|
+
end
|
39
|
+
|
40
|
+
done
|
41
|
+
end
|
42
|
+
|
43
|
+
def changed! facts = {}
|
44
|
+
@changed = true
|
45
|
+
@facts.merge! facts
|
46
|
+
done
|
47
|
+
end
|
48
|
+
|
49
|
+
def done
|
50
|
+
exit_json changed: @changed,
|
51
|
+
ansible_facts: self.class.stringify_keys(@facts)
|
52
|
+
end
|
53
|
+
|
54
|
+
def exit_json hash
|
55
|
+
# print JSON response to process' actual STDOUT (instead of $stdout,
|
56
|
+
# which may be pointing to the qb parent process)
|
57
|
+
STDOUT.print JSON.dump(self.class.stringify_keys(hash))
|
58
|
+
exit 0
|
59
|
+
end
|
60
|
+
|
61
|
+
def fail msg
|
62
|
+
exit_json failed: true, msg: msg
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end # QB
|
data/lib/qb/options.rb
CHANGED
@@ -161,7 +161,7 @@ module QB
|
|
161
161
|
|
162
162
|
qb_options = {
|
163
163
|
'hosts' => ['localhost'],
|
164
|
-
'facts' =>
|
164
|
+
'facts' => true,
|
165
165
|
}
|
166
166
|
|
167
167
|
if role.meta['default_user']
|
@@ -212,11 +212,10 @@ module QB
|
|
212
212
|
end
|
213
213
|
|
214
214
|
opts.on(
|
215
|
-
'-
|
216
|
-
'
|
217
|
-
"gather facts (often un-needed)",
|
215
|
+
'--NO-FACTS',
|
216
|
+
"don't gather facts",
|
218
217
|
) do |value|
|
219
|
-
qb_options['facts'] =
|
218
|
+
qb_options['facts'] = false
|
220
219
|
end
|
221
220
|
|
222
221
|
add opts, role_options, role
|
data/lib/qb/role.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
require 'cmds'
|
3
|
+
require 'parseconfig'
|
3
4
|
|
4
5
|
module QB
|
5
6
|
class Role
|
@@ -46,17 +47,44 @@ module QB
|
|
46
47
|
['qb.yml', 'qb'].any? {|filename| pathname.join('meta', filename).file?}
|
47
48
|
end
|
48
49
|
|
49
|
-
#
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
50
|
+
# get role paths from ansible.cfg if it exists in a directory.
|
51
|
+
#
|
52
|
+
# @param dir [Pathname] directory to look for ansible.cfg in.
|
53
|
+
#
|
54
|
+
# @return [Array<String>] role paths
|
55
|
+
#
|
56
|
+
def self.cfg_roles_path dir
|
57
|
+
path = dir.join 'ansible.cfg'
|
58
|
+
|
59
|
+
if path.file?
|
60
|
+
config = ParseConfig.new path.to_s
|
61
|
+
config['defaults']['roles_path'].split(':').map {|path|
|
62
|
+
QB::Util.resolve dir, path
|
63
|
+
}
|
64
|
+
else
|
65
|
+
[]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# @param dir [Pathname] dir to include.
|
70
|
+
def self.roles_paths dir
|
71
|
+
cfg_roles_path(dir) + [
|
72
|
+
dir.join('roles'),
|
73
|
+
dir.join('roles', 'tmp')
|
57
74
|
]
|
58
75
|
end
|
59
76
|
|
77
|
+
# @return [Array<Pathname>] places to look for role dirs.
|
78
|
+
def self.search_path
|
79
|
+
[QB::ROLES_DIR] + [
|
80
|
+
QB::Util.resolve,
|
81
|
+
QB::Util.resolve('ansible'),
|
82
|
+
QB::Util.resolve('dev'),
|
83
|
+
].map {|dir|
|
84
|
+
roles_paths dir
|
85
|
+
}.flatten
|
86
|
+
end
|
87
|
+
|
60
88
|
# array of QB::Role found in search path.
|
61
89
|
def self.available
|
62
90
|
search_path.
|
@@ -69,6 +97,8 @@ module QB
|
|
69
97
|
search_dir.children.select {|child| role_dir? child }
|
70
98
|
}.
|
71
99
|
flatten.
|
100
|
+
# should allow uniq to remove dups
|
101
|
+
map {|role_dir| role_dir.realpath }.
|
72
102
|
# needed when qb is run from the qb repo since QB::ROLES_DIR and
|
73
103
|
# ./roles are the same dir
|
74
104
|
uniq.
|
@@ -79,8 +109,10 @@ module QB
|
|
79
109
|
|
80
110
|
# get an array of QB::Role that match an input string
|
81
111
|
def self.matches input
|
112
|
+
available = self.available
|
113
|
+
|
82
114
|
available.each {|role|
|
83
|
-
# exact match to
|
115
|
+
# exact match to relative path
|
84
116
|
return [role] if role.rel_path.to_s == input
|
85
117
|
}.each {|role|
|
86
118
|
# exact match to full name
|
@@ -88,12 +120,29 @@ module QB
|
|
88
120
|
}.each {|role|
|
89
121
|
# exact match without the namespace prefix ('qb.' or similar)
|
90
122
|
return [role] if role.namespaceless == input
|
91
|
-
}.select {|role|
|
92
|
-
# select any that have that string in them
|
93
|
-
role.rel_path.to_s.include? input
|
94
|
-
}.tap {|matches|
|
95
|
-
QB.debug "role matches" => matches
|
96
123
|
}
|
124
|
+
|
125
|
+
# see if we prefix match any full names
|
126
|
+
name_prefix_matches = available.select {|role|
|
127
|
+
role.name.start_with? input
|
128
|
+
}
|
129
|
+
return name_prefix_matches unless name_prefix_matches.empty?
|
130
|
+
|
131
|
+
# see if we prefix match any name
|
132
|
+
namespaceless_prefix_matches = available.select {|role|
|
133
|
+
role.namespaceless.start_with? input
|
134
|
+
}
|
135
|
+
unless namespaceless_prefix_matches.empty?
|
136
|
+
return namespaceless_prefix_matches
|
137
|
+
end
|
138
|
+
|
139
|
+
# see if we word match any names
|
140
|
+
name_word_matches = available.select {|role|
|
141
|
+
QB::Util.words_start_with? role.name, input
|
142
|
+
}
|
143
|
+
return name_word_matches unless name_word_matches.empty?
|
144
|
+
|
145
|
+
[]
|
97
146
|
end
|
98
147
|
|
99
148
|
# find exactly one matching role for the input string or raise.
|
data/lib/qb/util.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
module QB
|
2
|
+
module Util
|
3
|
+
# split a string into 'words' for word-based matching
|
4
|
+
def self.words string
|
5
|
+
string.split(/[\W_-]+/).reject {|w| w.empty?}
|
6
|
+
end # .words
|
7
|
+
|
8
|
+
# see if words from an input match words
|
9
|
+
def self.words_start_with? full_string, input
|
10
|
+
# QB.debug "does #{ input } match #{ full_string }?"
|
11
|
+
|
12
|
+
input_words = words input
|
13
|
+
full_string_words = words full_string
|
14
|
+
|
15
|
+
full_string_words.each_with_index {|word, start_index|
|
16
|
+
# compute the end index in full_string_words
|
17
|
+
end_index = start_index + input_words.length - 1
|
18
|
+
|
19
|
+
# short-circuit if can't match (more input words than full words left)
|
20
|
+
if end_index >= full_string_words.length
|
21
|
+
return false
|
22
|
+
end
|
23
|
+
|
24
|
+
# create the slice to test against
|
25
|
+
slice = full_string_words[start_index..end_index]
|
26
|
+
|
27
|
+
# see if every word in the slice starts with the corresponding word
|
28
|
+
# in the input
|
29
|
+
if slice.zip(input_words).all? {|full_word, input_word|
|
30
|
+
full_word.start_with? input_word
|
31
|
+
}
|
32
|
+
# got a match!
|
33
|
+
return true
|
34
|
+
end
|
35
|
+
}
|
36
|
+
|
37
|
+
# no match
|
38
|
+
false
|
39
|
+
end # .match_words?
|
40
|
+
|
41
|
+
# @return [Pathname] absolute resolved path.
|
42
|
+
def self.resolve *segments
|
43
|
+
joined = Pathname.new ''
|
44
|
+
|
45
|
+
([Dir.pwd] + segments).reverse.each_with_index {|segment, index|
|
46
|
+
joined = Pathname.new(segment).join joined
|
47
|
+
return joined if joined.absolute?
|
48
|
+
}
|
49
|
+
|
50
|
+
# shouldn't ever happen
|
51
|
+
raise "resolution failed: #{ segments.inspect }"
|
52
|
+
end
|
53
|
+
end # Util
|
54
|
+
end # QB
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'socket'
|
3
|
+
require 'securerandom'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'nrser'
|
6
|
+
|
7
|
+
using NRSER
|
8
|
+
|
9
|
+
module QB; end
|
10
|
+
module QB::Util; end
|
11
|
+
|
12
|
+
module QB::Util::STDIO
|
13
|
+
SOCKET_DIR = Pathname.new('/').join 'tmp', 'qb-stdio'
|
14
|
+
|
15
|
+
# STDIO as a service exposed on a UNIX socket so that modules can stream
|
16
|
+
# their output to it, which is in turn printed to the console `qb` is running
|
17
|
+
# in.
|
18
|
+
class Service
|
19
|
+
def initialize name, dest
|
20
|
+
@name = name
|
21
|
+
@dest = dest
|
22
|
+
@thread = nil
|
23
|
+
@server = nil
|
24
|
+
@socket = nil
|
25
|
+
@env_key = "QB_STDIO_#{ name.upcase }"
|
26
|
+
|
27
|
+
unless SOCKET_DIR.exist?
|
28
|
+
FileUtils.mkdir SOCKET_DIR
|
29
|
+
end
|
30
|
+
|
31
|
+
@path = SOCKET_DIR.join "#{ name }.#{ SecureRandom.uuid }.sock"
|
32
|
+
|
33
|
+
@debug_header = "#{ name }@#{ @path.to_s }"
|
34
|
+
end
|
35
|
+
|
36
|
+
def debug *args
|
37
|
+
QB.debug "#{ @debug_header }", *args
|
38
|
+
end
|
39
|
+
|
40
|
+
def open!
|
41
|
+
debug "opening..."
|
42
|
+
|
43
|
+
# make sure env var is not already set (basically just prevents you from
|
44
|
+
# accidentally opening two instances with the same name)
|
45
|
+
if ENV.key? @env_key
|
46
|
+
raise <<-END.squish
|
47
|
+
env already contains key #{ @env_key } with value #{ ENV[@env_key] }
|
48
|
+
END
|
49
|
+
end
|
50
|
+
|
51
|
+
@thread = Thread.new do
|
52
|
+
debug "thread started."
|
53
|
+
|
54
|
+
@server = UNIXServer.new @path.to_s
|
55
|
+
@socket = @server.accept
|
56
|
+
|
57
|
+
while (line = @socket.gets) do
|
58
|
+
@dest.puts line
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# set the env key so children can find the socket path
|
63
|
+
ENV[@env_key] = @path.to_s
|
64
|
+
debug "set env var #{ @env_key }=#{ ENV[@env_key] }"
|
65
|
+
|
66
|
+
debug "service open."
|
67
|
+
end # open
|
68
|
+
|
69
|
+
def close!
|
70
|
+
# clean up.
|
71
|
+
#
|
72
|
+
# TODO not sure how correct this is...
|
73
|
+
#
|
74
|
+
debug "closing..."
|
75
|
+
|
76
|
+
@thread.kill
|
77
|
+
@socket.close
|
78
|
+
@socket = nil
|
79
|
+
@server.close
|
80
|
+
@server = nil
|
81
|
+
FileUtils.rm @path
|
82
|
+
|
83
|
+
debug "closed."
|
84
|
+
end
|
85
|
+
end # Service
|
86
|
+
end # QB::Util::STDIO
|
data/lib/qb/version.rb
CHANGED
data/qb.gemspec
CHANGED
@@ -44,6 +44,7 @@ Gem::Specification.new do |spec|
|
|
44
44
|
spec.add_dependency "cmds",'~> 0.0', ">= 0.0.9"
|
45
45
|
spec.add_dependency "nrser-extras", '~> 0.0', ">= 0.0.3"
|
46
46
|
spec.add_dependency "state_mate", '~> 0.0', ">= 0.0.9"
|
47
|
+
spec.add_dependency 'parseconfig', '~> 1.0', '>= 1.0.8'
|
47
48
|
|
48
49
|
|
49
50
|
if QB::VERSION.end_with? '.dev'
|
@@ -0,0 +1,167 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#!/usr/bin/env ruby
|
4
|
+
# WANT_JSON
|
5
|
+
|
6
|
+
# init bundler in dev env
|
7
|
+
if ENV['QB_DEV_ENV']
|
8
|
+
ENV.each {|k, v|
|
9
|
+
if k.start_with? 'QB_DEV_ENV_'
|
10
|
+
ENV[k.sub('QB_DEV_ENV_', '')] = v
|
11
|
+
end
|
12
|
+
}
|
13
|
+
require 'bundler/setup'
|
14
|
+
end
|
15
|
+
|
16
|
+
require 'qb'
|
17
|
+
require 'cmds'
|
18
|
+
require 'nrser'
|
19
|
+
|
20
|
+
class GitSubmoduleUpdate < QB::AnsibleModule
|
21
|
+
def main_dir
|
22
|
+
File.realpath @args['dir']
|
23
|
+
end
|
24
|
+
|
25
|
+
def resolve *path
|
26
|
+
QB::Util.resolve main_dir, *path
|
27
|
+
end
|
28
|
+
|
29
|
+
def submodules
|
30
|
+
out = Dir.chdir main_dir do
|
31
|
+
Cmds.out! "git submodule"
|
32
|
+
end
|
33
|
+
|
34
|
+
out.lines.map {|line|
|
35
|
+
match = line.match /([0-9a-f]{40})\s(\S+)\s/
|
36
|
+
commit = match[1]
|
37
|
+
rel_dir = match[2]
|
38
|
+
dir = resolve rel_dir
|
39
|
+
|
40
|
+
{
|
41
|
+
commit: commit,
|
42
|
+
dir: dir,
|
43
|
+
rel_dir: rel_dir,
|
44
|
+
detached: detached?(dir),
|
45
|
+
dirty: dirty?(dir),
|
46
|
+
}
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def dirty? repo_dir
|
51
|
+
Dir.chdir repo_dir do
|
52
|
+
!Cmds.out!("git status --porcelain").empty?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def detached? repo_dir
|
57
|
+
Dir.chdir repo_dir do
|
58
|
+
out = Cmds.out! "git branch"
|
59
|
+
|
60
|
+
!!(out.lines[0].match /\*\ \(HEAD\ detached\ at [0-9a-f]{7}\)/)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def branch_heads repo_dir
|
65
|
+
Dir.chdir repo_dir do
|
66
|
+
Cmds.out!("git show-ref").lines.map {|line|
|
67
|
+
m = line.match(/([0-9a-f]{40})\s+(\S+)\s/)
|
68
|
+
commit = m[1]
|
69
|
+
ref = m[2]
|
70
|
+
|
71
|
+
{
|
72
|
+
commit: commit,
|
73
|
+
ref: ref,
|
74
|
+
}
|
75
|
+
}
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def branch_heads_for_commit submodule
|
80
|
+
branch_heads(submodule[:dir]).select {|branch_head|
|
81
|
+
branch_head[:commit] == submodule[:commit]
|
82
|
+
}.reject {|branch_head| branch_head[:ref].end_with? 'HEAD'}
|
83
|
+
end
|
84
|
+
|
85
|
+
def attach! submodule
|
86
|
+
branch_heads = branch_heads_for_commit submodule
|
87
|
+
branch_head = nil
|
88
|
+
|
89
|
+
case branch_heads.length
|
90
|
+
when 0
|
91
|
+
# commit does not point to any branch heads - which means it's
|
92
|
+
# probably a commit in a branch that's behind the head
|
93
|
+
#
|
94
|
+
# we could figure out which branch it's in but we don't want to
|
95
|
+
# automatically update it because that might break shit.
|
96
|
+
#
|
97
|
+
# do nothing
|
98
|
+
return false
|
99
|
+
when 1
|
100
|
+
# commit is head of only one branch
|
101
|
+
branch_head = branch_heads[0]
|
102
|
+
else
|
103
|
+
# commit is head of multiple branches
|
104
|
+
local = branch_heads.select {|bh| bh[:ref].start_with? 'refs/heads'}
|
105
|
+
|
106
|
+
case local.length
|
107
|
+
when 0
|
108
|
+
# commit is head of multiple remote branches
|
109
|
+
# see if one is master
|
110
|
+
branch_head = branch_heads.find {|bh| bh[:ref].end_with? 'master'}
|
111
|
+
|
112
|
+
# if none do we're hosed - not sure which one it should be on
|
113
|
+
if branch_head.nil?
|
114
|
+
raise NRSER.squish <<-END
|
115
|
+
submodule #{ submodule[:rel_dir] } points to commit
|
116
|
+
#{ submodule[:commit] } that heads multiple non-master remote
|
117
|
+
branches: #{ branch_heads.map {|bh| bh[:ref]} }
|
118
|
+
END
|
119
|
+
end
|
120
|
+
|
121
|
+
when 1
|
122
|
+
# the commit is head of one local branch, use it
|
123
|
+
branch_head = local[0]
|
124
|
+
|
125
|
+
else
|
126
|
+
# the commit heads multiple local branches
|
127
|
+
# again, see if one is master
|
128
|
+
branch_head = local.find {|b| b[:ref].end_with? 'master'}
|
129
|
+
|
130
|
+
# if none do we're hosed - not sure which one it should be on
|
131
|
+
if branch_head.nil?
|
132
|
+
raise NRSER.squish <<-END
|
133
|
+
submodule #{ submodule[:rel_dir] } points to commit
|
134
|
+
#{ submodule[:commit] } that heads multiple non-master local
|
135
|
+
branches: #{ local.map {|bh| bh[:ref]} }
|
136
|
+
END
|
137
|
+
end
|
138
|
+
end # case
|
139
|
+
end
|
140
|
+
|
141
|
+
branch = branch_head[:ref].split('/')[-1]
|
142
|
+
|
143
|
+
Dir.chdir submodule[:dir] do
|
144
|
+
# checkout the branch
|
145
|
+
Cmds! "git checkout <%= branch %>", branch: branch
|
146
|
+
|
147
|
+
# do a pull if the head was on the remote
|
148
|
+
if branch_head[:ref].start_with? 'refs/remotes'
|
149
|
+
Cmds! "git pull origin <%= branch %>", branch: branch
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
@changed = true
|
154
|
+
end
|
155
|
+
|
156
|
+
def main
|
157
|
+
submodules.select {|sub|
|
158
|
+
sub[:detached]
|
159
|
+
}.each {|sub|
|
160
|
+
attach! sub
|
161
|
+
}
|
162
|
+
|
163
|
+
nil
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
GitSubmoduleUpdate.new.run
|
@@ -0,0 +1,44 @@
|
|
1
|
+
---
|
2
|
+
# meta/qb.yml file for qb.git_submodule_update
|
3
|
+
#
|
4
|
+
# qb settings for this role. see README.md for more info.
|
5
|
+
#
|
6
|
+
|
7
|
+
# description of the role to show in it's help output.
|
8
|
+
description: null
|
9
|
+
|
10
|
+
# prefix for role variables
|
11
|
+
var_prefix: null
|
12
|
+
|
13
|
+
# how to get a default for `dir` if it's not provided as the only
|
14
|
+
# positional argument. if a positional argument is provided it will
|
15
|
+
# override the method defined here.
|
16
|
+
#
|
17
|
+
# options:
|
18
|
+
#
|
19
|
+
# - null
|
20
|
+
# - require the value on the command line.
|
21
|
+
# - git_root
|
22
|
+
# - use the git root fof the directory that the `qb` command is invoked
|
23
|
+
# from. useful for 'project-centric' commands so they can be invoked
|
24
|
+
# from anywhere in the repo.
|
25
|
+
# - cwd
|
26
|
+
# - use the directory the `qb` command is invoked form.
|
27
|
+
# - {exe: PATH}
|
28
|
+
# - invoke an execuable, passing a JSON serialization of the options
|
29
|
+
# mapping their CLI names to values. path can be relative to role
|
30
|
+
# directory.
|
31
|
+
default_dir: git_root
|
32
|
+
|
33
|
+
# default user to become for play
|
34
|
+
default_user: null
|
35
|
+
|
36
|
+
# set to false to not save options in .qb-options.yml files
|
37
|
+
save_options: false
|
38
|
+
|
39
|
+
options: []
|
40
|
+
# - name: example
|
41
|
+
# description: an example of a variable.
|
42
|
+
# required: false
|
43
|
+
# type: boolean # boolean (default) | string
|
44
|
+
# short: e
|
@@ -0,0 +1,11 @@
|
|
1
|
+
---
|
2
|
+
# tasks file for qb.git_submodule_update
|
3
|
+
|
4
|
+
- name: update submodules
|
5
|
+
command: git submodule update --init
|
6
|
+
args:
|
7
|
+
chdir: "{{ git_submodule_update_dir }}"
|
8
|
+
|
9
|
+
- name: checkout branch for any commits that are exactly one head
|
10
|
+
git_submodule_update:
|
11
|
+
dir: "{{ git_submodule_update_dir }}"
|
data/roles/qb.role/meta/qb.yml
CHANGED
@@ -115,3 +115,14 @@
|
|
115
115
|
dest: "{{ dir }}/vars/main.yml"
|
116
116
|
force: "{{ role_force }}"
|
117
117
|
when: role_vars
|
118
|
+
|
119
|
+
# readme
|
120
|
+
# ======
|
121
|
+
|
122
|
+
- name: create README.md
|
123
|
+
template:
|
124
|
+
src: README.md.j2
|
125
|
+
dest: "{{ dir }}/README.md"
|
126
|
+
force: "{{ role_force }}"
|
127
|
+
when: role_readme
|
128
|
+
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: qb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.37
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- nrser
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-12-
|
11
|
+
date: 2016-12-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -112,6 +112,26 @@ dependencies:
|
|
112
112
|
- - ">="
|
113
113
|
- !ruby/object:Gem::Version
|
114
114
|
version: 0.0.9
|
115
|
+
- !ruby/object:Gem::Dependency
|
116
|
+
name: parseconfig
|
117
|
+
requirement: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - "~>"
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '1.0'
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 1.0.8
|
125
|
+
type: :runtime
|
126
|
+
prerelease: false
|
127
|
+
version_requirements: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '1.0'
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: 1.0.8
|
115
135
|
description:
|
116
136
|
email:
|
117
137
|
- neil@ztkae.com
|
@@ -133,23 +153,37 @@ files:
|
|
133
153
|
- bin/console
|
134
154
|
- bin/print-error
|
135
155
|
- bin/qb
|
156
|
+
- bin/rake
|
136
157
|
- bin/setup
|
137
158
|
- bin/ungem
|
138
159
|
- dev/ansible.cfg
|
139
160
|
- dev/hosts
|
140
161
|
- dev/requirements.yml
|
162
|
+
- dev/scratch/ansible_module/defaults/main.yml
|
163
|
+
- dev/scratch/ansible_module/library/test
|
164
|
+
- dev/scratch/ansible_module/meta/main.yml
|
165
|
+
- dev/scratch/ansible_module/meta/qb.yml
|
166
|
+
- dev/scratch/ansible_module/tasks/main.yml
|
141
167
|
- dev/scratch/case.rb
|
142
168
|
- dev/scratch/empty/defaults/main.yml
|
143
169
|
- dev/scratch/empty/meta/main.yml
|
144
170
|
- dev/scratch/empty/meta/qb.yml
|
145
171
|
- dev/scratch/empty/tasks/main.yml
|
146
172
|
- dev/scratch/stateSpec.js
|
173
|
+
- dev/scratch/stdio/defaults/main.yml
|
174
|
+
- dev/scratch/stdio/library/test
|
175
|
+
- dev/scratch/stdio/meta/main.yml
|
176
|
+
- dev/scratch/stdio/meta/qb.yml
|
177
|
+
- dev/scratch/stdio/tasks/main.yml
|
147
178
|
- dev/setup.yml
|
148
179
|
- exe/qb
|
149
180
|
- lib/qb.rb
|
181
|
+
- lib/qb/ansible_module.rb
|
150
182
|
- lib/qb/options.rb
|
151
183
|
- lib/qb/options/option.rb
|
152
184
|
- lib/qb/role.rb
|
185
|
+
- lib/qb/util.rb
|
186
|
+
- lib/qb/util/stdio.rb
|
153
187
|
- lib/qb/version.rb
|
154
188
|
- library/git_mkdir.py
|
155
189
|
- library/qb_facts.py
|
@@ -283,6 +317,13 @@ files:
|
|
283
317
|
- roles/qb.git_repo/meta/main.yml
|
284
318
|
- roles/qb.git_repo/meta/qb.yml
|
285
319
|
- roles/qb.git_repo/tasks/main.yml
|
320
|
+
- roles/qb.git_submodule_update/.qb-options.yml
|
321
|
+
- roles/qb.git_submodule_update/README.md
|
322
|
+
- roles/qb.git_submodule_update/defaults/main.yml
|
323
|
+
- roles/qb.git_submodule_update/library/git_submodule_update
|
324
|
+
- roles/qb.git_submodule_update/meta/main.yml
|
325
|
+
- roles/qb.git_submodule_update/meta/qb.yml
|
326
|
+
- roles/qb.git_submodule_update/tasks/main.yml
|
286
327
|
- roles/qb.gitignore/defaults/main.yml
|
287
328
|
- roles/qb.gitignore/files/gitignore/.github/PULL_REQUEST_TEMPLATE.md
|
288
329
|
- roles/qb.gitignore/files/gitignore/Actionscript.gitignore
|
@@ -523,6 +564,7 @@ files:
|
|
523
564
|
- roles/qb.role/meta/qb.yml
|
524
565
|
- roles/qb.role/tasks/main.yml
|
525
566
|
- roles/qb.role/templates/.gitkeep
|
567
|
+
- roles/qb.role/templates/README.md.j2
|
526
568
|
- roles/qb.role/templates/defaults/main.yml.j2
|
527
569
|
- roles/qb.role/templates/handlers/main.yml.j2
|
528
570
|
- roles/qb.role/templates/meta/main.yml.j2
|