zzdeploy 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +1 -0
- data/README.rdoc +10 -0
- data/Rakefile +25 -0
- data/bin/zz +14 -0
- data/lib/commands/add_instance.rb +187 -0
- data/lib/commands/build_deploy_config.rb +143 -0
- data/lib/commands/chef_bake.rb +61 -0
- data/lib/commands/chef_upload.rb +62 -0
- data/lib/commands/config_amazon.rb +64 -0
- data/lib/commands/delete_instances.rb +114 -0
- data/lib/commands/deploy_group_create.rb +128 -0
- data/lib/commands/deploy_group_delete.rb +42 -0
- data/lib/commands/deploy_group_list.rb +41 -0
- data/lib/commands/deploy_group_modify.rb +65 -0
- data/lib/commands/deploy_instances.rb +108 -0
- data/lib/commands/list_instances.rb +56 -0
- data/lib/commands/maint_instances.rb +63 -0
- data/lib/commands/meta_options.rb +26 -0
- data/lib/commands/multi_ssh_instance.rb +87 -0
- data/lib/commands/ssh_instance.rb +98 -0
- data/lib/commands.rb +23 -0
- data/lib/info.rb +5 -0
- data/lib/multi_ssh.rb +155 -0
- data/lib/printer.rb +54 -0
- data/lib/zz_deploy.rb +177 -0
- data/spec/spec_helper.rb +5 -0
- metadata +219 -0
data/LICENSE
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Copyright (c) 2011 ZangZing, LLC
|
data/README.rdoc
ADDED
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
|