zzdeploy 0.0.5

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