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 +1 -1
- data/README.md +104 -12
- data/grandprix.gemspec +2 -0
- data/lib/grandprix.rb +32 -0
- data/lib/grandprix/graph.rb +3 -1
- data/spec/lib/grandprix/elements_spec.rb +8 -4
- data/spec/lib/grandprix/planner_spec.rb +3 -3
- data/spec/lib/grandprix_extensions_spec.rb +28 -0
- data/spec/spec_helper.rb +15 -0
- data/version +1 -1
- metadata +70 -23
data/.rvmrc
CHANGED
@@ -1 +1 @@
|
|
1
|
-
rvm use 1.
|
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
|
-
$
|
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
|
-
##
|
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
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
data/grandprix.gemspec
CHANGED
data/lib/grandprix.rb
CHANGED
@@ -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
|
data/lib/grandprix/graph.rb
CHANGED
@@ -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"
|
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"
|
40
|
-
res.strings.should
|
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 ==
|
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
|
data/spec/spec_helper.rb
CHANGED
@@ -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.
|
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
|
-
|
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
|
-
|
13
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
74
|
-
|
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
|
-
|
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.
|
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
|