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