nuri 0.5.1 → 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/herry13/nuri.png?branch=master)](https://travis-ci.org/herry13/nuri)
|
6
7
|
[![Gem Version](https://badge.fury.io/rb/nuri.png)](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
|