Skip to content

Instantly share code, notes, and snippets.

@jfryman
Last active August 29, 2015 14:12
Show Gist options
  • Save jfryman/4af1142157477d2831c4 to your computer and use it in GitHub Desktop.
Save jfryman/4af1142157477d2831c4 to your computer and use it in GitHub Desktop.
Graph Generators for ActionChain/Mistral
#!/usr/bin/env ruby
# damn ugly spike to generate dot files from an actionchain workflow
require 'yaml'
require 'erb'
module ActionChain
class Graph
include ERB::Util
attr_reader :title, :workflow
def initialize(title, workflow)
@title = title
@workflow = workflow
end # ActionChain::Graph#initialize
def render
ERB.new(dot_template).result(binding)
end # ActionChain::Graph#render
def dot_template
%{
graph <%= @title %> {
<% # Render all nodes %>
<% @workflow.nodes.each do |node| %>
<%= node %>;
<% end %>
<% # Render all relationships %>
<% @workflow.relationships.each do |relationship| %>
<% relationship.each do |action, attributes| %>
<%= action %> -> <%= attributes['destination'] %> <% if attributes.has_key?('condition') %>[label="<%= attributes['condition'] %>"]<% end %>;
<% end %>
<% end %>
}
}
end # ActionChain::Graph#dot_template
def save
File.open("#{workflow.name}.dot", 'w') { |f| f.write(render) }
end # ActionChain::Graph#save
end # ActionChain::Graph
class Parser
attr_accessor :name
def initialize(name, workflow)
@workflow = workflow
@name = name
end
def nodes
@nodes = Array.new
workflow['chain'].each { |task| @nodes << task['name'] }
@nodes.uniq
end
def workflow
@workflow
end
# Super ugly
def relationships
@relationships = Array.new
# Calculate workflow relationships
# For each task
workflow['chain'].each do |task|
['on-success', 'on-failure'].each do |condition|
@relationships << {
task['name'] => {
'destination' => task[condition],
'condition' => condition,
}
} if task.has_key?(condition)
end
end
@relationships
end # ActionChain::Parser#relationships
end # ActionChain::Parser
class Loader
attr_reader :file
def initialize(file)
begin
@file = YAML::load_file(file)
rescue => e
puts "Unable to load YAML file..."
puts e
exit 1
end
end
def self.generate_graphs(title, file)
workflow = ActionChain::Loader.new(file).file
parsed_workflow = ActionChain::Parser.new(title, workflow)
graph = ActionChain::Graph.new(title, parsed_workflow)
graph.save
end
end # ActionChain::Loader
end # ActionChain
# Command line business
file = ARGV.shift
title = file.split('.')[0]
ActionChain::Loader.generate_graphs(title, file)
#!/usr/bin/env ruby
# damn ugly spike to generate dot files from a mistral workflow
require 'yaml'
require 'erb'
module Mistral
class Graph
include ERB::Util
attr_reader :workflow
def initialize(workflow)
@workflow = workflow
end # Mistral::Graph#initialize
def render
ERB.new(dot_template).result(binding)
end # Mistral::Graph#render
def dot_template
%{
graph <%= @workflow.name %> {
<% # Render all nodes %>
<% @workflow.nodes.each do |node| %>
<%= node %>;
<% end %>
<% if @workflow.global_nodes %>
{ rank=same; <%= @workflow.global_nodes.join(' ') %> }
<% end %>
<% # Render all relationships %>
<% @workflow.relationships.each do |relationship| %>
<% relationship.each do |action, attributes| %>
<%= action %> -> <%= attributes['destination'] %> <% if attributes.has_key?('condition') %>[label="<%= attributes['condition'] %>"]<% end %>;
<% end %>
<% end %>
}
}
end # Mistral::Graph#dot_template
def save
File.open("#{workflow.name}.dot", 'w') { |f| f.write(render) }
end # Mistral::Graph#save
end # Mistral::Graph
class Parser
attr_accessor :name
def initialize(name, workflow)
@workflow = workflow
@name = name
end
def nodes
@nodes = Array.new
@relationships.each do |relationship|
@nodes << relationship.first[0]
end
@nodes.uniq
end
def workflow
@workflow
end
# Super ugly
def relationships
@relationships = Array.new
# Calculate workflow relationships
# For each task
workflow['tasks'].each do |task, attributes|
# Check to see if either an action or workflow has been assigned
['workflow', 'action'].each do |next_action|
next unless attributes.has_key?(next_action)
# And add a new relationship from the current task to the next step in the graph
@relationships << {
task => {
'destination' => attributes[next_action],
}
}
# Now, let's see what steps each next_action
# take if it has been defined
['on-success', 'on-error'].each do |condition|
# Let's first get all the globally defined defaults
if workflow.has_key?('task-defaults')
# Loop through all of the global defaults if they exist and add
# individual relationships
Array(workflow['task-defaults'][condition]).each do |global_action|
next unless workflow['task-defaults'].has_key? condition
@relationships << {
attributes[next_action] => {
'destination' => global_action,
'condition' => condition
}
}
end
end
# And now let's loop through the individual task conditions
Array(attributes[condition]).each do |local_action|
@relationships << {
attributes[next_action] => {
'destination' => local_action,
'condition' => condition
}
}
end if attributes.has_key?(condition)
end
end
end
@relationships
end # Mistral::Parser#relationships
def global_nodes
return nil unless workflow.has_key?('task-defaults')
@nodes = Array.new
['on-error', 'on-success'].each do |condition|
next unless workflow['task-defaults'].has_key?(condition)
@nodes << workflow['task-defaults'][condition]
end
@nodes.flatten.uniq
end
end # Mistral::Parser
class Loader
attr_reader :file
def initialize(file)
begin
@file = YAML::load_file(file)
rescue => e
puts "Unable to load YAML file..."
puts e
exit 1
end
end
def self.generate_graphs(file)
master_document = Mistral::Loader.new(file).file
master_document['workflows'].each do |workflow, attributes|
parsed_workflow = Mistral::Parser.new(workflow, attributes)
parsed_workflow.relationships
graph = Mistral::Graph.new(parsed_workflow)
graph.save
end
end
end # Mistral::Loader
end # Mistral
# Command line business
file = ARGV.shift
Mistral::Loader.generate_graphs(file)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment