zzdeploy 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/LICENSE ADDED
@@ -0,0 +1 @@
1
+ Copyright (c) 2011 ZangZing, LLC
data/README.rdoc ADDED
@@ -0,0 +1,10 @@
1
+ = zz
2
+
3
+ Deploy tool for ZangZing and Amazon instances
4
+
5
+ == Installation
6
+ $ gem install zzdeploy
7
+
8
+ == Copyright
9
+
10
+ Copyright (c) 2011 ZangZing, LLC. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,25 @@
1
+ require 'spec/rake/spectask'
2
+ require 'rake/rdoctask'
3
+
4
+ $LOAD_PATH.unshift(File.expand_path('../lib', __FILE__))
5
+
6
+ Spec::Rake::SpecTask.new(:spec) do |spec|
7
+ spec.libs << 'lib' << 'spec'
8
+ spec.spec_files = FileList['spec/**/*_spec.rb']
9
+ end
10
+
11
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
12
+ spec.libs << 'lib' << 'spec'
13
+ spec.pattern = 'spec/**/*_spec.rb'
14
+ spec.rcov = true
15
+ end
16
+
17
+ Rake::RDocTask.new do |rdoc|
18
+ require 'zz_deploy'
19
+ rdoc.rdoc_dir = 'rdoc'
20
+ rdoc.title = "zzdeploy #{ZZDeploy::VERSION}"
21
+ rdoc.rdoc_files.include('README*')
22
+ rdoc.rdoc_files.include('lib/**/*.rb')
23
+ end
24
+
25
+ task :default => :spec
data/bin/zz ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # ./zz - ZZ deploy
4
+ #
5
+
6
+ require 'rubygems'
7
+ require "bundler/setup"
8
+ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
9
+ require 'zz_deploy'
10
+
11
+ exit_code = ZZDeploy.new.run
12
+ exit!(exit_code)
13
+
14
+
@@ -0,0 +1,187 @@
1
+ module Commands
2
+ class AddInstance
3
+
4
+ # holds the options that were passed
5
+ # you can set any initial defaults here
6
+ def options
7
+ @options ||= {
8
+ :instance_size => "c1.medium",
9
+ :start_app => false,
10
+ :availability_zone => "us-east-1c" # this is where our reserved instance currently are
11
+ }
12
+ end
13
+
14
+ # required options
15
+ def required_options
16
+ @required_options ||= Set.new [
17
+ :role,
18
+ :group,
19
+ ]
20
+ end
21
+
22
+ def register(opts, global_options)
23
+ opts.banner = "Usage: add [options]"
24
+ opts.description = "Add a server instance"
25
+
26
+ opts.on('-s', "--size instance_size", ["t1.micro", "m1.small", "c1.medium"], "The amazon instance size - currently we limit to 32 bit instances.") do |v|
27
+ options[:instance_size] = v
28
+ end
29
+
30
+ opts.on('-z', "--zone availability_zone", MetaOptions.availability_zones, "The amazon availability zone - currently we only support the east coast.") do |v|
31
+ options[:availability_zone] = v
32
+ end
33
+
34
+ opts.on('-r', "--role role", MetaOptions.roles, "Required - Role server will play.") do |v|
35
+ options[:role] = v
36
+ end
37
+
38
+ opts.on("--start_app", "Set if you want to deploy and start the app after instance is ready.") do |v|
39
+ options[:start_app] = v
40
+ end
41
+
42
+ opts.on('-e', "--extra extra", MetaOptions.roles, "Optional extra data associated with this instance.") do |v|
43
+ options[:extra] = v
44
+ end
45
+
46
+ opts.on('-g', "--group deploy_group", "Required - The deploy group we are in. A deploy group is a set of servers that are required to run the infrastructure for a server.") do |v|
47
+ options[:group] = v
48
+ end
49
+
50
+ opts.on('-p', "--print path", "The directory into which we output the data as a file per host.") do |v|
51
+ options[:result_path] = v
52
+ end
53
+ end
54
+
55
+
56
+ def run(global_options, amazon)
57
+ ec2 = amazon.ec2
58
+ utils = ZZSharedLib::Utils.new(amazon)
59
+
60
+ user_json = JSON.pretty_generate(options)
61
+
62
+ # deploy group
63
+ group_name = options[:group]
64
+ role = options[:role]
65
+ extra = options[:extra]
66
+ start_app = options[:start_app]
67
+
68
+ # first see if already exists
69
+ deploy_group = amazon.find_deploy_group(group_name)
70
+ recipes_deploy_tag = deploy_group.recipes_deploy_tag
71
+ group_config = deploy_group.config
72
+
73
+ availability_zone = options[:availability_zone] || group_config[:availability_zone]
74
+
75
+ # the security key
76
+ security_key = group_config[:amazon_security_key]
77
+
78
+ # the security group
79
+ security_group = group_config[:amazon_security_group]
80
+
81
+ # find ones matching the role
82
+ match = amazon.find_by_role(group_name, role)
83
+
84
+ # stop if we have a case where we only allow one of a particular kind
85
+ case role.to_sym
86
+ when :app_master, :db, :solo
87
+ if match.length > 0
88
+ raise "Argument error: You already have a duplicate role of #{role}. The existing instance is #{match[0]}."
89
+ end
90
+ end
91
+
92
+ # now find the proper AMI image to use
93
+ baseline_image = group_config[:amazon_image]
94
+
95
+ # first see if we have a specific image for this role
96
+ role_image = "#{baseline_image}_#{role}"
97
+ match_image = amazon.find_typed_resource("image", "Name", role_image)
98
+ if match_image.length > 1
99
+ raise "You must have only one AMI Image for #{role_image}. Found: #{match_image.length}"
100
+ end
101
+
102
+ if match_image.length != 1
103
+ # didn't have a specific role image so get the generic one
104
+ match_image = amazon.find_typed_resource("image", "Name", baseline_image)
105
+ if match_image.length != 1
106
+ raise "Need to have exactly one AMI Image for #{baseline_image}. Found: #{match_image.length}"
107
+ end
108
+ end
109
+
110
+
111
+ instances = ec2.run_instances(match_image[0], 1, 1, [security_group], security_key, user_json, nil, options[:instance_size],
112
+ nil, nil, availability_zone)
113
+ inst_id = instances[0][:aws_instance_id]
114
+ puts "Waiting for instance #{inst_id} to boot"
115
+ aws_state = ""
116
+ instance = nil
117
+ waits = 0
118
+ while aws_state != "running" do
119
+ print "."
120
+ STDOUT.flush
121
+ sleep(1)
122
+ waits += 1
123
+ # this silly bit of logic is needed because sometimes Amazons API does not know
124
+ # about the newly created instance for a brief period so don't ask till we give it
125
+ # a chance to learn about it
126
+ if waits >= 10
127
+ instance = ec2.describe_instances(inst_id)[0]
128
+ aws_state = instance[:aws_state]
129
+ end
130
+ end
131
+ puts
132
+ puts "Tagging instance."
133
+ ec2.create_tags(inst_id, {"Name" => "#{group_name}_#{role}_#{inst_id}", :group => group_name, :role => role, :extra => extra,
134
+ :state => 'booting', :deploy_app => ZZSharedLib::Utils::NEVER, :deploy_chef => ZZSharedLib::Utils::NEVER })
135
+
136
+ dns_name = instance[:dns_name]
137
+ ssh_cmd = "ssh -t -i ~/.ssh/#{group_config[:amazon_security_key]}.pem ec2-user@#{dns_name}"
138
+ puts ssh_cmd
139
+ test_cmd = 'echo "connected"'
140
+ test_cmd = "#{ssh_cmd} '#{test_cmd}'"
141
+ tries = 0
142
+ while true do
143
+ puts "testing ssh connection"
144
+ result = ZZSharedLib::CL.do_cmd_result test_cmd
145
+ break if result == 0
146
+ sleep(6)
147
+ tries += 1
148
+ if tries >= 10
149
+ # todo decide if we should terminate this instance
150
+ ec2.create_tags(inst_id, {:state => 'failed_boot' })
151
+ raise "Not able to establish ssh connection, make sure the security group has the ssh port open."
152
+ end
153
+ end
154
+
155
+ # if we get here we should have verified that the machine is ready and we can ssh into it, lets
156
+ # do the initial upload step for the chef recipes by fetching the proper tag on the remote machine
157
+ ec2.create_tags(inst_id, {:state => 'ready' })
158
+
159
+ git_cmd = ChefUpload.get_upload_command(recipes_deploy_tag)
160
+ remote_cmd = "#{ssh_cmd} \"#{git_cmd}\""
161
+ result = ZZSharedLib::CL.do_cmd_result remote_cmd
162
+ if result != 0
163
+ raise "The instance was created but we were unable to upload the chef recipes.\nYou should try again by using 'chef_upload' and make sure you have a valid git tag."
164
+ end
165
+
166
+ # set up our instance id
167
+ # first get all instances in our group which should include us
168
+ amazon.flush_tags # force a refresh of the cached tags
169
+ all_instances = amazon.find_and_sort_named_instances(group_name)
170
+
171
+ just_our_instance = all_instances.reject { |inst| inst[:resource_id] != inst_id }
172
+
173
+ # deploy the chef config
174
+ puts "Updating chef configuration for new instance."
175
+ BuildDeployConfig.do_config_deploy(utils, amazon, just_our_instance, group_name, deploy_group, options[:result_path])
176
+
177
+ # optionally deploy and start app
178
+ # note in this case we redeploy the whole group since
179
+ # there are dependencies between the instances
180
+ if start_app
181
+ puts "Now deploying all app instances since the configuration changed."
182
+ BuildDeployConfig.do_app_deploy(utils, amazon, all_instances, group_name, deploy_group, '', false, false, options[:result_path])
183
+ end
184
+
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,143 @@
1
+ module Commands
2
+
3
+ # this class builds up the contents of the json deploy file that we feed
4
+ # to the remote system to kickstart the chef run. It allows us to
5
+ # dynamically configure the options we want
6
+ class BuildDeployConfig
7
+
8
+ def self.build_json(zz_options, deploy_file)
9
+ chef_config = { :run_list => "recipe[deploy-manager]" }
10
+ zz_options[:dev_machine] = false
11
+ chef_config[:zz] = zz_options
12
+
13
+ json = JSON.pretty_generate(chef_config)
14
+
15
+ # now build up the command string to run
16
+ cmd =
17
+ "bash -l -c '(
18
+ cat <<'EOP'
19
+ #{json}
20
+ EOP
21
+ ) > /var/chef/#{deploy_file}.json
22
+ cd #{::ZZDeploy::RECIPES_DIR}
23
+ sudo bundle exec chef-solo -l debug -c chef-local/remote_solo.rb -j /var/chef/#{deploy_file}.json'"
24
+
25
+ return cmd
26
+ end
27
+
28
+ def self.remote_app_deploy(migrate_command, downtime)
29
+ zz = {
30
+ :deploy_what => "app",
31
+ :deploy_migrate_command => migrate_command,
32
+ :deploy_downtime => downtime
33
+ }
34
+ return build_json(zz, 'deploy_app')
35
+ end
36
+
37
+ def self.remote_app_restart(migrate_command, downtime)
38
+ zz = {
39
+ :deploy_what => "app_restart",
40
+ :deploy_migrate_command => migrate_command,
41
+ :deploy_downtime => downtime
42
+ }
43
+ return build_json(zz, 'deploy_app_restart')
44
+ end
45
+
46
+ def self.remote_app_maint(maint)
47
+ zz = {
48
+ :deploy_what => "app_maint",
49
+ :deploy_maint => maint
50
+ }
51
+ return build_json(zz, 'deploy_app_maint')
52
+ end
53
+
54
+ def self.remote_config_deploy
55
+ zz = {
56
+ :deploy_what => "config"
57
+ }
58
+ return build_json(zz, 'deploy_config')
59
+ end
60
+
61
+ def self.remote_shutdown
62
+ zz = {
63
+ :deploy_what => "shutdown"
64
+ }
65
+ return build_json(zz, 'deploy_shutdown')
66
+ end
67
+
68
+ def self.do_remote_shutdown(utils, amazon, instances, group_name, deploy_group, result_path)
69
+ begin
70
+ remote_cmd = remote_shutdown
71
+ multi = MultiSSH.new(amazon, group_name, deploy_group)
72
+ multi.run_instances(instances, remote_cmd)
73
+ rescue Exception => ex
74
+ # ignore any errors since we want to shut down regardless
75
+ ensure
76
+ multi.output_tracked_data_to_files("remote_shutdown", result_path) rescue nil
77
+ end
78
+ end
79
+
80
+ def self.do_config_deploy(utils, amazon, instances, group_name, deploy_group, result_path)
81
+ begin
82
+ utils.mark_deploy_state(instances, :deploy_chef, ZZSharedLib::Utils::START)
83
+ remote_cmd = remote_config_deploy
84
+ multi = MultiSSH.new(amazon, group_name, deploy_group)
85
+ multi.run_instances(instances, remote_cmd)
86
+ rescue Exception => ex
87
+ raise ex
88
+ ensure
89
+ multi.output_tracked_data_to_files("deploy_chef", result_path) rescue nil
90
+ # only mark ready if not in error state
91
+ utils.mark_deploy_state(instances, :deploy_chef, ZZSharedLib::Utils::READY, true)
92
+ end
93
+ end
94
+
95
+ def self.do_app_deploy(utils, amazon, instances, group_name, deploy_group, migrate_command, downtime, no_restart, result_path)
96
+ # ok, phase one is to do everything up to but not including the restart
97
+ begin
98
+ utils.mark_deploy_state(instances, :deploy_app, ZZSharedLib::Utils::START)
99
+ remote_cmd = remote_app_deploy(migrate_command, downtime)
100
+ multi = MultiSSH.new(amazon, group_name, deploy_group)
101
+ multi.run_instances(instances, remote_cmd)
102
+ rescue Exception => ex
103
+ raise ex
104
+ ensure
105
+ multi.output_tracked_data_to_files("app_deploy", result_path) rescue nil
106
+ end
107
+
108
+ # the prep is good and all servers are ready to restart
109
+ begin
110
+ if no_restart == false
111
+ puts "Restarting servers..."
112
+ utils.mark_deploy_state(instances, :deploy_app, ZZSharedLib::Utils::RESTARTING)
113
+ remote_cmd = remote_app_restart(migrate_command, downtime)
114
+ multi.run_instances(instances, remote_cmd)
115
+ end
116
+ rescue Exception => ex
117
+ raise ex
118
+ ensure
119
+ multi.output_tracked_data_to_files("app_restart", result_path) rescue nil
120
+ # mark as ready unless they are in the error state already
121
+ utils.mark_deploy_state(instances, :deploy_app, ZZSharedLib::Utils::READY, true)
122
+ end
123
+ end
124
+
125
+ # turn off or on the maintenance mode
126
+ def self.do_maint_deploy(utils, amazon, instances, group_name, deploy_group, maint, result_path)
127
+ begin
128
+ utils.mark_deploy_state(instances, :deploy_app, ZZSharedLib::Utils::MAINT)
129
+ remote_cmd = remote_app_maint(maint)
130
+ multi = MultiSSH.new(amazon, group_name, deploy_group)
131
+ multi.run_instances(instances, remote_cmd)
132
+ rescue Exception => ex
133
+ raise ex
134
+ ensure
135
+ multi.output_tracked_data_to_files("app_maint", result_path) rescue nil
136
+ # only mark ready if not in error state
137
+ utils.mark_deploy_state(instances, :deploy_app, ZZSharedLib::Utils::READY, true)
138
+ end
139
+ end
140
+
141
+ end
142
+
143
+ end
@@ -0,0 +1,61 @@
1
+ module Commands
2
+ class ChefBake
3
+
4
+ # holds the options that were passed
5
+ # you can set any initial defaults here
6
+ def options
7
+ @options ||= {
8
+ }
9
+ end
10
+
11
+ # required options
12
+ def required_options
13
+ @required_options ||= Set.new [
14
+ :group,
15
+ ]
16
+ end
17
+
18
+ def register(opts, global_options)
19
+ opts.banner = "Usage: chef_apply [options]"
20
+ opts.description = "Apply the chef scripts"
21
+
22
+ opts.on('-g', "--group name", "Required - Name of this deploy group.") do |v|
23
+ options[:group] = v
24
+ end
25
+
26
+ opts.on('-f', "--force", "Force a deploy even if the current status says we are deploying. You should only do this if you are certain the previous deploy is stuck.") do |v|
27
+ options[:force] = v
28
+ end
29
+ end
30
+
31
+
32
+ def run(global_options, amazon)
33
+ ec2 = amazon.ec2
34
+ utils = ZZSharedLib::Utils.new(amazon)
35
+
36
+ group_name = options[:group]
37
+
38
+ # first see if already exists
39
+ deploy_group = amazon.find_deploy_group(group_name)
40
+
41
+ recipes_deploy_tag = deploy_group.recipes_deploy_tag
42
+
43
+ instances = amazon.find_and_sort_named_instances(group_name)
44
+
45
+ # see if already deploying
46
+ if !options[:force]
47
+ # raises an exception if not all in the none state
48
+ utils.check_deploy_state(instances, [:deploy_chef, :deploy_app])
49
+ end
50
+
51
+ # verify that the chef deploy tag exists
52
+ cmd = "git ls-remote --tags git@github.com:zangzing/zz-chef-repo.git refs/tags/#{recipes_deploy_tag} | egrep refs/tags/#{recipes_deploy_tag}"
53
+ if ZZSharedLib::CL.do_cmd_result(cmd) != 0
54
+ raise "Could not find the tag: #{recipes_deploy_tag} in the remote zz-chef-repo repository. Make sure you check in and tag your code, and run chef_upload."
55
+ end
56
+
57
+ # tag is good, go ahead and deploy to all the machines in the group
58
+ BuildDeployConfig.do_config_deploy(utils, amazon, instances, group_name, deploy_group, options[:result_path])
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,62 @@
1
+ module Commands
2
+ class ChefUpload
3
+
4
+ # holds the options that were passed
5
+ # you can set any initial defaults here
6
+ def options
7
+ @options ||= {
8
+ }
9
+ end
10
+
11
+ # required options
12
+ def required_options
13
+ @required_options ||= Set.new [
14
+ :group,
15
+ :tag,
16
+ ]
17
+ end
18
+
19
+ def register(opts, global_options)
20
+ opts.banner = "Usage: chef_upload [options]"
21
+ opts.description = "Upload the chef scripts"
22
+
23
+ opts.on('-g', "--group name", "Required - Name of this deploy group.") do |v|
24
+ options[:group] = v
25
+ end
26
+
27
+ opts.on('-t', "--tag tag", "Required - Git tag to use for pulling chef code.") do |v|
28
+ options[:tag] = v
29
+ end
30
+ end
31
+
32
+
33
+ def run(global_options, amazon)
34
+ ec2 = amazon.ec2
35
+
36
+ group_name = options[:group]
37
+ recipes_deploy_tag = options[:tag]
38
+
39
+ # first see group exists
40
+ deploy_group = amazon.find_deploy_group(group_name)
41
+
42
+ # verify that the tag given is on the remote repo
43
+ cmd = "git ls-remote --tags git@github.com:zangzing/zz-chef-repo.git refs/tags/#{recipes_deploy_tag} | egrep refs/tags/#{recipes_deploy_tag}"
44
+ if ZZSharedLib::CL.do_cmd_result(cmd) != 0
45
+ raise "Could not find the tag specified in the remote zz-chef-repo repository. Make sure you check in and tag your code."
46
+ end
47
+
48
+ # tag is good, go ahead and upload to simple db
49
+ deploy_group.recipes_deploy_tag = recipes_deploy_tag
50
+ deploy_group.save
51
+ deploy_group.reload # save corrupts the in memory state so must reload, kinda lame
52
+
53
+ remote_cmd = ChefUpload.get_upload_command(recipes_deploy_tag)
54
+ multi = MultiSSH.new(amazon, group_name, deploy_group)
55
+ multi.run(remote_cmd)
56
+ end
57
+
58
+ def self.get_upload_command(recipes_deploy_tag)
59
+ "cd #{::ZZDeploy::RECIPES_DIR} && git fetch && git checkout -f #{recipes_deploy_tag} && git pull && bundle install --path #{::ZZDeploy::RECIPES_BUNDLE_DIR} --deployment"
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,64 @@
1
+
2
+ module Commands
3
+ class ConfigAmazon
4
+
5
+ # holds the options that were passed
6
+ # you can set any initial defaults here
7
+ def options
8
+ @options ||= {
9
+ }
10
+ end
11
+
12
+ # required options
13
+ def required_options
14
+ @required_options ||= Set.new [
15
+ :access_key,
16
+ :secret_key
17
+ ]
18
+ end
19
+
20
+ def register(opts, global_options)
21
+ opts.banner = "Usage: config_amazon [options]"
22
+ opts.description = "Write the amazon key configuration"
23
+
24
+ opts.on("--akey AmazonAccessKey", "Required: Amazon access key, or environment AWS_ACCESS_KEY_ID.") do |v|
25
+ options[:access_key] = v
26
+ end
27
+
28
+ opts.on("--skey AmazonSecretKey", "Required: Amazon secret key or environment AWS_SECRET_ACCESS_KEY") do |v|
29
+ options[:secret_key] = v
30
+ end
31
+ end
32
+
33
+
34
+ def run(global_options, amazon)
35
+ ec2 = amazon.ec2
36
+
37
+ access_key = options[:access_key]
38
+ secret_key = options[:secret_key]
39
+
40
+ info = {
41
+ :aws_access_key_id => access_key,
42
+ :aws_secret_access_key => secret_key
43
+ }
44
+
45
+ # make sure the chef dir exists
46
+ `sudo mkdir -p #{chef_dir}`
47
+
48
+ # generate the json into a temp file
49
+ json = JSON.pretty_generate(info)
50
+ chef_dir = "/var/chef"
51
+ amazon_path = "#{chef_dir}/amazon.json"
52
+ temp_path = File.expand_path('.', "~/amazon_temp.json")
53
+ File.open(temp_path, 'w') {|f| f.write(json) }
54
+
55
+ # now move the file and set permissions
56
+ `sudo cp #{temp_path} #{amazon_path}`
57
+ cmd = "sudo chown `whoami`:`whoami` #{amazon_path} && sudo chmod 0644 #{amazon_path}"
58
+ `#{cmd}`
59
+ # remove the temp file
60
+ `rm -f #{temp_path}`
61
+ puts "Your amazon config has been saved to #{amazon_path}"
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,114 @@
1
+
2
+ module Commands
3
+ class DeleteInstances
4
+
5
+ # holds the options that were passed
6
+ # you can set any initial defaults here
7
+ def options
8
+ @options ||= {
9
+ :force_delete => false
10
+ }
11
+ end
12
+
13
+ # required options
14
+ def required_options
15
+ @required_options ||= Set.new [
16
+ :instances,
17
+ :group
18
+ ]
19
+ end
20
+
21
+ def register(opts, global_options)
22
+ opts.banner = "Usage: delete [options]"
23
+ opts.description = "Delete server instance(s)"
24
+
25
+ opts.on('-i', "--instance instance1,instance2,etc", Array, "The instance(s) to delete.") do |v|
26
+ options[:instances] = v
27
+ end
28
+
29
+ opts.on('-g', "--group deploy_group", "Required - The deploy group we are in. A deploy group is a set of servers that are required to run the infrastructure for a server.") do |v|
30
+ options[:group] = v
31
+ end
32
+
33
+ opts.on('-f', "--force", "Force a deletion of a restricted app type (such as db or app_master). App will most likely fail to operate properly after this.") do |v|
34
+ options[:force_delete] = v
35
+ end
36
+
37
+ opts.on('-w', "--wait", "Wait until the instance has completely shut down and terminated. Otherwise, we return immediately after starting the terminate.") do |v|
38
+ options[:wait] = v
39
+ end
40
+
41
+ opts.on('-p', "--print path", "The directory into which we output the data as a file per host.") do |v|
42
+ options[:result_path] = v
43
+ end
44
+ end
45
+
46
+
47
+ def run(global_options, amazon)
48
+ ec2 = amazon.ec2
49
+ elb = amazon.elb
50
+ utils = ZZSharedLib::Utils.new(amazon)
51
+
52
+ group_name = options[:group]
53
+ deploy_group = amazon.find_deploy_group(group_name)
54
+ group_config = deploy_group.config
55
+ amazon_elb = group_config[:amazon_elb]
56
+
57
+ inst_ids = options[:instances]
58
+ if options[:force_delete] == false
59
+ # check to see if restricted type
60
+ inst_ids.each do |inst_id|
61
+ flat = amazon.flat_tags_for_resource(inst_id)
62
+ role = flat[:role]
63
+ role = role.to_sym unless role.nil?
64
+ case role
65
+ when :app_master, :db
66
+ raise "You cannot delete an instance with a role of #{role}. Doing so will cause the server to not operate. You can force with the --force option."
67
+ end
68
+ end
69
+ end
70
+
71
+
72
+ amazon.flush_tags # force a refresh of the cached tags
73
+ all_instances = amazon.find_and_sort_named_instances(group_name)
74
+
75
+ instances = all_instances.reject { |inst| inst_ids.include?(inst[:resource_id]) == false }
76
+
77
+ # call servers to perform clean shutdown
78
+ puts "Attempting to perform clean shutdown on remote machines. Will ignore any errors and continue with shutdown."
79
+ BuildDeployConfig.do_remote_shutdown(utils, amazon, instances, group_name, deploy_group, options[:result_path]) rescue nil
80
+
81
+ # mark state on machines as deleted
82
+ ec2.create_tags(inst_ids, {:state => 'delete' })
83
+
84
+ # now shut them down
85
+ ec2.terminate_instances([inst_ids])
86
+ inst_ids.each do |inst_id|
87
+ puts "Instance #{inst_id} is terminating."
88
+ end
89
+ if options[:wait] == true
90
+ puts "Waiting for instances to terminate."
91
+ while true do
92
+ print "."
93
+ STDOUT.flush
94
+ sleep(1)
95
+ instances = ec2.describe_instances(inst_ids)
96
+ all_terminated = true
97
+ instances.each do |instance|
98
+ if instance[:aws_state] != "terminated"
99
+ all_terminated = false
100
+ break
101
+ end
102
+ end
103
+ break if all_terminated
104
+ end
105
+ end
106
+
107
+ # we may want to kick off a redeploy of the chef config and app since the configuration of the
108
+ # group changed. Right now we leave this up to the caller since they may want to do it after
109
+ # deleting more than one instance to avoid multiple redeploys
110
+ puts
111
+ puts "Since the configuration has changed you should redeploy the configuration and application using chef_bake and deploy."
112
+ end
113
+ end
114
+ end