grandprix 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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