grandprix 0.0.4 → 0.0.5

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/.rvmrc CHANGED
@@ -1 +1 @@
1
- rvm use 1.9.3@grandprix --create
1
+ rvm use 1.8.7@grandprix --create
data/README.md CHANGED
@@ -63,7 +63,7 @@ and run:
63
63
  ### Usage
64
64
  An executable called `grandprix` will be installed, and can be called like this:
65
65
 
66
- $ topology -t topology_file elements_file
66
+ $ grandprix -t topology_file elements_file
67
67
 
68
68
  The elements file argument can be omitted and grandprix will read them from
69
69
  the standard in.
@@ -92,21 +92,113 @@ The `doc/sample/as_a_library` directory has an example ruby script calling
92
92
  grandprix programatically.
93
93
 
94
94
 
95
- ## More
95
+ ## How it works
96
+
97
+ ### The Basics
98
+
99
+ The basic function of grandprix is to to reorder a list of elements according to
100
+ a separately specified happens-before relation, a _topology_. The rule is
101
+ simple: an element **A** will be before another element **B** in the output
102
+ whenever there is a requirement on the topology that **B** must come after
103
+ **A**. This requirement may be direct — **B** is in the `after` list for the
104
+ **A** node in the input topology file — or it may be indirect — **A**'s `after`
105
+ list contains an element that itself has **B** in its list, and so on. If you
106
+ know some graph theory you may have recognized the rule as a _topological sort_,
107
+ and indeed **grandprix** is nothing more than a little topsort engine.
108
+
109
+ The `doc/sample/simple` directory contains sample topology and input files, and
110
+ an example of grandprix's output.
96
111
 
97
112
  Grandprix offers a few more conveniences, illustrated on the `doc/sample`
98
113
  directories:
99
114
 
100
- * Elements can contain extra data that will carry over to the output, such as
101
- version numbers. Just append an equals sign and the info you want to each
102
- element. Check out the `elements_with_extra_info` directory.
103
- * The topology itself can define information that will propagate to the
104
- output, in the form of an `annotation` attribute. See the `annotated_topology`
105
- directory.
106
- * Besides declaring that an element needs to come after others, grandprix also
107
- allows for an element to specify that it must always be accompanied by
108
- another, via the `alongside` attribute. This will extend the provided elements
109
- input. See the `alongside_elements` and `alongside_elements_2` directories.
115
+ ### Extra input data
116
+ Elements can contain extra data that will carry over to the output, such as
117
+ version or environment information. Just append an equals sign and the information
118
+ you want to each element. For instance if you want to append version numbers, the
119
+ elements input file could be something like this:
120
+
121
+ ```
122
+ frontend=1.0.0
123
+ client=2.0.3
124
+ backend=4.0.0
125
+ ```
126
+
127
+ The output would be a permutation of the input lines according to the rules
128
+ given on the topology. This may be useful to integrate grandprix into your
129
+ scripting workflow.
130
+
131
+ Check out the `elements_with_extra_info` directory for example input and
132
+ outputs.
133
+
134
+ ### Topology annotations
135
+ Adding extra data to the input elements is great for information that changes
136
+ with each run, but it would be tedious to have to always append long-lasting
137
+ information about the elements for each input. To address this, grandprix
138
+ offers a way to annotate the elements on the topology file:
139
+
140
+ ```
141
+ frontend:
142
+ after: [backend, images]
143
+ annotation: company-frontend-script
144
+
145
+ images:
146
+ annotation:
147
+ recipe: image-server
148
+ script: install-images
149
+
150
+ backend:
151
+ after: [db]
152
+ annotation:
153
+ - This
154
+ - And that
155
+ ```
156
+
157
+ The above shows the types of annotations that can be added to each node: simple
158
+ strings, hashes, and arrays of items. These values will carry over to the
159
+ output for each `grandprix` run:
160
+
161
+ ```
162
+ images=2.0.3={"recipe":"image-server","script":"install-images"}
163
+ backend=4.0.0=["This","And that"]
164
+ frontend=1.0.0=company-frontend-script
165
+ ```
166
+
167
+ In the above outout it can be seen that annotations are output for each element
168
+ after a second equals sign. Strings are output straight through, arrays and
169
+ objects are converted to a JSON serialization.
170
+
171
+ Sample files are on the `doc/sample/annotated_topology` directory.
172
+
173
+ ### Alongside Elements
174
+ Another common occurrence is the need to specify that some elements must always
175
+ be accompanied by others. For instance, still on the deploy use case, sometimes
176
+ we want to say that a certain service — say, a front-end server — must always be
177
+ deployed alongside anoter — it could a static assets server in this example.
178
+
179
+ The relationship is specified as an `alongside` entry on a node's topology file:
180
+
181
+ ```
182
+ frontend:
183
+ after: [backend]
184
+ alongside: [assets, images] # Frontend is always accompanied
185
+ # by assets and images
186
+
187
+ backend:
188
+ alongside: [external_backend]
189
+
190
+ images:
191
+ after: [client] #images that is an alongside dep of frontend
192
+ # can declare itself as after client
193
+ ```
194
+
195
+ When an element that declares alongside elements is part of the input, the
196
+ alongside elements are output as well. They inherit their declaring node's order
197
+ restrictions, but they can declare their own order restrictions which are also
198
+ obeyed.
199
+
200
+ See the `doc/sample/alongside_elements` and `doc/sample/alongside_elements_2`
201
+ directories.
110
202
 
111
203
 
112
204
  ## Contributing
@@ -14,4 +14,6 @@ Gem::Specification.new do |gem|
14
14
  gem.name = "grandprix"
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = File.read("version").chomp
17
+ gem.add_dependency 'json'
18
+ gem.add_dependency 'activesupport'
17
19
  end
@@ -3,7 +3,39 @@ module Grandprix
3
3
  end
4
4
 
5
5
  require 'json'
6
+ require 'active_support'
6
7
  require 'grandprix/elements'
7
8
  require 'grandprix/graph'
8
9
  require 'grandprix/planner'
9
10
  require 'grandprix/runner'
11
+
12
+ if RUBY_VERSION =~ /^1\.8/
13
+ class Array
14
+ def flat_map
15
+ self.reduce([]) {|so_far, current| so_far + yield(current)}
16
+ end
17
+
18
+ def to_ordered_hash
19
+ ActiveSupport::OrderedHash[self]
20
+ end
21
+ end
22
+
23
+ class Hash
24
+ def flat_map
25
+ self.reduce([]) do |so_far, current|
26
+ key = current[0]
27
+ value = current[1]
28
+ so_far + yield(key, value)
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+
35
+ if RUBY_VERSION =~ /^((1\.9)|2)/
36
+ class Array
37
+ def to_ordered_hash
38
+ Hash[self]
39
+ end
40
+ end
41
+ end
@@ -58,7 +58,9 @@ class Grandprix::Graph
58
58
  end
59
59
 
60
60
  def zeroes
61
- @counts.select {|vertex,count| count == 0 }.keys
61
+ selected = @counts.select {|vertex,count| count == 0 }
62
+ hash_selected = Hash[selected]
63
+ hash_selected.keys
62
64
  end
63
65
 
64
66
  def zeroes_among(vertices)
@@ -31,13 +31,13 @@ describe Grandprix::Elements do
31
31
 
32
32
  describe :alongside do
33
33
  it "should store the extra names" do
34
- (make(["a","c"]).alongside "a" => ["aa"], "c" => ["cc"]).should == make(["a", "c", "aa", "cc"])
34
+ (make(["a","c"]).alongside [["a", ["aa"]], ["c", ["cc"]]]).should == make(["a", "c", "aa", "cc"])
35
35
  end
36
36
 
37
37
  it "should copy extra data from the stored names to the names defined alongside" do
38
38
  elements = make ["first=0.1", "second=0.2", "third"]
39
- res = elements.alongside "first" => ["one"], "second" => ["two"], "third" => ["three"]
40
- res.strings.should == ["first=0.1", "second=0.2", "third", "one=0.1", "two=0.2", "three"]
39
+ res = elements.alongside [["first", ["one"]], ["second", ["two"]], ["third", ["three"]]]
40
+ res.strings.should =~ ["first=0.1", "second=0.2", "third", "one=0.1", "two=0.2", "three"]
41
41
  end
42
42
 
43
43
  it "REGRESSION: it should not add unrelated names" do
@@ -59,7 +59,11 @@ describe Grandprix::Elements do
59
59
  it "should add extra object information to the appropriate names" do
60
60
  elements = make ["first=1"]
61
61
  res = elements.annotate "first" => {:a => "some string", "b" => ["other"]}
62
- res.strings.should == [%|first=1={"a":"some string","b":["other"]}|]
62
+ res.strings.size.should == 1
63
+
64
+ line = res.strings.first
65
+ line.should match %r|first=1={.*}|
66
+ JSON.parse(res.strings.first.split(/=/).last).should include({"a" => "some string", "b" => ["other"]})
63
67
  end
64
68
 
65
69
  it "should format properly when the element has no prior extra information but is annotated" do
@@ -19,7 +19,7 @@ describe Grandprix::Planner do
19
19
 
20
20
  elements = ["frontend", "db", "client"]
21
21
 
22
- graph.should_receive(:sort).with([
22
+ graph.should_receive(:sort).with(in_any_order [
23
23
  ["backend", "frontend"],
24
24
  ["db", "backend"],
25
25
  ["mq", "backend"],
@@ -117,7 +117,7 @@ describe Grandprix::Planner do
117
117
 
118
118
  elements = ["frontend", "db", "client"]
119
119
 
120
- graph.should_receive(:sort).with([
120
+ graph.should_receive(:sort).with(in_any_order [
121
121
  ["backend", "frontend"],
122
122
  ["db", "backend"],
123
123
  ["mq", "backend"],
@@ -144,7 +144,7 @@ describe Grandprix::Planner do
144
144
 
145
145
  elements = ["frontend=1.0.0", "backend=2.0.0", "db"]
146
146
 
147
- graph.should_receive(:sort).with([
147
+ graph.should_receive(:sort).with(in_any_order [
148
148
  ["backend", "frontend"],
149
149
  ["db", "backend"],
150
150
  ]).and_return(["db", "backend", "frontend"])
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ describe "Extension" do
5
+ describe "Array.flat_map" do
6
+ it { [1,2,3].flat_map{|x| [x,x]}.should == [1,1,2,2,3,3]}
7
+ it { [1,2,3,4].flat_map{|x| x % 2 == 0 ? [x] : [] }.should == [2,4]}
8
+ end
9
+
10
+ describe "Hash.flat_map" do
11
+ it do
12
+ h = {:a => 10, :b => 20}
13
+ res = h.flat_map do |k, v|
14
+ [k, v, v]
15
+ end
16
+ res.should == [:a, 10, 10, :b, 20, 20]
17
+ end
18
+
19
+ it do
20
+ h = [[:a,1], [:b,2], [:c,3], [:d,4]].to_ordered_hash
21
+
22
+ res = h.flat_map do |k, v|
23
+ v % 2 == 1 ? [k] : []
24
+ end
25
+ res.should == [:a, :c]
26
+ end
27
+ end
28
+ end
@@ -86,3 +86,18 @@ RSpec::Matchers.define :beOrderedHaving do |*initial_segment|
86
86
  end
87
87
  end
88
88
  end
89
+
90
+ class InAnyOrderArgumentMatcher
91
+ def initialize(expected)
92
+ @expected = expected
93
+ end
94
+
95
+ def ==(value)
96
+ (value.is_a?(Array) && @expected.is_a?(Array) &&
97
+ (@expected - value) == [] && (value - @expected) == [] )
98
+ end
99
+ end
100
+
101
+ def in_any_order(expected)
102
+ InAnyOrderArgumentMatcher.new(expected)
103
+ end
data/version CHANGED
@@ -1 +1 @@
1
- 0.0.4
1
+ 0.0.5
metadata CHANGED
@@ -1,24 +1,60 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: grandprix
3
- version: !ruby/object:Gem::Version
4
- version: 0.0.4
3
+ version: !ruby/object:Gem::Version
4
+ hash: 21
5
5
  prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 5
10
+ version: 0.0.5
6
11
  platform: ruby
7
- authors:
12
+ authors:
8
13
  - Rafael de F. Ferreira
9
14
  autorequire:
10
15
  bindir: bin
11
16
  cert_chain: []
12
- date: 2012-12-15 00:00:00.000000000 Z
13
- dependencies: []
17
+
18
+ date: 2013-02-12 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: json
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: activesupport
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :runtime
47
+ version_requirements: *id002
14
48
  description: Deploy assistant.
15
- email:
49
+ email:
16
50
  - public@rafaelferreira.net
17
- executables:
51
+ executables:
18
52
  - grandprix
19
53
  extensions: []
54
+
20
55
  extra_rdoc_files: []
21
- files:
56
+
57
+ files:
22
58
  - .gitignore
23
59
  - .rspec
24
60
  - .rvmrc
@@ -56,37 +92,48 @@ files:
56
92
  - spec/lib/grandprix/graph_spec.rb
57
93
  - spec/lib/grandprix/planner_spec.rb
58
94
  - spec/lib/grandprix/runner_spec.rb
95
+ - spec/lib/grandprix_extensions_spec.rb
59
96
  - spec/matchers_spec.rb
60
97
  - spec/spec_helper.rb
61
98
  - version
62
- homepage: ''
99
+ homepage: ""
63
100
  licenses: []
101
+
64
102
  post_install_message:
65
103
  rdoc_options: []
66
- require_paths:
104
+
105
+ require_paths:
67
106
  - lib
68
- required_ruby_version: !ruby/object:Gem::Requirement
107
+ required_ruby_version: !ruby/object:Gem::Requirement
69
108
  none: false
70
- requirements:
71
- - - ! '>='
72
- - !ruby/object:Gem::Version
73
- version: '0'
74
- required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ hash: 3
113
+ segments:
114
+ - 0
115
+ version: "0"
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
117
  none: false
76
- requirements:
77
- - - ! '>='
78
- - !ruby/object:Gem::Version
79
- version: '0'
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ hash: 3
122
+ segments:
123
+ - 0
124
+ version: "0"
80
125
  requirements: []
126
+
81
127
  rubyforge_project:
82
- rubygems_version: 1.8.24
128
+ rubygems_version: 1.8.25
83
129
  signing_key:
84
130
  specification_version: 3
85
131
  summary: Deploy assistant
86
- test_files:
132
+ test_files:
87
133
  - spec/lib/grandprix/elements_spec.rb
88
134
  - spec/lib/grandprix/graph_spec.rb
89
135
  - spec/lib/grandprix/planner_spec.rb
90
136
  - spec/lib/grandprix/runner_spec.rb
137
+ - spec/lib/grandprix_extensions_spec.rb
91
138
  - spec/matchers_spec.rb
92
139
  - spec/spec_helper.rb