Module: QuiversTaskrunner

Defined in:
lib/quiverstaskrunner/version.rb,
lib/quiverstaskrunner/azuresetup.rb,
lib/quiverstaskrunner/replacetokens.rb,
lib/quiverstaskrunner/restorepoints.rb,
lib/quiverstaskrunner/helpers/clihelper.rb,
lib/quiverstaskrunner/helpers/envhelper.rb,
lib/quiverstaskrunner/helpers/xmlhelper.rb,
lib/quiverstaskrunner/helpers/rakehelper.rb,
lib/quiverstaskrunner/helpers/settingshelper.rb

Defined Under Namespace

Modules: CliHelper, EnvHelper, RakeHelper, SettingsHelper, XmlHelper

Constant Summary collapse

VERSION =
"0.1.2492472"
@@get_settings_options =
{ :cli_params_black_list => ["env", "nos", "no-swap", "nod", "no-staging-del" ] }
@@secrets =
{
	:cloud_services => "secrets/cloud_services.yml",
	:storage => "secrets/storage.yml",
	:certs => "secrets/certs.yml",
	:global => "secrets/global.yml"
}
@@allowed_secret_types =
{ 
	"cloud_services" => :cloud_services, 
	"storage" => :storage, 
	"certs" => :certs, 
	"global" => :global,
	"all" => :all
}

Class Method Summary collapse

Class Method Details

.configure_subscription(env = nil) ⇒ Object

Raises:

  • (StandardError)


131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/quiverstaskrunner/azuresetup.rb', line 131

def configure_subscription(env=nil)

	# 1. Load environment variables
	env = env || EnvHelper.get_env("env")
	pfx_path = EnvHelper.get_env("azure_management_certificate")
	subscription_id = EnvHelper.get_env("azure_subscription_id")

	# 2. Load secrets
	storage = SettingsHelper.get_settings(File.join(Dir.pwd, @@secrets[:storage]), env, @@get_settings_options)
	storage_details = storage["blob"]
	raise StandardError, "No details were found for blob storage for the #{env} environment" unless storage_details

	Azure.configure do |config|
	    config. = storage_details["name"]
	    config.storage_access_key   = storage_details["key"]
		config.management_certificate = pfx_path
	    config.subscription_id        = subscription_id
	    config.management_endpoint    = "https://management.core.windows.net"
	end
end

.create_azure_files_restore_points(files_to_backup) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/quiverstaskrunner/restorepoints.rb', line 7

def create_azure_files_restore_points(files_to_backup)

	# convert single file into an array
	files = files_to_backup.is_a?(Array) ? files_to_backup : (files_to_backup.nil? ? [] : [files_to_backup])

	files.each do |f|
		f_ext = File.extname(f)
		f_backup_ext = ".backup#{f_ext}"
		f_backup = f.gsub(f_ext, f_backup_ext)
		# 1. Delete previous backup files if there are still present
		FileUtils.rm_f(f_backup) if FileTest.exists? f_backup
		# 2. Create backup files
		FileUtils.cp f, f_backup
	end
end

.delete_staging(cloud_service_name, env = nil) ⇒ Object

Raises:

  • (StandardError)


338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'lib/quiverstaskrunner/azuresetup.rb', line 338

def delete_staging(cloud_service_name, env=nil)

	# 1. Load cloud service's details
	env = env || EnvHelper.get_env("env")
	cloud_services = SettingsHelper.get_settings(File.join(Dir.pwd, @@secrets[:cloud_services]), env, @@get_settings_options)
	cloud_service_details = cloud_services[cloud_service_name]
	raise StandardError, "No details were found for cloud service #{cloud_service_name}" unless cloud_service_details
	cloud_srv_name = cloud_service_details["name"]

	# 2. Check if there is a Staging deployment for that Cloud Service
	cloud_srv_mgmt = Azure::CloudServiceManagementService.new
	deployment = cloud_srv_mgmt.get_deployment(cloud_srv_name, {:slot => "staging", :no_exit_on_failure => true})

	# 3. Delete the Staging deployment if it exists
	start_time = Time.now
	if deployment.exists?
		puts "Deleting staging deployment with id #{deployment.private_id} for cloud service #{cloud_srv_name}"
		cloud_srv_mgmt.delete_cloud_service_deployment(cloud_srv_name, "staging")
		ellapsed_time_min = (Time.now - start_time)/60.to_f
		puts "Deleting staging deployment #{deployment.private_id} for cloud service #{cloud_srv_name} done. Operation took #{ellapsed_time_min} minutes to complete".colorize(:green)
	else
		puts "No staging deployment detected for cloud service '#{cloud_srv_name}'".colorize(:magenta)
	end
end

.deploy_blob_pkg_to_staging(cloud_service_name, blob_pkg_name, pkg_dir, labels, options = {}) ⇒ Object

Raises:

  • (StandardError)


197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/quiverstaskrunner/azuresetup.rb', line 197

def deploy_blob_pkg_to_staging(cloud_service_name, blob_pkg_name, pkg_dir, labels, options={})
	# 1. Load cloud service's details
	env = options[:env] || EnvHelper.get_env("env")
	timeout = options[:timeout] || 20*60 # 20 minutes
	update_message_on = is_update_message_on
	cloud_services = SettingsHelper.get_settings(File.join(Dir.pwd, @@secrets[:cloud_services]), env, @@get_settings_options)
	cloud_service_details = cloud_services[cloud_service_name]
	raise StandardError, "No details were found for cloud service #{cloud_service_name}" unless cloud_service_details
	# 2. Load storage details
	storage = SettingsHelper.get_settings(File.join(Dir.pwd, @@secrets[:storage]), env, @@get_settings_options)
	storage_details = storage["blob"]
	raise StandardError, "No details were found for blob storage for the #{env} environment" unless storage_details

	# 3. Configure the deployment details
	deployment_name = SecureRandom.uuid
	cloud_srv_name = cloud_service_details["name"]
	package_url = ENV["package_url"]
	if package_url.nil?
		if blob_pkg_name.nil? || blob_pkg_name.empty?
			raise ArgumentError, "Missing argument. No 'blob_pkg_name' or 'package_url' defined."
		else
			package_url = "https://#{storage_details['name']}.blob.core.windows.net/mydeployments/#{blob_pkg_name}"
		end
	end
	# This way of getting the service configuration file may seem weird(indeed, why not
	# using the standard File.open method?), but for some reasons that I don't understand,
	# File.open will result in a 'Bad Request : The specified configuration settings for Configuration 
	# are invalid.' error
	service_configuration = XmlHelper.get_xml(File.join(pkg_dir, "ServiceConfiguration.#{env.capitalize}.cscfg")).to_xml

	deployment_options = {
		:slot => "staging",
		:label => labels,
		:start_deployment => true,
		:fire_and_forget => true,
		:upgrade_if_exists => true
	}

	# 4. Asynchronously deploy package to staging
	start_time = Time.now
	init_msg = nil
	cloud_srv_mgmt = Azure::CloudServiceManagementService.new
	no_error = true
	begin
		puts "Start deploying package from blob storage #{package_url} to cloud service"
		resp = cloud_srv_mgmt.create_deployment(deployment_name, cloud_srv_name, package_url, service_configuration, deployment_options)
		init_msg = "successfully initiated"
	rescue Exception => ex
		no_error = false
		init_msg = "failed"
		raise ex
	ensure
		ellapsed_time_min = (Time.now - start_time)/60.to_f
		puts "Deployment #{deployment_name} #{init_msg} after #{ellapsed_time_min} minutes"
	end

	# 5. Wait until the deployment is done
	deployment_pending = true
	waiting_start_time = Time.now
	timeout = timeout
	counter = 1
	waiting_symbols = ["=", "+"]
	deployment_id = nil
	while deployment_pending do
		waiting_characters = Array.new(counter, waiting_symbols[0]).join
		if update_message_on
			print "\r"
			print "Waiting for confirmation of running instances #{waiting_characters}"
		end
		deployment = cloud_srv_mgmt.get_deployment(cloud_srv_name, {:slot => "staging" })
		raise StandardError, "#{timeout} seconds timeout. Client unable to confirm operation success" if (Time.now - waiting_start_time) > timeout
		sleep 10
		deployment_pending = !deployment.all_vms_running? || deployment.is_transitioning?
		deployment_id = deployment.private_id
		counter+=1
		if counter > 40
			counter = 1
			waiting_symbols.reverse! 
		end
	end
	print "\n"
	ellapsed_time_min = (Time.now - start_time)/60.to_f
	puts "Deployment #{deployment_name} with id #{deployment_id} running successfully. Deployment took #{ellapsed_time_min} minutes to complete".colorize(:green)
	return no_error
end

.is_update_message_onObject



37
38
39
40
41
42
43
44
45
# File 'lib/quiverstaskrunner/azuresetup.rb', line 37

def is_update_message_on
	update_message_on = true
	begin
		update_message_on = (EnvHelper.get_env("update_message_on") == "true")
	rescue
		update_message_on = true
	end
	return update_message_on
end

.list_secretsObject



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/quiverstaskrunner/azuresetup.rb', line 47

def list_secrets()
	if (defined? ARGV) && (!ARGV.detect { |x| x.index("secrets") == 0 })
		puts "usage: rake list secrets=<args> env=<args>".colorize(:yellow)
		puts 
		puts "This task lists the secrets key value pairs for a specific environment. The optional parameter 'secrets' allows to filter the results.".colorize(:yellow)
		puts "The 'secrets' option is used as follow:".colorize(:yellow)
		puts "     rake list secrets=cloud_services env=test".colorize(:yellow)
		puts "where 'cloud_services' represents all the key value pairs associated with Windows Azure Cloud Services.".colorize(:yellow)
		puts "It is possible to define multiple secrets type by using the '/' separator as follow:".colorize(:yellow)
		puts "     rake list secrets=cloud_services/storage env=test".colorize(:yellow)
		puts
		puts "Allowed secret types are:".colorize(:yellow)
		puts "     all              => List all secret types".colorize(:yellow)
		puts "     cloud_services   => List secrets about the Windows Azure Cloud Services".colorize(:yellow)
		puts "     storage          => List secrets about the Windows Azure Storage".colorize(:yellow)
		puts "     certs            => List all SSL certs currently set up for the consumer pages".colorize(:yellow)
		puts "     global           => List various common global settings used across all Quivers project".colorize(:yellow)
		puts 
		puts "Allowed environments are:".colorize(:yellow)
		puts "	   test".colorize(:yellow)
		puts "	   demo".colorize(:yellow)
		puts "	   prod".colorize(:yellow)
	else
		cli_hash = SettingsHelper.get_cli_settings()
		secrets_list = cli_hash["secrets"]
		white_list = 
			if (!secrets_list.nil?) && (!secrets_list.empty?) && (secrets_list != "all")
				white_list = secrets_list
					.split('/')
					.reject { |x| !@@allowed_secret_types.detect { |k,v| k == x }  }
					.map { |x| @@allowed_secret_types[x] }
			elsif secrets_list == "all"
				[ :all ]
			else
				nil
			end

		if (!white_list.nil?)
			env = env || EnvHelper.get_env("env")
			if white_list[0] != :all
				@@secrets
					.reject { |k,v| !white_list.detect { |x| k == x } }
					.each do |k,v| 
						puts "#{k}".colorize(:yellow)
						puts
						puts CliHelper.pretty_print(SettingsHelper.get_settings(File.join(Dir.pwd, v), env, @@get_settings_options)) 
					end
			else
				@@secrets
					.each do |k,v| 
						puts "#{k}".colorize(:yellow)
						puts
						puts CliHelper.pretty_print(SettingsHelper.get_settings(File.join(Dir.pwd, v), env, @@get_settings_options)) 
					end
			end
		else
			raise StandardError, "None of the provided secrets matches any allowed secret types. Run 'rake list -cs' to get the usage"
		end
	end
end

.replace_tokens(files_with_missing_tokens, tokens) ⇒ Object

Raises:

  • (ArgumentError)


4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/quiverstaskrunner/replacetokens.rb', line 4

def replace_tokens(files_with_missing_tokens, tokens)
	files = files_with_missing_tokens.is_a?(Array) ? files_with_missing_tokens : [files_with_missing_tokens]
	raise ArgumentError, "'tokens' must be a Hash" unless tokens.is_a?(Hash)

	files.each { |f_path|
		content = nil
		File.open(f_path, "rb") do |f|
			content = f.read
			tokens.each { |k,v|
				content = content.gsub("{{#{k}}}", v)
			}
		end

		File.open(f_path, "w") do |f|
			f.write(content)
		end
	}
end

.restore_and_clean_files(files_to_restore) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/quiverstaskrunner/restorepoints.rb', line 23

def restore_and_clean_files(files_to_restore)

	# convert single file into an array
	files = files_to_restore.is_a?(Array) ? files_to_restore : (files_to_restore.nil? ? [] : [files_to_restore])

	files.each do |f|
		f_ext = File.extname(f)
		f_backup_ext = ".backup#{f_ext}"
		f_backup = f.gsub(f_ext, f_backup_ext)
		# 1. Make sure there are restore files available
		raise StandardError, "Missing restore file #{f_backup}"  unless FileTest.exists? f_backup
		# 2. Delete the modified files
		FileUtils.rm_f(f)
		# 3. Restore modified files
		FileUtils.cp f_backup, f
		# 4. Delete backup files
		FileUtils.rm_f(f_backup)
	end
end

.scale_host(cloud_service_name, service_configuration, service_definition, env = nil) ⇒ Object

Raises:

  • (StandardError)


108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/quiverstaskrunner/azuresetup.rb', line 108

def scale_host(cloud_service_name, service_configuration, service_definition, env=nil)
	env = env || EnvHelper.get_env("env")

	# 1. Get cloud service's details
	cloud_services = SettingsHelper.get_settings(File.join(Dir.pwd, @@secrets[:cloud_services]), env, @@get_settings_options)
	cloud_service_details = cloud_services[cloud_service_name]
	raise StandardError, "No details were found for cloud service #{cloud_service_name}" unless cloud_service_details
	vmsize = cloud_service_details["vmsize"]
	vmnbr = cloud_service_details["vmnbr"]

	# 2. Load xml files
	service_config_xml = XmlHelper.get_xml(service_configuration)
	service_def_xml = XmlHelper.get_xml(service_definition)

	service_config_xml.update_node_at_css_selector('ServiceConfiguration Role Instances',
		{ :count => vmnbr })
	service_def_xml.update_node_at_css_selector('ServiceDefinition WebRole',
		{ :vmsize => vmsize })

	XmlHelper.overide_xml(service_configuration, service_config_xml)
	XmlHelper.overide_xml(service_definition, service_def_xml)
end

.swap(cloud_service_name, options = {}) ⇒ Object



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/quiverstaskrunner/azuresetup.rb', line 283

def swap(cloud_service_name, options={})

	# 1. Load cloud service's details
	env = options[:env] || EnvHelper.get_env("env")
	timeout = options[:timeout] || 20*60 # 20 minutes
	update_message_on = is_update_message_on
	cloud_services = SettingsHelper.get_settings(File.join(Dir.pwd, @@secrets[:cloud_services]), env, @@get_settings_options)
	cloud_service_details = cloud_services[cloud_service_name]
	cloud_srv_name = cloud_service_details["name"]

	# 2. Asynchronously swap deployments
	start_time = Time.now
	init_msg = nil
	cloud_srv_mgmt = Azure::CloudServiceManagementService.new
	no_error = true
	begin
		puts "Start swapping deployments for cloud service '#{cloud_srv_name}'"
		resp = cloud_srv_mgmt.swap_deployment(cloud_srv_name, {:fire_and_forget => true})
		init_msg = "successfully initiated"
	rescue Exception => ex
		no_error = false
		init_msg = "failed"
		raise ex
	ensure
		ellapsed_time_min = (Time.now - start_time)/60.to_f
		puts "Swapping deployments for cloud service '#{cloud_srv_name}' #{init_msg} after #{ellapsed_time_min} minutes"
	end

	# 3. Wait until the swapping is done
	deployment_pending = true
	waiting_start_time = Time.now
	counter = 1
	waiting_symbols = ["=", "+"]
	while deployment_pending do
		waiting_characters = Array.new(counter, waiting_symbols[0]).join
		if update_message_on
			print "\r"
			print "Waiting for confirmation of running instances #{waiting_characters}"
		end
		deployment = cloud_srv_mgmt.get_deployment(cloud_srv_name, {:slot => "production" })
		raise StandardError, "#{timeout} seconds timeout. Client unable to confirm operation success" if (Time.now - waiting_start_time) > timeout
		sleep 10
		deployment_pending = !deployment.all_vms_running? || deployment.is_transitioning?
		counter+=1
		if counter > 40
			counter = 1
			waiting_symbols.reverse! 
		end
	end
	print "\n"
	ellapsed_time_min = (Time.now - start_time)/60.to_f
	puts "Swapping deployments for cloud service '#{cloud_srv_name}' done. Swapping took #{ellapsed_time_min} minutes to complete".colorize(:green)
	return no_error
end

.upload_cspkg_to_blob(pkg_name, pkg_dir, options = {}) ⇒ Object



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/quiverstaskrunner/azuresetup.rb', line 152

def upload_cspkg_to_blob(pkg_name, pkg_dir, options={})
	# 1. Configure the blob object, and load the package inside it
	timeout = options[:timeout] || 20*60 # 20 minutes
	blob_srv = Azure::Blob::BlobService.new
	blob_name =  Time.now.utc.strftime("%Y%m%d_%H%M%S_#{pkg_name}")
	blob_container = "mydeployments"
	block_list = []
	counter = 1

	File.open(File.join(pkg_dir, pkg_name), "rb") do |f|
		f.each_chunk {|chunk|
			block_id = counter.to_s.rjust(5, '0')
			block_list << [block_id, :uncommitted]

			blob_options = {
				:content_md5 => Base64.strict_encode64(Digest::MD5.digest(chunk)),
				:content_type => "application/octet-stream",
				:timeout => timeout
			}
			
			md5 = blob_srv.create_blob_block(blob_container, blob_name, block_id, chunk, blob_options)
			counter += 1
		}
	end

	# 2. Upload the blob object to the blob storage
	start_time = Time.now
	uploaded_package_name = nil
	no_error = true
	begin
		puts "Start uploading package #{blob_name} to blob storage"
		#new_blob = blob_srv.create_block_blob(blob_container, blob_name, blob_content, blob_options)
		blob_srv.commit_blob_blocks(blob_container, blob_name, block_list)
		uploaded_package_name = blob_name
	rescue Exception => ex
		no_error = false
		raise ex
	ensure
		ellappsed_min = (Time.now - start_time)/60.to_f
		puts "Upload of package #{blob_name} to blob storage succeeded in #{ellappsed_min} minutes".colorize(:green)
	end

	return [uploaded_package_name, no_error]
end