pipely 0.0.1 → 0.1.0

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.
data/README.md CHANGED
@@ -1,4 +1,41 @@
1
1
  pipely
2
2
  ======
3
+ [![Gem Version](https://badge.fury.io/rb/pipely.png)](http://badge.fury.io/rb/pipely) [![Build Status](https://travis-ci.org/swipely/pipely.png?branch=master)](https://travis-ci.org/swipely/pipely) [![Code Climate](https://codeclimate.com/repos/524b941156b1025b6c08a96a/badges/c0ad2bbec610f1d0f0f7/gpa.png)](https://codeclimate.com/repos/524b941156b1025b6c08a96a/feed)
3
4
 
4
5
  Visualize pipeline definitions for AWS Data Pipeline
6
+
7
+ "AWS Data Pipeline is a web service that you can use to automate the movement and transformation of data. With AWS Data Pipeline, you can define data-driven workflows, so that tasks can be dependent on the successful completion of previous tasks."
8
+
9
+ http://docs.aws.amazon.com/datapipeline/latest/DeveloperGuide/what-is-datapipeline.html
10
+
11
+
12
+ ## Install
13
+
14
+ (First install [GraphViz](http://www.graphviz.org) if it is not already installed.)
15
+
16
+ Into Gemfile from rubygems.org:
17
+
18
+ gem 'pipely'
19
+
20
+ Into environment gems from rubygems.org:
21
+
22
+ gem install pipely
23
+
24
+
25
+ ## Usage
26
+
27
+ (If you used the Gemfile install, prefix the below commands with `bundle exec`.)
28
+
29
+ ### Command-line Interface
30
+
31
+ To render a JSON pipeline definition as a PNG graph visualization:
32
+
33
+ pipely definition.json
34
+
35
+ To specify the output path for PNG files:
36
+
37
+ pipely -o path/to/graph/pngs definition.json
38
+
39
+ ### Rake Tasks
40
+
41
+ Coming soon.
data/bin/pipely CHANGED
@@ -3,10 +3,21 @@
3
3
  require 'pipely'
4
4
  require 'optparse'
5
5
 
6
- output_path = nil
6
+ pipeline_id = input_path = output_path = nil
7
+ verbose = automatic_open = false
7
8
 
8
9
  OptionParser.new do |opts|
9
- opts.banner = "Usage: pipely [options] definition.json"
10
+ opts.banner = "Usage: pipely [options]"
11
+
12
+ opts.on("-p", "--pipeline-id PIPELINE_ID",
13
+ "ID of a live pipeline to visualize with live execution statuses") do |id|
14
+ pipeline_id = id
15
+ end
16
+
17
+ opts.on("-i", "--input PATH",
18
+ "Path to a JSON pipeline definition file to visualize") do |input|
19
+ input_path = input
20
+ end
10
21
 
11
22
  opts.on("-o", "--output PATH",
12
23
  "Path to write PNG file(s) of graph visualization(s)") do |output|
@@ -14,11 +25,20 @@ OptionParser.new do |opts|
14
25
  end
15
26
  end.parse!
16
27
 
17
- definition_file = ARGV.last
18
- definition_json = File.open(definition_file).read
28
+ if pipeline_id
29
+ live_pipeline = Pipely::LivePipeline.new(pipeline_id)
30
+ live_pipeline.print_runs_report
31
+ live_pipeline.render_graphs
32
+
33
+ puts "open: #{automatic_open}"
34
+ puts "verbose: #{verbose}"
19
35
 
20
- output_base = File.basename(definition_file,".*") + '.png'
21
- output_file = output_path ? File.join(output_path, output_base) : output_base
36
+ else
37
+ input_path ||= ARGV.last
38
+ definition_json = File.open(input_path).read
39
+ output_base = File.basename(definition_file,".*") + '.png'
40
+ output_file = output_path ? File.join(output_path, output_base) : output_base
22
41
 
23
- puts "Generating #{output_file}"
24
- Pipely.draw(definition_json, output_file)
42
+ puts "Generating #{output_file}"
43
+ Pipely.draw(definition_json, output_file)
44
+ end
data/lib/pipely.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'pipely/definition'
2
2
  require 'pipely/graph_builder'
3
+ require 'pipely/live_pipeline'
3
4
 
4
5
  # The top-level module for this gem. It provides the recommended public
5
6
  # interface for using Pipely to visualize and manipulate your Data Pipeline
@@ -7,9 +8,12 @@ require 'pipely/graph_builder'
7
8
  #
8
9
  module Pipely
9
10
 
10
- def self.draw(definition_json, filename, node_attributes=nil)
11
+ def self.draw(definition_json, filename, component_attributes=nil)
11
12
  definition = Definition.parse(definition_json)
12
- definition.apply_node_attributes(node_attributes) if node_attributes
13
+
14
+ if component_attributes
15
+ definition.apply_component_attributes(component_attributes)
16
+ end
13
17
 
14
18
  graph_builder = GraphBuilder.new
15
19
 
@@ -27,7 +27,7 @@ module Pipely
27
27
  'FAILED' => 'orangered',
28
28
  }
29
29
 
30
- include Virtus
30
+ include Virtus.model
31
31
 
32
32
  attribute :id, String
33
33
  attribute :type, String
@@ -84,7 +84,7 @@ module Pipely
84
84
  deps
85
85
  end
86
86
 
87
- def to_json(options={})
87
+ def to_json(options={}, depth=0)
88
88
  h = @original_args
89
89
 
90
90
  REFERENCE_KEYS.each do |key|
@@ -0,0 +1,66 @@
1
+ require 'fog'
2
+
3
+ module Pipely
4
+
5
+ # Uses Fog to communicate with the AWS Data Pipeline service
6
+ class FogClient < Struct.new(:pipeline_id)
7
+
8
+ def definition
9
+ objects = Fog::AWS[:data_pipeline].get_pipeline_definition(pipeline_id)
10
+
11
+ flattened_objects = []
12
+
13
+ objects['pipelineObjects'].each do |object|
14
+ h = {
15
+ id: object['id'],
16
+ name: object['name'],
17
+ }
18
+
19
+ object['fields'].each do |field|
20
+ k = field['key']
21
+ if field['refValue']
22
+ h[k] ||= []
23
+ h[k] << { ref: field['refValue'] }
24
+ else
25
+ h[k] = field['stringValue']
26
+ end
27
+ end
28
+
29
+ flattened_objects << h
30
+ end
31
+
32
+ { objects: flattened_objects }.to_json
33
+ end
34
+
35
+ def task_states_by_scheduled_start
36
+ c = Fog::AWS[:data_pipeline]
37
+ instances = c.query_objects(pipeline_id, 'INSTANCE')
38
+ instance_details = c.describe_objects(pipeline_id, instances['ids'])
39
+
40
+ task_states_by_scheduled_start = {}
41
+
42
+ instance_details['pipelineObjects'].each do |pipeline_object|
43
+ component_id = status = scheduled_start = nil
44
+
45
+ pipeline_object['fields'].each do |field|
46
+ case field['key']
47
+ when '@componentParent'
48
+ component_id = field['refValue']
49
+ when '@status'
50
+ status = field['stringValue']
51
+ when '@scheduledStartTime'
52
+ scheduled_start = field['stringValue']
53
+ end
54
+ end
55
+
56
+ task_states_by_scheduled_start[scheduled_start] ||= {}
57
+ task_states_by_scheduled_start[scheduled_start][component_id] = {
58
+ execution_state: status
59
+ }
60
+ end
61
+
62
+ task_states_by_scheduled_start
63
+ end
64
+
65
+ end
66
+ end
@@ -29,22 +29,34 @@ module Pipely
29
29
  def add_edges(components)
30
30
  components.each do |component|
31
31
  component.dependencies.each do |dependency|
32
- options = {
33
- :label => dependency.label,
34
- :color => dependency.color,
35
- }
36
-
37
- options[:dir] = 'back' if ('input' == dependency.label)
38
-
39
- @graph.add_edges(
40
- component.id,
41
- dependency.target_id,
42
- options
43
- )
32
+ add_edge(component, dependency)
44
33
  end
45
34
  end
46
35
  end
47
36
 
37
+ def add_edge(component, dependency)
38
+ options = {
39
+ :label => dependency.label,
40
+ :color => dependency.color,
41
+ }
42
+
43
+ options[:dir] = 'back' if ('input' == dependency.label)
44
+
45
+ if 'output' == dependency.label
46
+ @graph.add_edges(
47
+ dependency.target_id,
48
+ component.id,
49
+ options
50
+ )
51
+ else
52
+ @graph.add_edges(
53
+ component.id,
54
+ dependency.target_id,
55
+ options
56
+ )
57
+ end
58
+ end
59
+
48
60
  end
49
61
 
50
62
  end
@@ -0,0 +1,42 @@
1
+ require 'pipely/fog_client'
2
+ require 'pipely/runs_report'
3
+
4
+ module Pipely
5
+
6
+ # Represent a pipeline that has been deployed to AWS DataPipeline
7
+ class LivePipeline
8
+
9
+ def initialize(pipeline_id)
10
+ @pipeline_id = pipeline_id
11
+
12
+ client = FogClient.new(pipeline_id)
13
+ @definition_json = client.definition
14
+ @task_states_by_scheduled_start = client.task_states_by_scheduled_start
15
+
16
+ unless @definition_json
17
+ raise "No definition found for #{client.pipeline_id}"
18
+ end
19
+
20
+ if @task_states_by_scheduled_start.empty?
21
+ raise "No runs found for #{client.pipeline_id}"
22
+ end
23
+ end
24
+
25
+ def print_runs_report
26
+ RunsReport.new(@task_states_by_scheduled_start).print
27
+ end
28
+
29
+ def render_graphs
30
+ @task_states_by_scheduled_start.each do |start, task_states|
31
+ utc_time = Time.now.to_i
32
+ formatted_start = start.gsub(/[:-]/, '').sub('T', '-')
33
+
34
+ filename = "graphs/#{@pipeline_id}-#{formatted_start}-#{utc_time}.png"
35
+ puts "Generating #{filename}"
36
+ Pipely.draw(@definition_json, filename, task_states)
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+ end
@@ -14,7 +14,7 @@ module Pipely
14
14
  @raw_references.map{|h| Dependency.new(label, h['ref'])}
15
15
  end
16
16
 
17
- def to_json(options={})
17
+ def to_json(options={}, depth=0)
18
18
  if 1 == @raw_references.count
19
19
  @raw_references.first.to_json(options)
20
20
  else
@@ -0,0 +1,20 @@
1
+ module Pipely
2
+
3
+ # Prints a CLI report of the execution status of a live pipeline
4
+ class RunsReport < Struct.new(:task_states_by_scheduled_start)
5
+
6
+ def print
7
+ task_states_by_scheduled_start.each do |scheduled_start, task_states|
8
+ task_states.to_a.sort_by(&:first).each do |task_name, attributes|
9
+ current_state = attributes[:execution_state]
10
+
11
+ puts task_name.ljust(55) +
12
+ "scheduled_start: #{scheduled_start}\t\t" +
13
+ "current_state: #{current_state}"
14
+ end
15
+ end
16
+ end
17
+
18
+ end
19
+ end
20
+
@@ -1,3 +1,3 @@
1
1
  module Pipely
2
- VERSION = "0.0.1" unless defined?(::DataPipelineGraphviz::VERSION)
2
+ VERSION = "0.1.0" unless defined?(::DataPipelineGraphviz::VERSION)
3
3
  end
@@ -24,13 +24,14 @@ describe Pipely do
24
24
  described_class.draw(definition_json, filename)
25
25
  end
26
26
 
27
- context 'with node_attributes' do
28
- let(:node_attributes) { stub }
27
+ context 'with component_attributes' do
28
+ let(:component_attributes) { stub }
29
29
 
30
- it 'applies the node_attributes to the definition' do
31
- definition.should_receive(:apply_node_attributes).with(node_attributes)
30
+ it 'applies the component_attributes to the definition' do
31
+ definition.should_receive(:apply_component_attributes).
32
+ with(component_attributes)
32
33
 
33
- described_class.draw(definition_json, filename, node_attributes)
34
+ described_class.draw(definition_json, filename, component_attributes)
34
35
  end
35
36
  end
36
37
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pipely
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-10-02 00:00:00.000000000 Z
12
+ date: 2013-11-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ruby-graphviz
16
- requirement: &70104078435520 !ruby/object:Gem::Requirement
16
+ requirement: &70261464770320 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70104078435520
24
+ version_requirements: *70261464770320
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rake
27
- requirement: &70104078435100 !ruby/object:Gem::Requirement
27
+ requirement: &70261464769340 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,21 +32,32 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70104078435100
35
+ version_requirements: *70261464769340
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: virtus
38
- requirement: &70104078434680 !ruby/object:Gem::Requirement
38
+ requirement: &70261464784920 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
- - - ! '>='
41
+ - - ~>
42
42
  - !ruby/object:Gem::Version
43
- version: '0'
43
+ version: 1.0.0
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70261464784920
47
+ - !ruby/object:Gem::Dependency
48
+ name: fog
49
+ requirement: &70261464783540 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 1.18.0
44
55
  type: :runtime
45
56
  prerelease: false
46
- version_requirements: *70104078434680
57
+ version_requirements: *70261464783540
47
58
  - !ruby/object:Gem::Dependency
48
59
  name: rspec
49
- requirement: &70104078434260 !ruby/object:Gem::Requirement
60
+ requirement: &70261464782460 !ruby/object:Gem::Requirement
50
61
  none: false
51
62
  requirements:
52
63
  - - ! '>='
@@ -54,10 +65,10 @@ dependencies:
54
65
  version: '0'
55
66
  type: :development
56
67
  prerelease: false
57
- version_requirements: *70104078434260
68
+ version_requirements: *70261464782460
58
69
  - !ruby/object:Gem::Dependency
59
70
  name: cane
60
- requirement: &70104078433840 !ruby/object:Gem::Requirement
71
+ requirement: &70261464781200 !ruby/object:Gem::Requirement
61
72
  none: false
62
73
  requirements:
63
74
  - - ! '>='
@@ -65,7 +76,7 @@ dependencies:
65
76
  version: '0'
66
77
  type: :development
67
78
  prerelease: false
68
- version_requirements: *70104078433840
79
+ version_requirements: *70261464781200
69
80
  description:
70
81
  email:
71
82
  - matt@swipely.com
@@ -77,8 +88,11 @@ files:
77
88
  - lib/pipely/component.rb
78
89
  - lib/pipely/definition.rb
79
90
  - lib/pipely/dependency.rb
91
+ - lib/pipely/fog_client.rb
80
92
  - lib/pipely/graph_builder.rb
93
+ - lib/pipely/live_pipeline.rb
81
94
  - lib/pipely/reference_list.rb
95
+ - lib/pipely/runs_report.rb
82
96
  - lib/pipely/version.rb
83
97
  - lib/pipely.rb
84
98
  - Rakefile
@@ -92,7 +106,8 @@ files:
92
106
  - !binary |-
93
107
  YmluL3BpcGVseQ==
94
108
  homepage: http://github.com/swipely/pipely
95
- licenses: []
109
+ licenses:
110
+ - MIT
96
111
  post_install_message:
97
112
  rdoc_options: []
98
113
  require_paths:
@@ -103,12 +118,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
103
118
  - - ! '>='
104
119
  - !ruby/object:Gem::Version
105
120
  version: '0'
121
+ segments:
122
+ - 0
123
+ hash: -4454073296924687218
106
124
  required_rubygems_version: !ruby/object:Gem::Requirement
107
125
  none: false
108
126
  requirements:
109
127
  - - ! '>='
110
128
  - !ruby/object:Gem::Version
111
129
  version: '0'
130
+ segments:
131
+ - 0
132
+ hash: -4454073296924687218
112
133
  requirements: []
113
134
  rubyforge_project:
114
135
  rubygems_version: 1.8.11
@@ -122,4 +143,3 @@ test_files:
122
143
  - spec/lib/pipely/graph_builder_spec.rb
123
144
  - spec/lib/pipely/reference_list_spec.rb
124
145
  - spec/lib/pipely_spec.rb
125
- has_rdoc: