a4tools 1.2.7
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.
- checksums.yaml +7 -0
- data/.bundle/install.log +38 -0
- data/.gitignore +2 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +38 -0
- data/a4tools.gemspec +38 -0
- data/bin/deploy_latest_clients +32 -0
- data/bin/devsite_config_server +48 -0
- data/bin/netshell +23 -0
- data/bin/update_server +101 -0
- data/bin/usher +54 -0
- data/lib/a4tools.rb +61 -0
- data/lib/a4tools/version.rb +3 -0
- data/lib/acres_client.rb +376 -0
- data/lib/clients/caching_client.rb +151 -0
- data/lib/clients/deployment_client.rb +53 -0
- data/lib/clients/kai_config_client.rb +39 -0
- data/lib/clients/usher_client.rb +72 -0
- data/lib/clients/usher_mgmt_client.rb +201 -0
- data/lib/event_manager.rb +24 -0
- data/lib/events.json +1 -0
- data/lib/net_shell/builtin_command.rb +312 -0
- data/lib/net_shell/builtin_commands/build.rb +251 -0
- data/lib/net_shell/builtin_commands/cd.rb +12 -0
- data/lib/net_shell/builtin_commands/connect.rb +122 -0
- data/lib/net_shell/builtin_commands/deploy.rb +280 -0
- data/lib/net_shell/builtin_commands/disconnect.rb +15 -0
- data/lib/net_shell/builtin_commands/excerpt.rb +97 -0
- data/lib/net_shell/builtin_commands/exit.rb +7 -0
- data/lib/net_shell/builtin_commands/get.rb +38 -0
- data/lib/net_shell/builtin_commands/help.rb +40 -0
- data/lib/net_shell/builtin_commands/host.rb +126 -0
- data/lib/net_shell/builtin_commands/inject.rb +42 -0
- data/lib/net_shell/builtin_commands/jsoncache.rb +80 -0
- data/lib/net_shell/builtin_commands/kai_event.rb +151 -0
- data/lib/net_shell/builtin_commands/persist.rb +24 -0
- data/lib/net_shell/builtin_commands/pwd.rb +6 -0
- data/lib/net_shell/builtin_commands/recap.rb +188 -0
- data/lib/net_shell/builtin_commands/references.rb +63 -0
- data/lib/net_shell/builtin_commands/select.rb +36 -0
- data/lib/net_shell/builtin_commands/send.rb +74 -0
- data/lib/net_shell/builtin_commands/set.rb +29 -0
- data/lib/net_shell/builtin_commands/show.rb +183 -0
- data/lib/net_shell/builtin_commands/site.rb +122 -0
- data/lib/net_shell/builtin_commands/ssh.rb +62 -0
- data/lib/net_shell/builtin_commands/talk.rb +90 -0
- data/lib/net_shell/builtin_commands/translate.rb +45 -0
- data/lib/net_shell/builtin_commands/unset.rb +14 -0
- data/lib/net_shell/builtin_commands/usher.rb +55 -0
- data/lib/net_shell/builtin_commands/usher_device.rb +39 -0
- data/lib/net_shell/builtin_commands/usher_site.rb +245 -0
- data/lib/net_shell/builtin_commands/usherm_connect.rb +21 -0
- data/lib/net_shell/colors.rb +149 -0
- data/lib/net_shell/command.rb +97 -0
- data/lib/net_shell/io.rb +132 -0
- data/lib/net_shell/net_shell.rb +396 -0
- data/lib/net_shell/prompt.rb +335 -0
- data/lib/object_builder/definitions/app_info_for_script.rb +83 -0
- data/lib/object_builder/definitions/connection_request.rb +28 -0
- data/lib/object_builder/definitions/device_info_for_system.rb +37 -0
- data/lib/object_builder/object_builder.rb +145 -0
- data/lib/talk.json +1 -0
- data/lib/talk_consumer.rb +235 -0
- metadata +279 -0
@@ -0,0 +1,251 @@
|
|
1
|
+
category "Protocol manipulation"
|
2
|
+
description "Interactively build a JSON object of a given class"
|
3
|
+
usage "class_name"
|
4
|
+
help <<-EOS
|
5
|
+
Launches an interactive prompt to build the indicated Talk class. At the conclusion of this prompt, a JSON string will be printed to stdout.
|
6
|
+
|
7
|
+
This command is somewhat magical in that the interactive prompts do not appear on the command's standard output, nor does it gather prompt input from standard input; only the JSON appears on standard output.
|
8
|
+
|
9
|
+
Therefore, you can pipe the output of #{command.style(:command)} directly to #{"send".style(:command)} or some other command.
|
10
|
+
EOS
|
11
|
+
|
12
|
+
|
13
|
+
validate { args.length == 2 }
|
14
|
+
tab 0 do
|
15
|
+
talk.things_named_like(args[1], :class).map { |thing| talk.truncated_name thing[:name] }
|
16
|
+
end
|
17
|
+
|
18
|
+
run do
|
19
|
+
cls = talk.class_named(args[1])
|
20
|
+
return show_error("#{args[1]}: no such class defined") if cls.nil?
|
21
|
+
|
22
|
+
obj = build(cls)
|
23
|
+
obj[:__class] = talk.class_named(args[1])[:name] rescue args[1]
|
24
|
+
JSON.generate(obj)
|
25
|
+
end
|
26
|
+
|
27
|
+
###
|
28
|
+
|
29
|
+
def prompt_for_path(path)
|
30
|
+
path.join(".").style(:class) + ") "
|
31
|
+
end
|
32
|
+
|
33
|
+
def prompt_for_value(path, type)
|
34
|
+
"#{path.join(".").style(:class)} #{type.style(:field_datatype)}) "
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_action(allowed)
|
38
|
+
char = STDIN.gets.chomp rescue nil
|
39
|
+
char = nil unless char.nil? or allowed.include? char
|
40
|
+
char
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_value_real(value)
|
44
|
+
case value
|
45
|
+
when nil, "null", ""
|
46
|
+
nil
|
47
|
+
else
|
48
|
+
value.to_f
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def get_value_integer(value)
|
53
|
+
case value
|
54
|
+
when nil, "null", ""
|
55
|
+
nil
|
56
|
+
else
|
57
|
+
value.to_i
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def get_value_boolean(value)
|
62
|
+
case value
|
63
|
+
when "0", "false"
|
64
|
+
false
|
65
|
+
when "1", "true"
|
66
|
+
true
|
67
|
+
when "", "null"
|
68
|
+
nil
|
69
|
+
else
|
70
|
+
true
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def get_value_string(value)
|
75
|
+
case value
|
76
|
+
when nil, "null"
|
77
|
+
nil
|
78
|
+
else
|
79
|
+
value
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def get_value(field, path)
|
84
|
+
type = field[:type].first
|
85
|
+
console_print prompt_for_value(path, type)
|
86
|
+
|
87
|
+
value = STDIN.gets
|
88
|
+
if value.nil? then
|
89
|
+
value = "null" # nil => CTRL+D. Take advantage that all get_value_* interpret null as nil-value to make CTRL+D -> null shortcut.
|
90
|
+
console_puts # need a newline since none was actually typed
|
91
|
+
end
|
92
|
+
|
93
|
+
value = value.chomp
|
94
|
+
if(value == "?") then
|
95
|
+
show_field_help(field)
|
96
|
+
return get_value(field, path)
|
97
|
+
end
|
98
|
+
|
99
|
+
return get_value_integer(value) if([ "uint8", "uint16", "uint32", "uint64", "int8", "int16", "int32", "int64" ].include?(type))
|
100
|
+
return get_value_real(value) if([ "real" ].include?(type))
|
101
|
+
return get_value_boolean(value) if([ "bool" ].include?(type))
|
102
|
+
return get_value_string(value) if([ "string" ].include?(type))
|
103
|
+
end
|
104
|
+
|
105
|
+
def in_array?(field, depth)
|
106
|
+
field[:type][-1 - depth] == "[]"
|
107
|
+
end
|
108
|
+
|
109
|
+
def in_hash?(field, depth)
|
110
|
+
field[:type][-1 - depth] == "{}"
|
111
|
+
end
|
112
|
+
|
113
|
+
def in_container?(field, depth)
|
114
|
+
in_array?(field, depth) or in_hash?(field, depth)
|
115
|
+
end
|
116
|
+
|
117
|
+
def prompt_for_action(field, path, type, actions)
|
118
|
+
while true do
|
119
|
+
console_puts "#{path.join('.').style(:class)}: #{type.style(:field_datatype)}. #{actions.map { |a| "[#{a[0].bold}]#{a[1..-1]}" }.join(', ')}"
|
120
|
+
console_print prompt_for_path(path)
|
121
|
+
action = get_action(actions.map { |a| a[0] })
|
122
|
+
return (actions.select { |a| a[0] == action }).first unless action.nil?
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def type_at_depth(field, depth)
|
127
|
+
field[:type][0 .. -1 - depth].join("")
|
128
|
+
end
|
129
|
+
|
130
|
+
def show_field_help(field, depth=-1)
|
131
|
+
depth = field[:type].length - 1 if depth < 0
|
132
|
+
console_puts "#{talk.truncated_name(type_at_depth(field, depth)).style(:field_datatype)} #{field[:name].style(:field_name)}\n\t#{field[:description]}"
|
133
|
+
end
|
134
|
+
|
135
|
+
def handle_array_start(field, path, depth)
|
136
|
+
actions = [ "build", "null", "?" ]
|
137
|
+
|
138
|
+
case prompt_for_action(field, path, type_at_depth(field, depth), actions)
|
139
|
+
when "?"
|
140
|
+
show_field_help(field, depth)
|
141
|
+
return handle_array_start(field, path, depth)
|
142
|
+
when nil, "null"
|
143
|
+
return nil
|
144
|
+
when "build"
|
145
|
+
collection = []
|
146
|
+
building = true
|
147
|
+
index = 0
|
148
|
+
while true do
|
149
|
+
element = handle_array_element(field, path[0..-2] + ["#{path.last}[#{index}]"], depth+1)
|
150
|
+
return collection if element.nil?
|
151
|
+
collection.push element[:value]
|
152
|
+
index += 1
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def handle_array_element(field, path, depth)
|
158
|
+
actions = [ "build", "null", "done", "?" ]
|
159
|
+
case prompt_for_action(field, path, type_at_depth(field, depth), actions)
|
160
|
+
when "?"
|
161
|
+
show_field_help(field, depth)
|
162
|
+
return handle_array_element(field, path, depth)
|
163
|
+
when nil, "null"
|
164
|
+
return { value:nil }
|
165
|
+
when "done"
|
166
|
+
return nil
|
167
|
+
when "build"
|
168
|
+
return { value:handle_field(field, path, depth+1, true) }
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def populate(field, path)
|
173
|
+
while true do
|
174
|
+
idx = 0
|
175
|
+
builders = ObjectBuilder.builders_for_name(field[:type].first)
|
176
|
+
builders.each do |builder|
|
177
|
+
console_puts("#{idx.to_s.style(:constant_value)} #{builder.identifier.to_s.style(:method_name)}: #{builder.description}")
|
178
|
+
idx += 1
|
179
|
+
end
|
180
|
+
|
181
|
+
console_print prompt_for_path(path)
|
182
|
+
action = STDIN.gets.chomp rescue nil
|
183
|
+
|
184
|
+
return handle_complex(field, path) if action.nil? # CTRL+D cancels populate
|
185
|
+
index = action.match(/^[0-9]+$/) ? action.to_i : nil
|
186
|
+
return builders[index].value if not index.nil? and index < builders.length
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def has_cache?(field)
|
191
|
+
ObjectBuilder.supported_classes(false).each do |name|
|
192
|
+
return true if talk.name_matches?(name, field[:type].first)
|
193
|
+
end
|
194
|
+
false
|
195
|
+
end
|
196
|
+
|
197
|
+
def handle_complex(field, path, assume_build=false)
|
198
|
+
return build(talk.class_named(field[:type].first), path) if assume_build
|
199
|
+
|
200
|
+
actions = if has_cache?(field) then
|
201
|
+
[ "build", "populate", "null", "?" ]
|
202
|
+
else
|
203
|
+
[ "build", "null", "?" ]
|
204
|
+
end
|
205
|
+
|
206
|
+
case prompt_for_action(field, path, field[:type].first, actions)
|
207
|
+
when "build"
|
208
|
+
build(talk.class_named(field[:type].first), path)
|
209
|
+
when nil, "null"
|
210
|
+
nil
|
211
|
+
when "populate"
|
212
|
+
populate(field, path)
|
213
|
+
when "?"
|
214
|
+
show_field_help(field, field[:type].length-1)
|
215
|
+
return handle_complex(field, path)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def handle_primitive(field, path)
|
220
|
+
get_value(field, path)
|
221
|
+
end
|
222
|
+
|
223
|
+
def handle_field(field, path, depth=0, assume_build=false)
|
224
|
+
path += [field[:name]] if depth == 0
|
225
|
+
if depth+1 < field[:type].length then
|
226
|
+
if in_array?(field, depth)
|
227
|
+
return handle_array_start(field, path, depth)
|
228
|
+
else
|
229
|
+
# TODO: handle dictionary
|
230
|
+
return nil
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
if talk.primitive? field[:type].first
|
235
|
+
return handle_primitive(field, path)
|
236
|
+
else
|
237
|
+
return handle_complex(field, path, assume_build)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def build(cls, path=[])
|
242
|
+
if path.empty? then
|
243
|
+
console_puts "Building #{talk.truncated_name cls}..."
|
244
|
+
else
|
245
|
+
console_puts "#{talk.truncated_name cls} #{path.last}\n\t#{cls[:description]}"
|
246
|
+
end
|
247
|
+
|
248
|
+
result = {}
|
249
|
+
cls[:field].each { |field| result[field[:name]] = handle_field(field, path) }
|
250
|
+
result
|
251
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
category "Shell management"
|
2
|
+
description "Change working directory"
|
3
|
+
usage "directory"
|
4
|
+
validate { args.length <= 2 }
|
5
|
+
help "Behaves much like cd in your favorite shell."
|
6
|
+
|
7
|
+
run do
|
8
|
+
p = File.expand_path(args.length == 2 ? args[1] : "~")
|
9
|
+
return show_error("Directory does not exist") unless File.directory?(p)
|
10
|
+
Dir.chdir(p)
|
11
|
+
""
|
12
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
category "Protocol manipulation"
|
2
|
+
description "Connect to a remote server"
|
3
|
+
usage "url [client_name]"
|
4
|
+
|
5
|
+
help <<-EOS
|
6
|
+
Connect to a remote server, indicated by the URL.
|
7
|
+
Supported schemes: http, https, ws, wss.
|
8
|
+
|
9
|
+
Once connected, commands like #{"send".style(:command)} will use the connected server as a target. Connecting also resets the #{"recap".style(:command)} log.
|
10
|
+
|
11
|
+
Comet support is not included; therefore, http and https provide synchronous communication only. ws and wss support fully asynchronous communication.
|
12
|
+
|
13
|
+
#{command.style(:command)} does make special effort to validate that a URL is accessible. For http and https clients, this means that attempts to use this connection will fail. Due to the nature of websockets, #{command.style(:command)} will display an error on exit if these services are unavailable.
|
14
|
+
EOS
|
15
|
+
|
16
|
+
validate { args.length >= 2 }
|
17
|
+
|
18
|
+
opt :kai, "Connect the Kai Mercury, Central and QATest clients to a particular site ID or host"
|
19
|
+
|
20
|
+
tab 0 do
|
21
|
+
if params[:kai] then
|
22
|
+
(usherm[:usherSystemId][:usherSites].map { |site| site[:idSite] } +
|
23
|
+
usherm[:usherSystemId][:usherSites].map { |site| site[:name] }) rescue []
|
24
|
+
else
|
25
|
+
[]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
run do
|
30
|
+
if params[:kai] then
|
31
|
+
connect_kai
|
32
|
+
else
|
33
|
+
connect_raw
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
###
|
38
|
+
|
39
|
+
def connect_raw
|
40
|
+
key = args[2].to_sym rescue :user
|
41
|
+
clients = []
|
42
|
+
client = AcresClient.new(args[1])
|
43
|
+
return show_error("Unable to contact server at #{args[1]}") unless client.transport_connect
|
44
|
+
|
45
|
+
@shell.set_client(key, client)
|
46
|
+
client.on(:connect) { |trigger, msg| @shell.notify_connect(client) }
|
47
|
+
client.on(:sent) { |trigger,msg| @shell.notify_outgoing_message(client, msg) }
|
48
|
+
client.on(:message) { |trigger,msg| @shell.notify_incoming_message(client, msg) }
|
49
|
+
client.on(:disconnect) { |trigger,msg| @shell.notify_disconnect(client) }
|
50
|
+
|
51
|
+
@shell.set_active_client(key)
|
52
|
+
""
|
53
|
+
end
|
54
|
+
|
55
|
+
def connect_kai
|
56
|
+
dest = args[1]
|
57
|
+
|
58
|
+
if dest.match(/^([a-zA-Z]+):\/\/[a-zA-Z0-9\-\.]+(:[0-9]+)?(\/)?/) then
|
59
|
+
connect_to_url(dest)
|
60
|
+
elsif dest.match(/^[0-9]+$/) then
|
61
|
+
connect_to_site_id(dest.to_i)
|
62
|
+
else
|
63
|
+
connect_to_usher_host(dest)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def request_caches
|
68
|
+
version = @shell.client(:config).site_version
|
69
|
+
client_interfaces.keys.each { |key| @shell.client(key).version = version }
|
70
|
+
@shell.client(:config).machines
|
71
|
+
end
|
72
|
+
|
73
|
+
def connect_to_url(base)
|
74
|
+
client_interfaces.each do |key, uri|
|
75
|
+
url = URI.join(base, uri).to_s
|
76
|
+
@shell.set_client(key, client_for_key(key, url))
|
77
|
+
end
|
78
|
+
|
79
|
+
request_caches
|
80
|
+
"Connected to #{@shell.client(:config).version.style(:host)} server"
|
81
|
+
end
|
82
|
+
|
83
|
+
def connect_to_site_id(site_id)
|
84
|
+
site = usherm.site_with_id(site_id)
|
85
|
+
return show_error("Site ##{site_id}: No such site ID in usher") if site.nil?
|
86
|
+
provider = usherm.provider_for_product(site_id, "Mercury")
|
87
|
+
return show_error("Site ##{site_id}: Does not offer Mercury") if provider.nil?
|
88
|
+
url = usherm.url_for_provider(provider)
|
89
|
+
return show_error("Site ##{site_id}: Site appears to be down") if url.nil?
|
90
|
+
puts "Connecting to Kai at #{url}"
|
91
|
+
connect_to_url(url)
|
92
|
+
end
|
93
|
+
|
94
|
+
def connect_to_usher_host(host)
|
95
|
+
site = usherm.site_with_name(host)
|
96
|
+
return show_error("Site #{host}: Does not appear to be listed in usher") if site.nil?
|
97
|
+
connect_to_site_id(site[:idSite])
|
98
|
+
end
|
99
|
+
|
100
|
+
def client_for_key(key, url)
|
101
|
+
case key
|
102
|
+
when :mercury
|
103
|
+
AcresClient.new(url, @shell.get_env(:kai_user), @shell.get_env(:kai_pass))
|
104
|
+
when :central
|
105
|
+
AcresClient.new(url, @shell.get_env(:usherm_user), @shell.get_env(:usherm_pass))
|
106
|
+
when :config
|
107
|
+
KaiConfigClient.new(url, @shell.get_env(:usherm_user), @shell.get_env(:usherm_pass))
|
108
|
+
when :qa
|
109
|
+
AcresClient.new(url, @shell.get_env(:kai_user), @shell.get_env(:kai_pass))
|
110
|
+
else
|
111
|
+
AcresClient.new(url, @shell.get_env(:usherm_user), @shell.get_env(:usherm_pass))
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def client_interfaces
|
116
|
+
{
|
117
|
+
mercury:"/mercury/client?wrap",
|
118
|
+
central:"/central/central?wrap",
|
119
|
+
config:"/config/config?wrap",
|
120
|
+
qa:"/mercury/qatest?wrap"
|
121
|
+
}
|
122
|
+
end
|
@@ -0,0 +1,280 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
category "Deployment"
|
4
|
+
description "Manage deployments"
|
5
|
+
usage [
|
6
|
+
"list -- list active publications",
|
7
|
+
"builds -- list builds",
|
8
|
+
"publish -- publish a build"
|
9
|
+
]
|
10
|
+
|
11
|
+
opt :url, "Deployment server URL"
|
12
|
+
|
13
|
+
opt :app, "List builds for application", :type => :string
|
14
|
+
opt :jeb, "List builds for jeb (i.e. three-word name)", :type => :string
|
15
|
+
opt :branch, "List builds for branch", :type => :string
|
16
|
+
opt :commit, "List builds for commit hash", :type => :string
|
17
|
+
opt :yes, "Automatically include all apps in publication. No effect if --app is supplied."
|
18
|
+
|
19
|
+
opt :site, "When used with list, only list publications to given site. When used with publish, indicates site to deploy to.", :type => :integer
|
20
|
+
|
21
|
+
|
22
|
+
validate do
|
23
|
+
return false if args.length <= 1
|
24
|
+
|
25
|
+
case args[1]
|
26
|
+
when "builds"
|
27
|
+
args.length == 2
|
28
|
+
when "list"
|
29
|
+
true
|
30
|
+
when "publish"
|
31
|
+
return "Must supply at least one site ID" unless args.length >= 3
|
32
|
+
true
|
33
|
+
else
|
34
|
+
"#{args[1]}: unknown subcommand"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
tab(:app) { all_apps }
|
39
|
+
tab(:jeb) { all_jebs }
|
40
|
+
tab(:branch) { all_branches }
|
41
|
+
tab(:commit) { all_commits }
|
42
|
+
|
43
|
+
tab 0 do
|
44
|
+
[:list, :builds, :publish]
|
45
|
+
end
|
46
|
+
|
47
|
+
run do
|
48
|
+
usherm.ensure_fresh
|
49
|
+
deployment.ensure_fresh
|
50
|
+
case args[1]
|
51
|
+
when "builds"
|
52
|
+
builds
|
53
|
+
when "list"
|
54
|
+
list
|
55
|
+
when "publish"
|
56
|
+
publish
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
###
|
61
|
+
# Tab completors
|
62
|
+
|
63
|
+
def all_apps
|
64
|
+
all_of_key(:appId)
|
65
|
+
end
|
66
|
+
|
67
|
+
def all_branches
|
68
|
+
all_of_key(:branches)
|
69
|
+
end
|
70
|
+
|
71
|
+
def all_commits
|
72
|
+
all_of_key(:commitId)
|
73
|
+
end
|
74
|
+
|
75
|
+
def all_jebs
|
76
|
+
all_of_key(:buildId)
|
77
|
+
end
|
78
|
+
|
79
|
+
def all_of_key(key)
|
80
|
+
instances = {}
|
81
|
+
|
82
|
+
deployment[:buildList][:builds].each do |build|
|
83
|
+
if build[key].is_a? Array then
|
84
|
+
build[key].each { |item| instances[item] = true }
|
85
|
+
else
|
86
|
+
instances[build[key]] = true
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
instances.keys
|
91
|
+
end
|
92
|
+
|
93
|
+
###
|
94
|
+
# List
|
95
|
+
|
96
|
+
def list
|
97
|
+
sites = if args.length >= 3 then
|
98
|
+
args[2..-1].map { |arg| arg.to_i }
|
99
|
+
else
|
100
|
+
(usherm[:usherSystemId][:usherSites].map { |site| site[:idSite] }).sort
|
101
|
+
end
|
102
|
+
|
103
|
+
sites.each do |site_id|
|
104
|
+
list_site(site_id, deployment.query(:buildPublicationList, site_id))
|
105
|
+
end
|
106
|
+
""
|
107
|
+
end
|
108
|
+
|
109
|
+
def list_site(site_id, publications)
|
110
|
+
site = usherm.site_with_id(site_id) || {name:"Unknown site", siteDescription:"Not in usher"}
|
111
|
+
lines = []
|
112
|
+
|
113
|
+
lines.push "Site #{site_id} -- #{site[:name]} (#{site[:siteDescription]})"
|
114
|
+
publications[:orders].keys.sort.each do |app|
|
115
|
+
next if params[:app] and not talk.name_matches?(app, params[:app])
|
116
|
+
lines.push list_site_app(app.to_s, publications[:orders][app])
|
117
|
+
end
|
118
|
+
|
119
|
+
puts lines.join("\n")+"\n" unless lines.length == 1
|
120
|
+
end
|
121
|
+
|
122
|
+
def list_site_app(app, orders)
|
123
|
+
pub = orders.first
|
124
|
+
return if params[:jeb] and pub[:buildInfo][:buildId] != params[:jeb]
|
125
|
+
return if params[:commit] and pub[:buildInfo][:commitId] != params[:commit]
|
126
|
+
return if params[:branch] and not(pub[:buildInfo][:branches].include? params[:branch])
|
127
|
+
"\t#{timestamp(pub[:publicationTime])} #{app.style(:class)} -- #{build_info(pub[:buildInfo])}"
|
128
|
+
end
|
129
|
+
|
130
|
+
###
|
131
|
+
# Builds
|
132
|
+
|
133
|
+
def builds
|
134
|
+
show_build_set(build_set)
|
135
|
+
""
|
136
|
+
end
|
137
|
+
|
138
|
+
def build_set
|
139
|
+
if params[:jeb] then
|
140
|
+
builds_for_jeb(params[:jeb])
|
141
|
+
elsif params[:commit] then
|
142
|
+
builds_for_commit(params[:commit])
|
143
|
+
elsif params[:branch] then
|
144
|
+
builds_for_branch(params[:branch])
|
145
|
+
elsif params[:app] then
|
146
|
+
builds_for_app(params[:app])
|
147
|
+
else
|
148
|
+
deployment[:buildList][:builds]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def builds_for_app(app)
|
153
|
+
deployment[:buildList][:builds].select { |build| talk.name_matches?(build[:appId], app) }
|
154
|
+
end
|
155
|
+
|
156
|
+
def builds_for_commit(commit)
|
157
|
+
set =
|
158
|
+
if params[:app] then
|
159
|
+
builds_for_app(params[:app])
|
160
|
+
else
|
161
|
+
deployment[:buildList][:builds]
|
162
|
+
end
|
163
|
+
|
164
|
+
set.select { |build| build[:commitId].start_with? commit }
|
165
|
+
end
|
166
|
+
|
167
|
+
def builds_for_jeb(jeb)
|
168
|
+
set =
|
169
|
+
if params[:app] then
|
170
|
+
builds_for_app(params[:app])
|
171
|
+
else
|
172
|
+
deployment[:buildList][:builds]
|
173
|
+
end
|
174
|
+
|
175
|
+
set.select { |build| build[:buildId] == jeb }
|
176
|
+
end
|
177
|
+
|
178
|
+
def builds_for_branch(branch)
|
179
|
+
set =
|
180
|
+
if params[:app] then
|
181
|
+
builds_for_app(params[:app])
|
182
|
+
else
|
183
|
+
deployment[:buildList][:builds]
|
184
|
+
end
|
185
|
+
|
186
|
+
set.select { |build| build[:branches].include? branch }
|
187
|
+
end
|
188
|
+
|
189
|
+
def build_set_by_app(builds)
|
190
|
+
apps = {}
|
191
|
+
builds.each do |build|
|
192
|
+
apps[build[:appId]] ||= []
|
193
|
+
apps[build[:appId]].push build
|
194
|
+
end
|
195
|
+
apps
|
196
|
+
end
|
197
|
+
|
198
|
+
###
|
199
|
+
# Publish
|
200
|
+
|
201
|
+
def prompt_for_apps(set)
|
202
|
+
apps = build_set_by_app(set)
|
203
|
+
return apps.keys.sort.map { |app| apps[app].last } if params[:yes]
|
204
|
+
|
205
|
+
selected = []
|
206
|
+
apps.keys.sort.each do |app|
|
207
|
+
build = apps[app].last
|
208
|
+
cls = sprintf("%s", app).style(:class)
|
209
|
+
name = sprintf("%s", build[:buildId]).style(:build_name)
|
210
|
+
branch = build[:branches].join(",").style(:branch)
|
211
|
+
line = sprintf("%99s", "#{cls}: #{name} [#{branch}]")
|
212
|
+
|
213
|
+
while true do
|
214
|
+
print "#{line} [y/N]> "
|
215
|
+
input = STDIN.gets.chomp.upcase rescue nil
|
216
|
+
input = "N" if not input.nil? and input.empty?
|
217
|
+
|
218
|
+
selected.push build if input == "Y"
|
219
|
+
puts if input.nil?
|
220
|
+
break if ["Y", "N"].include? input
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
selected
|
225
|
+
end
|
226
|
+
|
227
|
+
def publish
|
228
|
+
sites = args[2..-1].map { |arg| arg.to_i }
|
229
|
+
selected = prompt_for_apps(build_set)
|
230
|
+
selected.each do |build|
|
231
|
+
puts "Publishing #{build[:appId].style(:class)} \"#{build[:buildId].style(:build_name)}\" to site #{sites.join(",")}..."
|
232
|
+
deployment.publish_build(build, sites)
|
233
|
+
end
|
234
|
+
"Done."
|
235
|
+
end
|
236
|
+
|
237
|
+
###
|
238
|
+
# Other stuff
|
239
|
+
def show_app(app, limit=50)
|
240
|
+
list = deployment.index_list_by_app[app]
|
241
|
+
return show_error("#{app}: No such app in deployment server") if list.nil?
|
242
|
+
|
243
|
+
list.each do |build|
|
244
|
+
puts show_build(build)
|
245
|
+
end
|
246
|
+
|
247
|
+
""
|
248
|
+
end
|
249
|
+
|
250
|
+
def timestamp(ts)
|
251
|
+
DateTime.strptime(ts.to_s,'%s').strftime("%Y-%m-%d %H:%M:%S").style(:timestamp)
|
252
|
+
end
|
253
|
+
|
254
|
+
def build_id(build)
|
255
|
+
sprintf("%4d", build[:buildRecordId]).style(:identifier)
|
256
|
+
end
|
257
|
+
|
258
|
+
def branches(build)
|
259
|
+
build[:branches].join(",").style(:branch)
|
260
|
+
end
|
261
|
+
|
262
|
+
def author(build)
|
263
|
+
build[:author].style(:author) rescue ""
|
264
|
+
end
|
265
|
+
|
266
|
+
def build_info(build)
|
267
|
+
name = sprintf("%36s", build[:buildId]).style(:build_name)
|
268
|
+
"#{name} [#{branches(build)}] -- #{author(build)}"
|
269
|
+
end
|
270
|
+
|
271
|
+
def show_app(app, builds)
|
272
|
+
puts "#{app.style(:class)}: #{builds.length} builds"
|
273
|
+
builds.each { |build| puts timestamp(build[:postDate]) + " " + build_info(build) }
|
274
|
+
puts
|
275
|
+
end
|
276
|
+
|
277
|
+
def show_build_set(set)
|
278
|
+
apps = build_set_by_app(set)
|
279
|
+
apps.keys.sort.each { |app| show_app(app, apps[app]) }
|
280
|
+
end
|