nuri 0.5.1 → 0.5.2
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 +4 -4
- data/README.md +42 -11
- data/VERSION +1 -1
- data/bin/delete_modules +35 -11
- data/bin/install_module +54 -65
- data/bin/nuri +48 -38
- data/bin/sfw2graph +178 -0
- data/examples/bonfire.sfp +6 -14
- data/examples/hadoop1.sfp +21 -0
- data/examples/hadoop2.sfp +2 -1
- data/examples/{generator.rb → mockcloud/generator.rb} +0 -0
- data/examples/{run.rb → mockcloud/run.rb} +0 -0
- data/examples/wordpress.sfp +41 -0
- data/lib/nuri.rb +1 -1
- data/lib/nuri/choreographer.rb +1 -1
- data/lib/nuri/{net_helper.rb → helper.rb} +32 -2
- data/lib/nuri/master.rb +13 -27
- data/lib/nuri/orchestrator.rb +1 -1
- data/lib/nuri/targz.rb +97 -0
- data/modules/aptpackage/aptpackage.rb +22 -14
- data/modules/hadoop1/hadoop1.rb +84 -215
- data/modules/hadoop1/hadoop1.sfp +19 -8
- data/modules/hadoop2/hadoop2.rb +10 -3
- data/modules/hadoop2/hadoop2.sfp +1 -1
- data/modules/hadoop2/yarn-site.xml +13 -48
- data/modules/install_module +54 -65
- data/modules/package/package.sfp +1 -1
- data/modules/tarpackage/tarpackage.rb +54 -18
- metadata +9 -6
- data/bin/push_model +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5713187406a70381abeffa8e382892172e1cab3a
|
4
|
+
data.tar.gz: 589c3291070bf16e6ee05e4846192ae3d0ddca81
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5917ac035100aeb2e1dc75ff326034d9bbc7b5ef8a8679307581d389cca20dc8b9138bafddcdddccfbe9e6457184418eaa1ffb42ea6ddfd6c5fed6d8d30b8728
|
7
|
+
data.tar.gz: d14d4cfd96af587cdf02a4f622e1ad7773db83052eef68de3e304f1196313814aca4e0673d3069d721f662fba89db8158bef6a91b1b2f09eeb3e0ee13248c01a
|
data/README.md
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
-
Nuri
|
1
|
+
[Nuri](http://herry13.github.io/nuri)
|
2
2
|
====
|
3
3
|
- author: Herry [herry13@gmail.com]
|
4
4
|
- license: BSD
|
5
5
|
|
6
|
+
[](https://travis-ci.org/herry13/nuri)
|
6
7
|
[](https://badge.fury.io/rb/nuri)
|
7
8
|
|
8
9
|
Nuri is an automated workflow configuration tool. It allows us to define the desired state of
|
@@ -29,19 +30,19 @@ Requirements
|
|
29
30
|
------------
|
30
31
|
- Ruby (>= 1.9.1)
|
31
32
|
- Ruby Gems:
|
32
|
-
- sfplanner
|
33
|
-
-
|
34
|
-
-
|
33
|
+
- sfplanner
|
34
|
+
- sfpagent
|
35
|
+
- colorize
|
36
|
+
- coderay
|
35
37
|
|
36
|
-
Tested on: **Ubuntu 12.04**, **Debian Squeeze**
|
38
|
+
Tested on Linux: **Ubuntu 12.04**, **Debian Squeeze**, and **Fedora 13**
|
37
39
|
|
38
40
|
|
39
41
|
To install
|
40
42
|
----------
|
41
43
|
|
42
|
-
$ apt-get install
|
43
|
-
$ gem install
|
44
|
-
$ git clone https://github.com/herry13/nuri
|
44
|
+
$ apt-get install curl ruby1.9.1 ruby1.9.1-dev libz-dev libaugeas-ruby1.9.1 libxml2-dev libxslt-dev make gcc
|
45
|
+
$ gem install nuri
|
45
46
|
|
46
47
|
|
47
48
|
Usage
|
@@ -49,16 +50,46 @@ Usage
|
|
49
50
|
Assume that the model of your system is in file "model.sfp".
|
50
51
|
- to get the current state
|
51
52
|
|
52
|
-
$
|
53
|
+
$ nuri state -m model.sfp
|
53
54
|
|
54
55
|
- to generate the plan
|
55
56
|
|
56
|
-
$
|
57
|
+
$ nuri plan -m model.sfp
|
57
58
|
|
58
59
|
press 'Y' and enter to execute the plan (if the plan exists).
|
59
60
|
|
60
61
|
- to execute any generated plan
|
61
62
|
|
62
|
-
$
|
63
|
+
$ nuri plan -m model.sfp -a
|
63
64
|
|
64
65
|
|
66
|
+
Console mode
|
67
|
+
------------
|
68
|
+
We could enter Nuri console mode and do operations:
|
69
|
+
- enter console mode
|
70
|
+
|
71
|
+
$ nuri console
|
72
|
+
|
73
|
+
- edit the model which is stored in ~/.nuri/main.sfp
|
74
|
+
|
75
|
+
nuri@user> edit
|
76
|
+
|
77
|
+
- compile and print the model
|
78
|
+
|
79
|
+
nuri@user> model
|
80
|
+
|
81
|
+
- generate the current state of the system based on the model
|
82
|
+
|
83
|
+
nuri@user> state
|
84
|
+
|
85
|
+
- generate the plan
|
86
|
+
|
87
|
+
nuri@user> plan
|
88
|
+
|
89
|
+
- orchestrate the execution of any plan generated by Nuri
|
90
|
+
|
91
|
+
nuri@user> plan -a
|
92
|
+
|
93
|
+
- choreograph the execution of any plan generated by Nuri
|
94
|
+
|
95
|
+
nuri@user> bsig -a
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.5.
|
1
|
+
0.5.2
|
data/bin/delete_modules
CHANGED
@@ -1,11 +1,35 @@
|
|
1
|
-
#!/bin/
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'uri'
|
4
|
+
require 'net/http'
|
5
|
+
|
6
|
+
module Nuri
|
7
|
+
module Util
|
8
|
+
def self.delete_modules(address, port, protocol="http")
|
9
|
+
url = "#{protocol}://#{address}:#{port}/modules"
|
10
|
+
uri = URI.parse(url)
|
11
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
12
|
+
request = Net::HTTP::Delete.new(uri.request_uri)
|
13
|
+
response = http.request(request)
|
14
|
+
(response.code == '200')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
if $0 == __FILE__
|
20
|
+
DefaultPort = 1314
|
21
|
+
|
22
|
+
if ARGV.length < 1
|
23
|
+
puts "Usage: delete_modules <address> [port]"
|
24
|
+
exit(1)
|
25
|
+
end
|
26
|
+
|
27
|
+
address = ARGV.shift
|
28
|
+
port = (ARGV[0].nil? ? DefaultPort : ARGV[0].to_i)
|
29
|
+
|
30
|
+
if Nuri::Util.delete_modules(address, port)
|
31
|
+
puts '{"status":"ok"}'
|
32
|
+
else
|
33
|
+
puts '{"status":"failed"}'
|
34
|
+
end
|
35
|
+
end
|
data/bin/install_module
CHANGED
@@ -1,65 +1,54 @@
|
|
1
|
-
#!/bin/
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
address
|
13
|
-
|
14
|
-
|
15
|
-
#
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
# delete directory that holds temporary files
|
57
|
-
rm -rf $dir
|
58
|
-
|
59
|
-
if [[ $missing_module == 0 ]]; then
|
60
|
-
echo "status: ok"
|
61
|
-
else
|
62
|
-
echo "status: failed"
|
63
|
-
fi
|
64
|
-
|
65
|
-
exit $missing_module
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'uri'
|
5
|
+
require 'net/http'
|
6
|
+
require File.dirname(__FILE__) + '/../lib/nuri/targz'
|
7
|
+
|
8
|
+
module Nuri
|
9
|
+
module Util
|
10
|
+
TarGzip = Object.new.extend(Util::Tar)
|
11
|
+
|
12
|
+
def self.install_modules(address, port, modules, protocol="http")
|
13
|
+
data = {}
|
14
|
+
modules.each do |module_name|
|
15
|
+
raise Exception, "Module #{module_name} is not exist!" if not ::File.directory?(module_name)
|
16
|
+
data[module_name] = TarGzip.targzip(module_name, module_name).read
|
17
|
+
end
|
18
|
+
|
19
|
+
url = "#{protocol}://#{address}:#{port}/modules"
|
20
|
+
uri = URI.parse(url)
|
21
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
22
|
+
request = Net::HTTP::Put.new(uri.request_uri)
|
23
|
+
request.set_form_data(data)
|
24
|
+
response = http.request(request)
|
25
|
+
|
26
|
+
(response.code == '200')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
if $0 == __FILE__
|
32
|
+
if ARGV.length < 2
|
33
|
+
puts "Usage: install_module <address> [port] <module-name> ..."
|
34
|
+
exit(1)
|
35
|
+
end
|
36
|
+
|
37
|
+
DefaultPort = 1314
|
38
|
+
|
39
|
+
address = ARGV.shift
|
40
|
+
port = (ARGV.length >= 2 ? ARGV.shift : DefaultPort)
|
41
|
+
modules = ARGV
|
42
|
+
missing = modules.select { |mod| not ::File.directory?(mod) }
|
43
|
+
modules = modules - missing
|
44
|
+
|
45
|
+
success = Nuri::Util.install_modules(address, port, modules),
|
46
|
+
output = {
|
47
|
+
:status => success,
|
48
|
+
:installed_modules => modules,
|
49
|
+
:missing_modules => missing
|
50
|
+
}
|
51
|
+
puts JSON.generate(output)
|
52
|
+
|
53
|
+
exit (success ? 0 : 1)
|
54
|
+
end
|
data/bin/nuri
CHANGED
@@ -6,8 +6,6 @@ require "#{dir}/lib/nuri"
|
|
6
6
|
Version = File.read(File.dirname(__FILE__) + "/../VERSION").strip
|
7
7
|
About = "Nuri #{Version} (c) 2013"
|
8
8
|
|
9
|
-
HTTP = Object.new.extend(Nuri::Net::Helper)
|
10
|
-
|
11
9
|
Nuri.init
|
12
10
|
|
13
11
|
def console(*args)
|
@@ -15,8 +13,9 @@ def console(*args)
|
|
15
13
|
$stdout.puts(args)
|
16
14
|
end
|
17
15
|
|
18
|
-
class
|
19
|
-
|
16
|
+
class Nuri::Console
|
17
|
+
include Nuri::Helper
|
18
|
+
|
20
19
|
ParentEliminator = Sfp::Visitor::ParentEliminator.new
|
21
20
|
|
22
21
|
def process_args(args, parser)
|
@@ -77,6 +76,7 @@ EOS
|
|
77
76
|
opt :plain, 'print output in plain JSON'
|
78
77
|
opt :no_interactive, 'disable interactive input'
|
79
78
|
opt :no_push_module, 'disable automatic push module'
|
79
|
+
opt :image, 'image graph of the generated plan', :default => Dir.home + '/.nuri/plan.png'
|
80
80
|
end
|
81
81
|
help, args = check_help(args)
|
82
82
|
opts = process_args args, parser
|
@@ -91,6 +91,13 @@ EOS
|
|
91
91
|
plan = master.get_plan(opts)
|
92
92
|
if plan.is_a?(Hash) and !plan['workflow'].nil?
|
93
93
|
if plan['workflow'].length > 0
|
94
|
+
plan_file = Dir.home + '/.nuri/plan.json'
|
95
|
+
File.open(plan_file, 'w') { |f| f.write(JSON.generate(plan)) }
|
96
|
+
|
97
|
+
if opts[:image]
|
98
|
+
system "#{File.dirname(__FILE__)}/sfw2graph #{plan_file} #{opts[:image]}"
|
99
|
+
end
|
100
|
+
|
94
101
|
if opts[:plain]
|
95
102
|
puts JSON.generate(plan)
|
96
103
|
else
|
@@ -120,6 +127,7 @@ EOS
|
|
120
127
|
puts "Execution failed!".red
|
121
128
|
end
|
122
129
|
end
|
130
|
+
|
123
131
|
elsif plan['workflow'].length == 0
|
124
132
|
puts (opts[:plain] ? 'Goal state has been achieved.' : 'Goal state has been achieved.').green
|
125
133
|
end
|
@@ -212,7 +220,7 @@ where <subcommand> is:
|
|
212
220
|
model get current model
|
213
221
|
bsig get current Behavioural Signature model
|
214
222
|
exec <action> execute given action description
|
215
|
-
<action>
|
223
|
+
<action> := <path> [param1=value1 param2=value2 ...]
|
216
224
|
module get modules list
|
217
225
|
log get last 100 lines of logs
|
218
226
|
list list of available agents from model file
|
@@ -224,6 +232,7 @@ EOS
|
|
224
232
|
opt :port, "port", :default => 1314
|
225
233
|
opt :ssh_user, "SSH username", :short => '-u', :default => 'root'
|
226
234
|
opt :ssh_port, "SSH port", :short => '-p', :default => '22'
|
235
|
+
opt :raw, "print a RAW output"
|
227
236
|
end
|
228
237
|
help, args = check_help(args)
|
229
238
|
subcommand = (args.length > 0 ? args.shift : '')
|
@@ -237,7 +246,7 @@ EOS
|
|
237
246
|
|
238
247
|
if help
|
239
248
|
parser.educate(STDOUT)
|
240
|
-
elsif `which sfpagent`.strip.length > 0
|
249
|
+
elsif `which sfpagent`.strip.length > 0 or ssh_opt.length > 0
|
241
250
|
case subcommand
|
242
251
|
when 'install'
|
243
252
|
system("#{ssh_opt} sudo gem install sfpagent --no-ri --no-rdoc")
|
@@ -259,32 +268,52 @@ EOS
|
|
259
268
|
system("#{ssh_opt} sudo gem update sfpagent --no-ri --no-rdoc")
|
260
269
|
|
261
270
|
when 'state'
|
262
|
-
code, state =
|
271
|
+
code, state = get_data(opts[:address], 1314, "/state")
|
263
272
|
if code == '200' and state =~ /{.*}/
|
264
273
|
state = JSON[state]['state']
|
265
|
-
|
266
|
-
|
274
|
+
if not opts[:raw]
|
275
|
+
state.keys.each { |key| state.delete(key) if state[key]['_context'] != 'object' }
|
276
|
+
state.accept(Sfp::Helper::Sfp2Ruby)
|
277
|
+
end
|
278
|
+
puts CodeRay.encode(YAML.dump(state), :yaml, :terminal)
|
267
279
|
else
|
268
280
|
puts state
|
269
281
|
end
|
270
282
|
|
271
283
|
when 'model'
|
272
|
-
code, model =
|
284
|
+
code, model = get_data(opts[:address], 1314, "/model")
|
273
285
|
if code == '200' and model =~ /{.*}/
|
274
286
|
model = JSON[model]
|
275
|
-
|
276
|
-
|
287
|
+
if not opts[:raw]
|
288
|
+
model.select! { |k,v| k[0,1] != '_' and (not v.is_a?(Hash) or v['_context'] == 'object') }
|
289
|
+
model.accept(Sfp::Helper::Sfp2Ruby)
|
290
|
+
end
|
291
|
+
puts CodeRay.encode(YAML.dump(model), :yaml, :terminal)
|
292
|
+
elsif code == '404'
|
293
|
+
puts "The model of desired state is not exist!"
|
277
294
|
else
|
278
|
-
puts
|
295
|
+
$stderr.puts "Error: #{code}"
|
279
296
|
end
|
280
297
|
|
281
298
|
when 'bsig'
|
282
|
-
code, json =
|
283
|
-
|
299
|
+
code, json = get_data(opts[:address], 1314, "/bsig")
|
300
|
+
if code == '200' and json.length > 0
|
301
|
+
puts CodeRay.encode(JSON.pretty_generate(JSON[json]), :json, :terminal)
|
302
|
+
elsif code == '404'
|
303
|
+
puts 'Behavioural Signature model is not exist!'
|
304
|
+
else
|
305
|
+
$stderr.puts "Error: #{code}"
|
306
|
+
end
|
284
307
|
|
285
308
|
when 'module'
|
286
|
-
code, json =
|
287
|
-
|
309
|
+
code, json = get_data(opts[:address], 1314, "/modules")
|
310
|
+
if code == '200' and json.length >= 2
|
311
|
+
puts CodeRay.encode(YAML.dump(JSON[json]), :yaml, :terminal)
|
312
|
+
elsif code == '404'
|
313
|
+
puts 'No module is available!'
|
314
|
+
else
|
315
|
+
$stderr.puts "Error: #{code}"
|
316
|
+
end
|
288
317
|
|
289
318
|
when 'exec', 'execute'
|
290
319
|
if args.length > 0
|
@@ -298,7 +327,7 @@ EOS
|
|
298
327
|
end
|
299
328
|
end
|
300
329
|
data = { 'action' => JSON.generate({ 'name' => name, 'parameters' => parameters }) }
|
301
|
-
code, _ =
|
330
|
+
code, _ = post_data(opts[:address], 1314, "/execute", data)
|
302
331
|
puts (code == '200' ? "Executing #{name} [OK]".green : "Executing #{name} [Failed]".red)
|
303
332
|
else
|
304
333
|
$stderr.puts 'Invalid parameters (usage: agent exec <action-path> [action-arguments]).'
|
@@ -497,23 +526,4 @@ EOS
|
|
497
526
|
end
|
498
527
|
end
|
499
528
|
|
500
|
-
|
501
|
-
Sfp2Ruby = Object.new
|
502
|
-
def Sfp2Ruby.visit(name, value, parent)
|
503
|
-
if name[0] == '_'
|
504
|
-
parent.delete(name)
|
505
|
-
elsif value.is_a?(Hash)
|
506
|
-
case value['_context']
|
507
|
-
when 'null'
|
508
|
-
parent[name] = nil
|
509
|
-
when 'any_value', 'constraint', 'procedure'
|
510
|
-
parent.delete(name)
|
511
|
-
when 'set'
|
512
|
-
parent[name] = value['_values']
|
513
|
-
end
|
514
|
-
end
|
515
|
-
true
|
516
|
-
end
|
517
|
-
end
|
518
|
-
|
519
|
-
Sfp::Console.new.run if $0 == __FILE__
|
529
|
+
Nuri::Console.new.run if $0 == __FILE__
|
data/bin/sfw2graph
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Sfp
|
7
|
+
end
|
8
|
+
|
9
|
+
module Sfp::Graph
|
10
|
+
ActionColor = 'white'
|
11
|
+
ActionLabelWithParameters = false
|
12
|
+
|
13
|
+
def self.dot2image(dot, image_file)
|
14
|
+
dot_file = "/tmp/#{Time.now.getutc.to_i}.dot"
|
15
|
+
File.open(dot_file, 'w') { |f|
|
16
|
+
f.write(dot)
|
17
|
+
f.flush
|
18
|
+
}
|
19
|
+
!!system("dot -Tpng -o #{image_file} #{dot_file}")
|
20
|
+
ensure
|
21
|
+
File.delete(dot_file) if File.exist?(dot_file)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.clean(value)
|
25
|
+
return value[2, value.length-2] if value[0,2] == '$.'
|
26
|
+
return value
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.get_label(action, withparameters=true)
|
30
|
+
label = clean(action["name"])
|
31
|
+
if withparameters and ActionLabelWithParameters
|
32
|
+
label += "("
|
33
|
+
if action["parameters"].length > 0
|
34
|
+
action["parameters"].each { |key,value|
|
35
|
+
label += "#{clean(key)}=#{clean(value.to_s)},"
|
36
|
+
}
|
37
|
+
label.chop!
|
38
|
+
end
|
39
|
+
label += ')'
|
40
|
+
end
|
41
|
+
return label
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.partial2dot(json)
|
45
|
+
dot = "digraph {\n"
|
46
|
+
|
47
|
+
dot += "init_state [label=\"\", shape=doublecircle, fixedsize=true, width=0.35];\n"
|
48
|
+
dot += "final_state [label=\"\", shape=doublecircle, style=filled, fillcolor=black, fixedsize=true, width=0.35];\n"
|
49
|
+
last_actions = Hash.new
|
50
|
+
json["workflow"].each { |action|
|
51
|
+
dot += "#{action["id"]}[label=\"#{get_label(action)}\", shape=rect, style=filled, fillcolor=#{ActionColor}];\n"
|
52
|
+
last_actions[action["id"].to_s] = action
|
53
|
+
}
|
54
|
+
|
55
|
+
json["workflow"].each { |action|
|
56
|
+
has_predecessor = false
|
57
|
+
action["predecessors"].each { |prevId|
|
58
|
+
dot += "#{prevId} -> #{action["id"]};\n"
|
59
|
+
has_predecessor = true
|
60
|
+
last_actions.delete(prevId.to_s)
|
61
|
+
}
|
62
|
+
if not has_predecessor
|
63
|
+
dot += "init_state -> #{action["id"]};\n"
|
64
|
+
end
|
65
|
+
}
|
66
|
+
|
67
|
+
last_actions.each { |id,action|
|
68
|
+
dot += "#{id} -> final_state;\n"
|
69
|
+
}
|
70
|
+
|
71
|
+
dot += "}"
|
72
|
+
|
73
|
+
return dot
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.stage2dot(json)
|
77
|
+
dot = "digraph {\n"
|
78
|
+
|
79
|
+
dot += "init_state [label=\"\", shape=doublecircle, fixedsize=true, width=0.35];\n"
|
80
|
+
dot += "final_state [label=\"\", shape=doublecircle, style=filled, fillcolor=black, fixedsize=true, width=0.35];\n"
|
81
|
+
index = 0
|
82
|
+
prevState = "init_state"
|
83
|
+
json["workflow"].each { |stage|
|
84
|
+
id = 0
|
85
|
+
stage.each { |action|
|
86
|
+
dot += "a" + index.to_s + "a" + id.to_s + '[label="' + get_label(action) + '", shape=rect]' + ";\n"
|
87
|
+
dot += prevState + " -> a" + index.to_s + "a" + id.to_s + ";\n"
|
88
|
+
id += 1
|
89
|
+
}
|
90
|
+
if index < json["workflow"].length-1
|
91
|
+
dot += "state" + index.to_s + ' [label="", shape=circle, fixedsize=true, width=0.35]' + ";\n"
|
92
|
+
prevState = "state" + index.to_s
|
93
|
+
id = 0
|
94
|
+
stage.each { |action|
|
95
|
+
dot += "a" + index.to_s + "a" + id.to_s + " -> " + prevState + ";\n"
|
96
|
+
id += 1
|
97
|
+
}
|
98
|
+
else
|
99
|
+
id = 0
|
100
|
+
stage.each { |action|
|
101
|
+
dot += "a" + index.to_s + "a" + id.to_s + " -> final_state;\n"
|
102
|
+
id += 1
|
103
|
+
}
|
104
|
+
end
|
105
|
+
index += 1
|
106
|
+
}
|
107
|
+
dot += "}"
|
108
|
+
return dot
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.sequential2dot(json)
|
112
|
+
dot = "digraph {\n"
|
113
|
+
|
114
|
+
dot += "init_state [label=\"\", shape=doublecircle, fixedsize=true, width=0.35];\n"
|
115
|
+
dot += "final_state [label=\"\", shape=doublecircle, style=filled, fillcolor=black, fixedsize=true, width=0.35];\n"
|
116
|
+
id = 0
|
117
|
+
json["workflow"].each { |action|
|
118
|
+
dot += id.to_s + '[label="' + get_label(action) + '", shape=rect]' + ";\n"
|
119
|
+
id += 1
|
120
|
+
}
|
121
|
+
|
122
|
+
id = 0
|
123
|
+
prevActionId = nil
|
124
|
+
json["workflow"].each { |action|
|
125
|
+
if id == 0
|
126
|
+
dot += "init_state -> " + id.to_s + ";\n"
|
127
|
+
elsif id == json["workflow"].length-1
|
128
|
+
dot += id.to_s + " -> final_state;\n"
|
129
|
+
end
|
130
|
+
if prevActionId != nil
|
131
|
+
dot += prevActionId.to_s + " -> " + id.to_s + ";\n"
|
132
|
+
end
|
133
|
+
prevActionId = id
|
134
|
+
id += 1
|
135
|
+
}
|
136
|
+
dot += "}"
|
137
|
+
return dot
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def main
|
142
|
+
if ARGV.length < 1
|
143
|
+
puts "Convert SFP plan to an image graph with Graphviz (dot).\n\nUsage: sfw2dot.rb <input-file> [output-file]\n\n"
|
144
|
+
exit
|
145
|
+
end
|
146
|
+
|
147
|
+
fp = open(ARGV[0], "rb")
|
148
|
+
json = JSON.parse(fp.read)
|
149
|
+
fp.close
|
150
|
+
|
151
|
+
dot = ""
|
152
|
+
case json["type"]
|
153
|
+
when 'partial-order', 'parallel'
|
154
|
+
dot = Sfp::Graph::partial2dot(json)
|
155
|
+
when 'sequential'
|
156
|
+
dot = Sfp::Graph::sequential2dot(json)
|
157
|
+
when 'stage'
|
158
|
+
dot = Sfp::Graph::stage2dot(json)
|
159
|
+
else
|
160
|
+
throw Exception, "Unrecognised type of workflow: #{json['type']}"
|
161
|
+
end
|
162
|
+
|
163
|
+
outfile = "/tmp/#{Time.now.to_f}.dot"
|
164
|
+
fp = open(outfile, "w");
|
165
|
+
fp.write(dot)
|
166
|
+
fp.close
|
167
|
+
|
168
|
+
cmd = 'dot -Tpng -o';
|
169
|
+
if ARGV.length > 1
|
170
|
+
cmd += "#{ARGV[1]} #{outfile}"
|
171
|
+
else
|
172
|
+
cmd += ARGV[0].sub(/\.[a-zA-Z]*/,'') + ".png < #{outfile}"
|
173
|
+
end
|
174
|
+
system(cmd)
|
175
|
+
File.delete(outfile)
|
176
|
+
end
|
177
|
+
|
178
|
+
main if __FILE__ == $0
|