kiel 0.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/lib/kiel.rb +249 -0
- data/lib/kiel/cloud/aws.rb +163 -0
- data/lib/kiel/cloud/mock.rb +76 -0
- data/lib/kiel/scm/git.rb +21 -0
- data/lib/kiel/scm/mock.rb +24 -0
- data/lib/kiel/setup/capistrano.rb +46 -0
- data/lib/kiel/setup/capistrano_executer.rb +42 -0
- data/lib/kiel/setup/mock.rb +14 -0
- metadata +54 -0
data/lib/kiel.rb
ADDED
@@ -0,0 +1,249 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'kiel/scm/git'
|
3
|
+
require 'kiel/cloud/aws'
|
4
|
+
|
5
|
+
# Kiel tries to make the task to create cloud images easier by braking the whole installation into smaller, reproducible tasks.
|
6
|
+
# Each step is versioned by a version control system like git or subversion. Each installation step is described by a file
|
7
|
+
# containing a capistrano script. Kiel assumes that there is a specific order in which the steps have to be executed.
|
8
|
+
#
|
9
|
+
# The purpose of splitting the installation of a machine image into smaller tasks is to save time when debugging the
|
10
|
+
# installation and save time, when little changes have to be made to the installation.
|
11
|
+
#
|
12
|
+
# If one step fails, all subsequent installation steps might fail too. But when one step succeeds, that step can be
|
13
|
+
# used as base for all subsequence steps.
|
14
|
+
#
|
15
|
+
# License:: Distributes under the MIT license
|
16
|
+
|
17
|
+
module Kiel
|
18
|
+
#--
|
19
|
+
RECOGNIZED_STEP_OPTIONS = [ :name, :task, :scm_name, :setup_name, :description ]
|
20
|
+
DEFAULT_OPTIONS = {}
|
21
|
+
RECOGNIZED_OPTIONS = [ :scm, :cloud, :setup, :base_image, :root_dir ]
|
22
|
+
|
23
|
+
class Implementation
|
24
|
+
def initialize defaults
|
25
|
+
@defaults = defaults.dup
|
26
|
+
end
|
27
|
+
|
28
|
+
# this getters defer the construction of expensive devices to the latest moment possible
|
29
|
+
def scm
|
30
|
+
@defaults[ :scm ] ||= SCM::Git.new
|
31
|
+
end
|
32
|
+
|
33
|
+
def cloud
|
34
|
+
@defaults[ :cloud ] ||= Cloud::AWS.new
|
35
|
+
end
|
36
|
+
|
37
|
+
def setup
|
38
|
+
@defaults[ :setup ] ||= Setup::Capistrano.new
|
39
|
+
end
|
40
|
+
|
41
|
+
def expand_path file_name
|
42
|
+
@defaults[ :root_dir ] ||= Dir.pwd
|
43
|
+
|
44
|
+
if file_name.kind_of? Array
|
45
|
+
file_name.collect { |f| File.expand_path f, @defaults[ :root_dir ] }
|
46
|
+
else
|
47
|
+
File.expand_path file_name, @defaults[ :root_dir ]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def build_tags step, steps
|
52
|
+
steps.inject( { 'image_type' => step[ :name ].to_s, step[ :name ].to_s => step[ :version ].to_s } ) do | t, s |
|
53
|
+
t.merge s[ :name ].to_s => s[ :version ].to_s
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def initial_image_id step, base_steps
|
58
|
+
if base_steps.empty?
|
59
|
+
unless @defaults.key? :base_image
|
60
|
+
raise ArgumentError, "no :base_image given. Need to know what the base image of the very first image to produce should be"
|
61
|
+
end
|
62
|
+
{ :id => @defaults[ :base_image ] }
|
63
|
+
else
|
64
|
+
step, *steps = *base_steps
|
65
|
+
{ :tags => build_tags( step, steps ) }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_task step, steps
|
70
|
+
task = Rake::Task::define_task( step[ :task ] => steps.collect{ | s | s[ :task ] } ) do | task, arguments |
|
71
|
+
tags = build_tags step, steps
|
72
|
+
|
73
|
+
if cloud.exists? tags
|
74
|
+
puts "image \'#{step[ :name ]}\' already up to date and exists:"
|
75
|
+
tags.each{ | key, value | puts "\'#{key}\' => \'#{value}\'" }
|
76
|
+
else
|
77
|
+
puts "starting instance for: \'#{step[ :name ]}\'"
|
78
|
+
instance = cloud.start_instance initial_image_id( step, steps )
|
79
|
+
puts "instance for: \'#{step[ :name ]}\' started."
|
80
|
+
|
81
|
+
begin
|
82
|
+
dns_name = cloud.dns_name instance
|
83
|
+
expand_step = step.dup.merge( setup_name: expand_path( step[ :setup_name ] ) )
|
84
|
+
|
85
|
+
puts "excuting installation for: \'#{step[ :name ]}\'"
|
86
|
+
setup.execute expand_step, dns_name
|
87
|
+
puts "installation for: \'#{step[ :name ]}\' done."
|
88
|
+
|
89
|
+
puts "storing image for: \'#{step[ :name ]}\'"
|
90
|
+
cloud.store_image instance, tags
|
91
|
+
puts "image for: \'#{step[ :name ]}\' stored"
|
92
|
+
rescue
|
93
|
+
cloud.stop_instance instance
|
94
|
+
raise
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
task.add_description( step[ :description ] ) if step.key? :description
|
100
|
+
task
|
101
|
+
end
|
102
|
+
|
103
|
+
def add_versions steps
|
104
|
+
steps.collect() do | step |
|
105
|
+
name = step[ :scm_name ] == '*' ? '*' : expand_path( step[ :scm_name ] )
|
106
|
+
step.merge( version: scm.version( name ) )
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
private :setup, :cloud, :scm, :initial_image_id, :build_tags
|
111
|
+
end
|
112
|
+
|
113
|
+
# checks that all keys in options are valid
|
114
|
+
def self.check_options options
|
115
|
+
options.each_key{ | key |
|
116
|
+
raise ArgumentError, "Unrecognized option: \'#{key}\'" unless RECOGNIZED_OPTIONS.include? key
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.expand_steps steps
|
121
|
+
raise ArgumentError, "no steps given" if steps.empty?
|
122
|
+
|
123
|
+
# convert all steps to hashes and check the given parameters
|
124
|
+
steps = steps.collect do | s; step |
|
125
|
+
if s.respond_to? :to_hash
|
126
|
+
step = s.to_hash.dup
|
127
|
+
step.each { | key, value |
|
128
|
+
raise ArgumentError, "unrecognized step option: \'#{key}\'" unless RECOGNIZED_STEP_OPTIONS.include? key
|
129
|
+
}
|
130
|
+
|
131
|
+
raise ArgumentError, "every step have to have at least a name" unless step.key? :name
|
132
|
+
|
133
|
+
step
|
134
|
+
elsif s.respond_to? :to_s
|
135
|
+
{ :name => s.to_s }
|
136
|
+
else
|
137
|
+
raise ArgumentError, "a step have to be a string, symbol or a hash"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
steps.first[ :scm_name ] = '*' unless steps.first.key? :scm_name
|
142
|
+
|
143
|
+
# merge in defaults
|
144
|
+
steps = steps.collect do | step |
|
145
|
+
{
|
146
|
+
:task => step[ :name ],
|
147
|
+
:scm_name => "#{step[ :name ]}.rb"
|
148
|
+
}.merge step
|
149
|
+
end.collect do | step |
|
150
|
+
{
|
151
|
+
:setup_name => step[ :scm_name ] == '*' ? "#{step[ :name ]}.rb" : step[ :scm_name ]
|
152
|
+
}.merge step
|
153
|
+
end
|
154
|
+
|
155
|
+
steps
|
156
|
+
end
|
157
|
+
|
158
|
+
# defines the +steps+ necessary to build an image by constructing Rake::Tasks that depend on each other. The
|
159
|
+
# dependencies are defined in the order the steps are given. Every step depends on all other steps following
|
160
|
+
# that step in the list of given +steps+.
|
161
|
+
#
|
162
|
+
# Each step produces a new machine image, by starting a server in the cloud with the previous image,
|
163
|
+
# adding a defined set of installation instructions and than saving the resulting image for the next step.
|
164
|
+
#
|
165
|
+
# Every step is defined by hash with the following keys:
|
166
|
+
#
|
167
|
+
# :name:: The name of the step. The name is used to name a tag in the resulting image. The value of the tag is
|
168
|
+
# the source code version of the sources of that step. By default +name+ is expanded to +name.rb+ in the
|
169
|
+
# current directory.
|
170
|
+
#
|
171
|
+
# :task:: The name of the Rake::Task to be created for that step. If not given, the +name+ is used.
|
172
|
+
#
|
173
|
+
# :scm_name:: The name that is passed to the source code management to determine the version of the description
|
174
|
+
# of the step. If not given, +name+ is expanded to +name.rb+ in the current directory. For the first
|
175
|
+
# element this defaults to '*', which is a special notation for the latest version of the repository.
|
176
|
+
#
|
177
|
+
# :setup_name:: The name of the script to be executed. This defaults to :scm_name if given and not '*' or to
|
178
|
+
# :name + '.rb'
|
179
|
+
#
|
180
|
+
# :description:: Optional description for the task to be created.
|
181
|
+
#
|
182
|
+
# A step can be given by just one string or symbol, both following lines will result in the same images created.
|
183
|
+
# Kiel::image [ :stepA, :stepB ]
|
184
|
+
# Kiel::image [ { :name => 'stepA', :task => 'stepA', :scm_name => '*', :setup_name ='stepA.rb' },
|
185
|
+
# { :name => 'stepB', :task => 'stepB', :scm_name => 'stepB.rb' } ]
|
186
|
+
#
|
187
|
+
# +options+ is a set of configurations that can be used to override global options set by Kiel::set_defaults.
|
188
|
+
# Possible options are:
|
189
|
+
#
|
190
|
+
# :scm:: An instance of the +source code management+ used to retrieve version informations. By default this will
|
191
|
+
# be an instance of +Kiel::SCM::Git+.
|
192
|
+
#
|
193
|
+
# :setup:: An instance of the device used to execute steps to execute the installations steps. By default this will
|
194
|
+
# be an instance of +Kiel::Setup::Capistrano+.
|
195
|
+
#
|
196
|
+
# :cloud:: An instance of the cloud provider to lookup images and to access cloud instances. By default this will
|
197
|
+
# be an instance of +Kiel::Cloud::AWS+
|
198
|
+
#
|
199
|
+
# :base_image:: A cloud image id that is used as base for the very first step. This is the right most argument in
|
200
|
+
# the list of +steps+.
|
201
|
+
#
|
202
|
+
# :root_dir:: Root directory, where all file names are bassed on. If the options is not given, the current directory is used
|
203
|
+
#
|
204
|
+
# Example:
|
205
|
+
# Kiel::image [ 'application', 'base' ], setup: Kiel::Setup::Capistrano.new, base_image: 'ami-6d555119'
|
206
|
+
#
|
207
|
+
# Will assume that every new version in the repository should lead to a new image based on an base image. The
|
208
|
+
# layout of the base image is defined by base.rb and the base images is only recreated when the version of base.rb
|
209
|
+
# changes. The base image is build by starting a cloud image with the id 'ami-6d555119'. To setup the base-image,
|
210
|
+
# base.rb is executed by a newly created Kiel::Setup::Capistrano instance. The resulting base image will be stored
|
211
|
+
# with the tags:
|
212
|
+
# { 'image_type' => 'base', 'base' => '<version of base.rb>' }.
|
213
|
+
#
|
214
|
+
# An application image is build by starting a cloud server with the base image and executing the steps provided by
|
215
|
+
# application.rb. The application image is then stored with the following tags:
|
216
|
+
# { 'iamge_type' => 'application', 'application' => '<version of the overall repository>, 'base' => '<version of base.rb>' }.
|
217
|
+
def self.image steps, options = {}
|
218
|
+
check_options( options )
|
219
|
+
|
220
|
+
implemenation = Implementation.new defaults().merge( options )
|
221
|
+
steps = expand_steps steps
|
222
|
+
steps = implemenation.add_versions steps
|
223
|
+
|
224
|
+
while !steps.empty? do
|
225
|
+
step, *steps = *steps
|
226
|
+
|
227
|
+
implemenation.create_task step.dup, steps.dup
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
private_class_method :expand_steps, :check_options
|
232
|
+
|
233
|
+
# set the global defaults that are applied to Kiel::image
|
234
|
+
def self.set_defaults defs
|
235
|
+
check_options defs
|
236
|
+
@@defaults ||= DEFAULT_OPTIONS.dup
|
237
|
+
@@defaults.merge! defs
|
238
|
+
end
|
239
|
+
|
240
|
+
def self.reset_defaults
|
241
|
+
@@defaults = nil
|
242
|
+
end
|
243
|
+
|
244
|
+
# returns the global defaults that are applied to every call to Kiel::image
|
245
|
+
def self.defaults
|
246
|
+
@@defaults ||= DEFAULT_OPTIONS.dup
|
247
|
+
@@defaults
|
248
|
+
end
|
249
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
module Kiel
|
4
|
+
module Cloud
|
5
|
+
INSTANCE_STARTUP_TIMEOUT = 120
|
6
|
+
RECOGNIZED_OPTIONS = [ :region, :credentials, :instance, :start_options ]
|
7
|
+
|
8
|
+
# Implements the connection to the Amazon Web Services (AWS). The current implementation works for one
|
9
|
+
# configured region. The default server is a small EC2 instance.
|
10
|
+
class AWS
|
11
|
+
# The contructor takes the following configuration options:
|
12
|
+
#
|
13
|
+
# :region:: A string naming the region to be used. If no region is given, the default region is used.
|
14
|
+
#
|
15
|
+
# :credentials:: A hash containing the fields 'access_key_id' and 'secret_access_key' with the credential
|
16
|
+
# information to your amazon account.
|
17
|
+
#
|
18
|
+
# :instance:: An instance of the AWS::EC2. If that instance is given, no +credentials:+ should be given.
|
19
|
+
# Kiel::Cloud::AWS will instead use this instance.
|
20
|
+
#
|
21
|
+
# :start_options:: Options that are applied to EC2::InstanceCollection.create (siehe aws-sdk for more
|
22
|
+
# details). The aws_tests.rb uses the :key_name and :security_groups options to set the
|
23
|
+
# name of the used ssh key and a security group, where ssh is enabled.
|
24
|
+
def initialize options = {}
|
25
|
+
require 'aws/ec2'
|
26
|
+
|
27
|
+
options.each_key do | key |
|
28
|
+
raise ArgumentError, "unrecognized option \'#{key}\'" unless RECOGNIZED_OPTIONS.include? key
|
29
|
+
end
|
30
|
+
|
31
|
+
@ec2 = options[ :instance ]
|
32
|
+
@start_options = options[ :start_options ] || {}
|
33
|
+
|
34
|
+
if @ec2
|
35
|
+
puts "\'credentials\' ignored as an instance was given too" if options.key? :credentials
|
36
|
+
else
|
37
|
+
::AWS.config( options[ :credentials ] )
|
38
|
+
|
39
|
+
@ec2 = ::AWS::EC2.new
|
40
|
+
@ec2 = @ec2.regions[ options[ :region ] ] if options.key? :region
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def all_images_by_tags tags
|
45
|
+
images = @ec2.images.with_owner('self').tagged( tags.first.first )
|
46
|
+
|
47
|
+
images = images.select do | image |
|
48
|
+
image_tags = image.tags.to_h
|
49
|
+
image_tags.merge( tags ) == image_tags
|
50
|
+
end
|
51
|
+
|
52
|
+
images
|
53
|
+
end
|
54
|
+
|
55
|
+
def image_by_tags tags
|
56
|
+
images = all_images_by_tags tags
|
57
|
+
|
58
|
+
raise "#{images.size} are tagged with the given tags: #{tags.inspect}" if images.size > 1
|
59
|
+
images.size == 1 ? images.first : nil
|
60
|
+
end
|
61
|
+
|
62
|
+
def wait_for_ec2 instance
|
63
|
+
puts "waiting for EC2 instance to start."
|
64
|
+
sleep_count = INSTANCE_STARTUP_TIMEOUT
|
65
|
+
while instance.status == :pending and sleep_count != 0 do
|
66
|
+
sleep 1
|
67
|
+
sleep_count = sleep_count - 1
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def wait_for_image image
|
72
|
+
image_state = :pending
|
73
|
+
while image_state == :pending do
|
74
|
+
begin
|
75
|
+
image_state = image.state
|
76
|
+
rescue => e
|
77
|
+
puts "err: #{e.inspect}"
|
78
|
+
end
|
79
|
+
|
80
|
+
sleep 1
|
81
|
+
STDOUT.write '.'
|
82
|
+
end
|
83
|
+
puts ''
|
84
|
+
end
|
85
|
+
|
86
|
+
private :image_by_tags, :wait_for_ec2, :wait_for_image
|
87
|
+
|
88
|
+
# starts a server instance in the cloud, returning a handle to that instance.
|
89
|
+
# the image is either named by an image id +:id => 'image_id'+ or by a set of tags that match for
|
90
|
+
# just one image +:tags => { 'image_type' => 'application', 'base' => '34' }+
|
91
|
+
def start_instance image_name
|
92
|
+
options = @start_options.merge( if image_name.key?( :id )
|
93
|
+
{ image_id: image_name[ :id ] }
|
94
|
+
else
|
95
|
+
tags = image_name[ :tags ]
|
96
|
+
image = image_by_tags tags
|
97
|
+
raise RuntimeError, "no image with tags: \'#{tags}\' found to start an instance" unless image
|
98
|
+
|
99
|
+
{ image_id: image.id }
|
100
|
+
end )
|
101
|
+
|
102
|
+
instance = @ec2.instances.create( options )
|
103
|
+
|
104
|
+
begin
|
105
|
+
wait_for_ec2 instance
|
106
|
+
puts "ec2 instance \'#{instance.dns_name}\' started."
|
107
|
+
rescue
|
108
|
+
instance.terminate
|
109
|
+
raise
|
110
|
+
end
|
111
|
+
|
112
|
+
instance
|
113
|
+
end
|
114
|
+
|
115
|
+
# store the given +instance+ and add the hash of +tags+ to the image.
|
116
|
+
def store_image instance, tags
|
117
|
+
begin
|
118
|
+
|
119
|
+
puts "waiting 2 minutes before starting to take the image..."
|
120
|
+
sleep 120
|
121
|
+
puts "creating images..."
|
122
|
+
|
123
|
+
image = @ec2.images.create(
|
124
|
+
:instance_id => instance.id,
|
125
|
+
:no_reboot => true,
|
126
|
+
:description => "automaticaly created #{tags[ 'image_type' ]} image",
|
127
|
+
:name => "#{tags[ 'image_type' ]} #{Digest::SHA1.hexdigest tags.inspect}" )
|
128
|
+
|
129
|
+
wait_for_image image
|
130
|
+
|
131
|
+
tags.each do | key, value |
|
132
|
+
image.add_tag( key, :value => value )
|
133
|
+
end
|
134
|
+
ensure
|
135
|
+
stop_instance instance
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# stops the given instance.
|
140
|
+
def stop_instance instance
|
141
|
+
begin
|
142
|
+
instance.terminate
|
143
|
+
rescue
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# returns true, if an image with the given tags exists
|
148
|
+
def exists? tags
|
149
|
+
raise ArgumentError, "AWS.exists? with empty tags" if tags.empty?
|
150
|
+
|
151
|
+
image_by_tags tags
|
152
|
+
end
|
153
|
+
|
154
|
+
def dns_name instance
|
155
|
+
instance.dns_name
|
156
|
+
end
|
157
|
+
|
158
|
+
# deletes the given image
|
159
|
+
def delete_image image_name
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
|
2
|
+
module Kiel
|
3
|
+
module Cloud
|
4
|
+
|
5
|
+
# Implementation of the Cloud access-Interface
|
6
|
+
# The implementation assumes that a cloud provider provides machine images, used to start machines,
|
7
|
+
# that this images can a not unique set of tags and a unique id. Where the id is provided by the cloud provider
|
8
|
+
# tags are provided by the user. Kiel used the tags to store version informations to an image and uses this
|
9
|
+
# set of tags to identify an image.
|
10
|
+
#
|
11
|
+
# The implementation assumes that a cloud provider allows to start a server with a machine image as parameter
|
12
|
+
# and that the resulting instance has a public dns_name to be reachable.
|
13
|
+
class Mock
|
14
|
+
# +existing_images+ simulates an initial set of existing machine images, +dns_names+ provides a set of
|
15
|
+
# names that are assigned to newly created cloud instances.
|
16
|
+
def initialize existing_images = [], dns_names = []
|
17
|
+
@names = dns_names.dup
|
18
|
+
@calls = []
|
19
|
+
@images = [ existing_images.dup ].flatten
|
20
|
+
@running = []
|
21
|
+
@next_instance = 0
|
22
|
+
end
|
23
|
+
|
24
|
+
# starts a server instance in the cloud, returning a handle to that instance.
|
25
|
+
# the image is either named by an image id +:id => 'image_id'+ or by a set of tags that match for
|
26
|
+
# just one image +:tags => { 'image_type' => 'application', 'base' => '34' }+
|
27
|
+
def start_instance image_name
|
28
|
+
raise ArgumentError, "image_name must contain exactly one identification" unless image_name.size == 1
|
29
|
+
|
30
|
+
@running << @next_instance
|
31
|
+
@next_instance += 1
|
32
|
+
@running.last
|
33
|
+
end
|
34
|
+
|
35
|
+
# store the given +instance+ under the given +image_name+ and add the hash of +tags+ to the image.
|
36
|
+
def store_image instance, tags
|
37
|
+
image = { id: instance, tags: tags }
|
38
|
+
@calls << { func: :store_image, args: image }
|
39
|
+
@images << image
|
40
|
+
stop_instance instance
|
41
|
+
end
|
42
|
+
|
43
|
+
# stops the given instance.
|
44
|
+
def stop_instance instance
|
45
|
+
unless @running.delete( instance )
|
46
|
+
raise RuntimeError, "there is no instance #{instance} running"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# returns true, if an image with the given tags exists
|
51
|
+
def exists? tags
|
52
|
+
@images.detect { | image | image[ :tags ] == tags }
|
53
|
+
end
|
54
|
+
|
55
|
+
# returns the dns name from an instance
|
56
|
+
def dns_name instance
|
57
|
+
"#{instance}"
|
58
|
+
end
|
59
|
+
|
60
|
+
#--
|
61
|
+
def calls
|
62
|
+
@calls
|
63
|
+
end
|
64
|
+
|
65
|
+
#--
|
66
|
+
def running_instances
|
67
|
+
@running
|
68
|
+
end
|
69
|
+
|
70
|
+
#--
|
71
|
+
def stored_images
|
72
|
+
@images
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/kiel/scm/git.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
module Kiel
|
4
|
+
module SCM
|
5
|
+
class Git
|
6
|
+
def single_version file
|
7
|
+
result = `git rev-list --max-count 1 HEAD #{file}`
|
8
|
+
result.gsub( /\n$/, '' )
|
9
|
+
end
|
10
|
+
|
11
|
+
private :single_version
|
12
|
+
|
13
|
+
def version file
|
14
|
+
files = [ file == '*' ? '' : file ].flatten
|
15
|
+
return single_version( files.first ) if files.size == 1
|
16
|
+
|
17
|
+
files.sort.inject( '' ) { | sum, file | Digest::SHA1.hexdigest sum + single_version(file) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
module Kiel
|
3
|
+
module SCM
|
4
|
+
|
5
|
+
# A mock, implementing the interface to a source code managment system
|
6
|
+
class Mock
|
7
|
+
def initialize versions = {}
|
8
|
+
@versions = versions
|
9
|
+
end
|
10
|
+
|
11
|
+
# returns the latest version of the given file
|
12
|
+
def version step
|
13
|
+
step = step.to_s
|
14
|
+
raise RuntimeError, "no mocked version for step \'#{step}\'" unless @versions.key? step
|
15
|
+
@versions[ step ]
|
16
|
+
end
|
17
|
+
|
18
|
+
#--
|
19
|
+
def versions hash
|
20
|
+
@versions = hash
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Kiel
|
4
|
+
module Setup
|
5
|
+
# The Capistrano Setup component executes one task out of a given script. The name of the script is by default
|
6
|
+
# the name of step + '.rb' in the +:root_dir+ given to Kiel::image. The task to be executed is 'deploy:step'.
|
7
|
+
# The tags that will be applied to the resulting image are passed to the script as global constant ::TAGS
|
8
|
+
class Capistrano
|
9
|
+
|
10
|
+
# +options+ are passed directly to Capistrano:
|
11
|
+
# options.each { | key, value |
|
12
|
+
# set key, value
|
13
|
+
# }
|
14
|
+
# so every options that have to be set in every script should be set by using this options.
|
15
|
+
def initialize options = {}
|
16
|
+
@options = options
|
17
|
+
end
|
18
|
+
|
19
|
+
# step contains the whole step informations at least :setup_name contains the script to be executed,
|
20
|
+
# :tags contains the tags that will be added to the images after setup, :version contains the version of the
|
21
|
+
# steps associated scm_name's file version.
|
22
|
+
def execute step, dns_server
|
23
|
+
options = { script: step[ :setup_name ], tags: step[ :tags ], version: step[ :version ],
|
24
|
+
name: step[ :name ].to_s, capistrano_options: @options, capistrano_server: dns_server }
|
25
|
+
|
26
|
+
file = IO.popen( ['cap', '-f', File.expand_path( '../capistrano_executer.rb', __FILE__ ), step[ :name ].to_s ], 'r+' )
|
27
|
+
|
28
|
+
file.write YAML::dump( options )
|
29
|
+
file.close_write
|
30
|
+
|
31
|
+
last_line = ''
|
32
|
+
begin
|
33
|
+
begin
|
34
|
+
text = file.readpartial 1024
|
35
|
+
STDOUT.write text
|
36
|
+
last_line += text
|
37
|
+
last_line = last_line.split("\n").last
|
38
|
+
end until text.empty?
|
39
|
+
rescue EOFError
|
40
|
+
end
|
41
|
+
|
42
|
+
raise "Error while executing #{step[ :setup_name ]}" if last_line =~ /\+-\+-\+-\+ERORR\+-\+-\+-\+/
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Kiel
|
4
|
+
OPTIONS = YAML.load STDIN
|
5
|
+
TAGS = OPTIONS[ :tags ]
|
6
|
+
end
|
7
|
+
|
8
|
+
Kiel::OPTIONS[ :capistrano_options ].each do | key, value |
|
9
|
+
set key, value
|
10
|
+
end
|
11
|
+
|
12
|
+
role :server, Kiel::OPTIONS[ :capistrano_server ]
|
13
|
+
|
14
|
+
load Kiel::OPTIONS[ :script ]
|
15
|
+
|
16
|
+
task Kiel::OPTIONS[ :name ] do
|
17
|
+
begin
|
18
|
+
retry_count = 10
|
19
|
+
|
20
|
+
begin
|
21
|
+
deploy.step
|
22
|
+
rescue Capistrano::ConnectionError => ex
|
23
|
+
raise unless ex.message =~ /Errno::ECONNREFUSED|Errno::ETIMEDOUT/
|
24
|
+
|
25
|
+
puts "Connection failed"
|
26
|
+
retry_count -= 1
|
27
|
+
|
28
|
+
if retry_count == 0
|
29
|
+
puts 'giving up...'
|
30
|
+
raise
|
31
|
+
end
|
32
|
+
|
33
|
+
puts 'retrying...'
|
34
|
+
sleep 15
|
35
|
+
retry
|
36
|
+
end
|
37
|
+
rescue Exception => ex
|
38
|
+
puts ex.message
|
39
|
+
puts ex.backtrace.join("\n")
|
40
|
+
puts '+-+-+-+ERORR+-+-+-+'
|
41
|
+
end
|
42
|
+
end
|
metadata
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kiel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.0'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Torsten Robitzki
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-30 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Helper to build rake task to build cloud images step by step for easier
|
15
|
+
debugging and testing
|
16
|
+
email: gemmaster@robitzki.de
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- lib/kiel/cloud/aws.rb
|
22
|
+
- lib/kiel/cloud/mock.rb
|
23
|
+
- lib/kiel/scm/git.rb
|
24
|
+
- lib/kiel/scm/mock.rb
|
25
|
+
- lib/kiel/setup/capistrano.rb
|
26
|
+
- lib/kiel/setup/capistrano_executer.rb
|
27
|
+
- lib/kiel/setup/mock.rb
|
28
|
+
- lib/kiel.rb
|
29
|
+
homepage: https://github.com/TorstenRobitzki/Kiel
|
30
|
+
licenses:
|
31
|
+
- MIT
|
32
|
+
post_install_message:
|
33
|
+
rdoc_options: []
|
34
|
+
require_paths:
|
35
|
+
- lib
|
36
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ! '>='
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
requirements: []
|
49
|
+
rubyforge_project:
|
50
|
+
rubygems_version: 1.8.24
|
51
|
+
signing_key:
|
52
|
+
specification_version: 3
|
53
|
+
summary: Building cloud images step by step
|
54
|
+
test_files: []
|