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 +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
|