zzdeploy 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,128 @@
1
+ module Commands
2
+ class DeployGroupCreate
3
+
4
+ # holds the options that were passed
5
+ # you can set any initial defaults here
6
+ def options
7
+ @options ||= {
8
+ :amazon_elb => ''
9
+ }
10
+ end
11
+
12
+ # required options
13
+ def required_options
14
+ @required_options ||= Set.new [
15
+ :group,
16
+ :app_name,
17
+ :rails_env,
18
+ :vhost,
19
+ :email_host,
20
+ :app_git_url,
21
+ :amazon_security_key,
22
+ :amazon_security_group,
23
+ :amazon_image,
24
+ :database_host,
25
+ :database_username,
26
+ :database_password,
27
+ :database_schema
28
+ ]
29
+ end
30
+
31
+ def register(opts, global_options)
32
+ opts.banner = "Usage: create_deploy_group [options]"
33
+ opts.description = "Create a deploy group"
34
+
35
+ opts.on('-g', "--group name", "Required - Name of this deploy group.") do |v|
36
+ options[:group] = v
37
+ end
38
+
39
+ opts.on("--app_name appname", "Required - Name of the application.") do |v|
40
+ options[:app_name] = v
41
+ end
42
+
43
+ opts.on("--rails_env rails_env", "Required - The rails environment to use on deploy.") do |v|
44
+ options[:rails_env] = v
45
+ end
46
+
47
+ opts.on("--vhost vhost", "Required - The vhost your server will be deployed as.") do |v|
48
+ options[:vhost] = v
49
+ end
50
+
51
+ opts.on("--email_host emailhost", "Required - The email host name to use for incomming email processing.") do |v|
52
+ options[:email_host] = v
53
+ end
54
+
55
+ opts.on("--app_git_url app_git_url", "Required - Git URL to fetch code from.") do |v|
56
+ options[:app_git_url] = v
57
+ end
58
+
59
+ opts.on("--extra_json_file extra", "Optional - A file with JSON that has application custom context associated with this deploy group.") do |v|
60
+ options[:extra_json_file] = v
61
+ end
62
+
63
+ opts.on("--zone availability_zone", MetaOptions.availability_zones, "The amazon availability zone - currently only east coast.") do |v|
64
+ options[:availability_zone] = v
65
+ end
66
+
67
+ opts.on("--amazon_security_key key", "Required - The SSH key name to use, assumes pre-configured on Amazon.") do |v|
68
+ options[:amazon_security_key] = v
69
+ end
70
+
71
+ opts.on("--amazon_security_group group", "Required - The amazon security group, assumes pre-configured on Amazon.") do |v|
72
+ options[:amazon_security_group] = v
73
+ end
74
+
75
+ opts.on("--amazon_image ami", "Required - The baseline Amazon image to use, assumes pre-configured on Amazon.") do |v|
76
+ options[:amazon_image] = v
77
+ end
78
+
79
+ opts.on("--amazon_elb load_balancer", "Optional - The elastic load balancer we operate under.") do |v|
80
+ options[:amazon_elb] = v
81
+ end
82
+
83
+ opts.on("--database_host database", "Required - The database host name.") do |v|
84
+ options[:database_host] = v
85
+ end
86
+
87
+ opts.on("--database_username username", "Required - The database user name name.") do |v|
88
+ options[:database_username] = v
89
+ end
90
+
91
+ opts.on("--database_password password", "Required - The database password.") do |v|
92
+ options[:database_password] = v
93
+ end
94
+
95
+ opts.on("--database_schema schema", "Required - The database schema name.") do |v|
96
+ options[:database_schema] = v
97
+ end
98
+
99
+ end
100
+
101
+
102
+ def run(global_options, amazon)
103
+ group_name = options[:group]
104
+ extra_file = options[:extra_json_file]
105
+ if !extra_file.nil?
106
+ # they are passing a path to a file containing custom json so add it to the extra field
107
+ options.delete(:extra_json_file)
108
+ json = File.open(extra_file, 'r') {|f| f.read }
109
+ # make sure we can parse into a hash
110
+ extra = JSON.parse(json)
111
+ options[:extra] = extra
112
+ end
113
+ config_json = JSON.fast_generate(options)
114
+
115
+ # first see if already exists
116
+ deploy_group = ZZSharedLib::DeployGroupSimpleDB.find_by_zz_object_type_and_group(ZZSharedLib::DeployGroupSimpleDB.object_type, group_name, :auto_load => true)
117
+
118
+ if !deploy_group.nil?
119
+ raise "This deploy group already exists. Doing nothing."
120
+ end
121
+
122
+ deploy_group = ZZSharedLib::DeployGroupSimpleDB::create(:zz_object_type => ZZSharedLib::DeployGroupSimpleDB.object_type, :group => group_name,
123
+ :config_json => config_json, :recipes_deploy_tag => "origin/master", :app_deploy_tag => "master",
124
+ :created_at => Time.now.strftime('%Y-%m-%dT%H:%M:%S%z'))
125
+
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,42 @@
1
+ module Commands
2
+ class DeployGroupDelete
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: delete_deploy_group [options]"
20
+ opts.description = "Delete a deploy group"
21
+
22
+ opts.on('-g', "--group name", "Required - Name of this deploy group.") do |v|
23
+ options[:group] = v
24
+ end
25
+
26
+ end
27
+
28
+
29
+ def run(global_options, amazon)
30
+ group_name = options[:group]
31
+
32
+ # first see if already exists
33
+ deploy_group = amazon.find_deploy_group(group_name)
34
+
35
+ if deploy_group.nil? || deploy_group[:group] != group_name
36
+ raise "Deploy group not found. Doing nothing."
37
+ end
38
+
39
+ deploy_group.delete
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,41 @@
1
+ module Commands
2
+ class DeployGroupList
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
+ ]
15
+ end
16
+
17
+ def register(opts, global_options)
18
+ opts.banner = "Usage: list_deploy_groups [options]"
19
+ opts.description = "List the deploy groups"
20
+
21
+ end
22
+
23
+
24
+ def run(global_options, amazon)
25
+ ec2 = amazon.ec2
26
+
27
+ # first see if already exists
28
+ deploy_groups = ZZSharedLib::DeployGroupSimpleDB.find_all_by_zz_object_type(ZZSharedLib::DeployGroupSimpleDB.object_type, :auto_load => true)
29
+
30
+ deploy_groups.each do |deploy_group|
31
+ puts "Name: #{deploy_group[:group]}"
32
+ puts "Recipes_deploy_tag: #{deploy_group[:recipes_deploy_tag]}"
33
+ puts "App_deploy_tag: #{deploy_group[:app_deploy_tag]}"
34
+ puts "Config Json:"
35
+ pretty = JSON.pretty_generate(deploy_group.config)
36
+ puts "#{pretty}"
37
+ puts
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,65 @@
1
+ module Commands
2
+ class DeployGroupModify
3
+
4
+ # holds the options that were passed
5
+ # you can set any initial defaults here
6
+ def options
7
+ @options ||= {
8
+ :group,
9
+ :extra_json_file
10
+ }
11
+ end
12
+
13
+ # required options
14
+ def required_options
15
+ @required_options ||= Set.new [
16
+ :group,
17
+ :extra_json_file
18
+ ]
19
+ end
20
+
21
+ def register(opts, global_options)
22
+ opts.banner = "Usage: deploy_group_modify [options]"
23
+ opts.description = "Lets you modify the app extra data by replacing the current extra data with the new data supplied."
24
+
25
+ opts.on('-g', "--group name", "Required - Name of this deploy group.") do |v|
26
+ options[:group] = v
27
+ end
28
+
29
+ opts.on('-e', "--extra_json_file extra", "Required - A file with JSON that has application custom context associated with this deploy group.") do |v|
30
+ options[:extra_json_file] = v
31
+ end
32
+
33
+ end
34
+
35
+
36
+ def run(global_options, amazon)
37
+ ec2 = amazon.ec2
38
+
39
+ group_name = options[:group]
40
+ extra_file = options[:extra_json_file]
41
+ if !extra_file.nil?
42
+ # they are passing a path to a file containing custom json so add it to the extra field
43
+ json = File.open(extra_file, 'r') {|f| f.read }
44
+ # make sure we can parse into a hash
45
+ extra = JSON.parse(json)
46
+ options[:extra] = extra
47
+ end
48
+
49
+ # get the existing deploy group
50
+ deploy_group = amazon.find_deploy_group(group_name)
51
+ config = deploy_group.config
52
+ extra_file = options[:extra_json_file]
53
+
54
+ # open up the specified file with the json and parse
55
+ json = File.open(extra_file, 'r') {|f| f.read }
56
+ # make sure we can parse into a hash
57
+ extra = JSON.parse(json)
58
+
59
+ # ok, now set or replace any existing extra data
60
+ config[:extra] = extra
61
+ deploy_group.config = config
62
+ deploy_group.save
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,108 @@
1
+ module Commands
2
+ class DeployInstances
3
+
4
+ # holds the options that were passed
5
+ # you can set any initial defaults here
6
+ def options
7
+ @options ||= {
8
+ :migrate_command => '',
9
+ :downtime => false,
10
+ :no_restart => false
11
+ }
12
+ end
13
+
14
+ # required options
15
+ def required_options
16
+ @required_options ||= Set.new [
17
+ :group,
18
+ ]
19
+ end
20
+
21
+ def register(opts, global_options)
22
+ opts.banner = "Usage: deploy [options]"
23
+ opts.description = "Deploy the applications for the specified group"
24
+
25
+ opts.on('-g', "--group name", "Required - Name of this deploy group.") do |v|
26
+ options[:group] = v
27
+ end
28
+
29
+ opts.on('-t', "--tag tag", "Required - Git tag to use for pulling chef code.") do |v|
30
+ options[:tag] = v
31
+ end
32
+
33
+ opts.on('-m', "--migrate [command]", "Migrate command to run. Does not force downtime, use the --downtime option for that.") do |v|
34
+ options[:migrate_command] = v || 'rake db:migrate'
35
+ end
36
+
37
+ opts.on('-d', "--downtime", "If this flag is set we bring the server down and bring up the maint page during the restart phase.") do |v|
38
+ options[:downtime] = v
39
+ end
40
+
41
+ opts.on("--no_restart", "Set this if you only want the deploy without a restart, useful for creating AMI images.") do |v|
42
+ options[:no_restart] = v
43
+ end
44
+
45
+ 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|
46
+ options[:force] = v
47
+ end
48
+
49
+ opts.on('-p', "--print path", "The directory into which we output the data as a file per host.") do |v|
50
+ options[:result_path] = v
51
+ end
52
+ end
53
+
54
+
55
+ def run(global_options, amazon)
56
+ ec2 = amazon.ec2
57
+ utils = ZZSharedLib::Utils.new(amazon)
58
+
59
+ group_name = options[:group]
60
+ migrate_command = options[:migrate_command]
61
+ downtime = options[:downtime]
62
+ app_deploy_tag = options[:tag]
63
+ no_restart = options[:no_restart]
64
+
65
+ # first see if already exists
66
+ deploy_group = amazon.find_deploy_group(group_name)
67
+
68
+ group_config = deploy_group.config
69
+ gitrepo = group_config[:app_git_url]
70
+
71
+ # verify that the chef deploy tag exists
72
+ full_ref = "refs/heads/#{app_deploy_tag}"
73
+ cmd = "git ls-remote #{gitrepo} #{full_ref} | egrep #{full_ref}"
74
+ if ZZSharedLib::CL.do_cmd_result(cmd) != 0
75
+ # now try to see if it's a tag
76
+ full_ref = "refs/tags/#{app_deploy_tag}"
77
+ cmd = "git ls-remote #{gitrepo} #{full_ref} | egrep #{full_ref}"
78
+ if ZZSharedLib::CL.do_cmd_result(cmd) != 0
79
+ raise "Could not find the tag or ref: #{app_deploy_tag} in the remote #{gitrepo} repository."
80
+ end
81
+ end
82
+
83
+ deploy_group.app_deploy_tag = app_deploy_tag
84
+ deploy_group.save
85
+ deploy_group.reload # save corrupts the in memory state so must reload, kinda lame
86
+
87
+
88
+ instances = amazon.find_and_sort_named_instances(group_name)
89
+
90
+ # see if already deploying
91
+ if !options[:force]
92
+ # raises an exception if not all in the ready or error state
93
+ utils.check_deploy_state(instances, [:deploy_chef, :deploy_app])
94
+ end
95
+
96
+
97
+ # tag is good, go ahead and deploy to all the machines in the group
98
+ # this is a two phase operation. The first pushes the code and preps everything
99
+ # up to the point of a before restart operation. We then call again to
100
+ # perform the restart. This ensures that all servers were prepped before
101
+ # we try to restart to minimize the chance of issues.
102
+ BuildDeployConfig.do_app_deploy(utils, amazon, instances, group_name, deploy_group, migrate_command, downtime, no_restart, options[:result_path])
103
+ ui = Printer.new(STDOUT, STDERR, STDIN)
104
+ ui.msg(ui.color("Your app has been successfully deployed - open for business.", :green, :bold, :reverse))
105
+
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,56 @@
1
+ require "set"
2
+
3
+ module Commands
4
+ class ListInstances
5
+
6
+ # holds the options that were passed
7
+ # you can set any initial defaults here
8
+ def options
9
+ @options ||= {
10
+ }
11
+ end
12
+
13
+ # required options
14
+ def required_options
15
+ @required_options ||= Set.new [
16
+ :group
17
+ ]
18
+ end
19
+
20
+ def register(opts, global_options)
21
+ opts.banner = "Usage: add [options]"
22
+ opts.description = "Add a server instance"
23
+
24
+ opts.on('-r', "--role role", MetaOptions.roles, "Role to look for.") do |v|
25
+ options[:role] = v
26
+ end
27
+
28
+ opts.on('-g', "--group deploy_group", "Required: Group to look for.") do |v|
29
+ options[:group] = v
30
+ end
31
+
32
+ end
33
+
34
+
35
+ def run(global_options, amazon)
36
+ ec2 = amazon.ec2
37
+
38
+ instances = amazon.find_and_sort_named_instances(options[:group], options[:role])
39
+
40
+ first = true
41
+ instances.each do |instance|
42
+ if first
43
+ s = sprintf("%-40s%-14s%-40s","Name", "Instance", "Public Host")
44
+ puts s
45
+ first = false
46
+ end
47
+ name = instance[:Name]
48
+ resource_id = instance[:resource_id]
49
+ public_host = instance[:public_hostname]
50
+ s = sprintf("%-40s%-14s%-40s",name, resource_id, public_host)
51
+ puts s
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,63 @@
1
+ module Commands
2
+ class MaintInstances
3
+
4
+ # holds the options that were passed
5
+ # you can set any initial defaults here
6
+ def options
7
+ @options ||= {
8
+ :migrate_command => '',
9
+ :downtime => false
10
+ }
11
+ end
12
+
13
+ # required options
14
+ def required_options
15
+ @required_options ||= Set.new [
16
+ :group,
17
+ :maint,
18
+ ]
19
+ end
20
+
21
+ def register(opts, global_options)
22
+ opts.banner = "Usage: maint [options]"
23
+ opts.description = "Put up or take down the maintenance page."
24
+
25
+ opts.on('-g', "--group name", "Required - Name of this deploy group.") do |v|
26
+ options[:group] = v
27
+ end
28
+
29
+ opts.on('-m', "--[no-]maint", "Required - Use --maint if you want the maint page, --no-maint to remove the maint page.") do |v|
30
+ options[:maint] = v
31
+ end
32
+
33
+ opts.on('-p', "--print path", "The directory into which we output the data as a file per host.") do |v|
34
+ options[:result_path] = v
35
+ end
36
+ end
37
+
38
+
39
+ def run(global_options, amazon)
40
+ ec2 = amazon.ec2
41
+ utils = ZZSharedLib::Utils.new(amazon)
42
+
43
+ group_name = options[:group]
44
+ maint = options[:maint]
45
+
46
+ # first see if already exists
47
+ deploy_group = amazon.find_deploy_group(group_name)
48
+
49
+ instances = amazon.find_and_sort_named_instances(group_name)
50
+
51
+ # see if already deploying
52
+ if !options[:force]
53
+ # raises an exception if not all in the ready or error state
54
+ utils.check_deploy_state(instances, [:deploy_chef, :deploy_app])
55
+ end
56
+
57
+
58
+ # put up or take down the maint page
59
+ BuildDeployConfig.do_maint_deploy(utils, amazon, instances, group_name, deploy_group, maint, options[:result_path])
60
+
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,26 @@
1
+ # this class defines the valid meta options we allow
2
+ # such as the role types allowed, apps allowed, environments allowed
3
+ #
4
+ module Commands
5
+ class MetaOptions
6
+ # the roles that a given server can play
7
+ # only one role per server is allowed. If we
8
+ # need custom functionality we define a role that
9
+ # has the functionality we need.
10
+ # The meaning of the roles is defined in the chef script
11
+ # mapping from a role to recipes
12
+ def self.roles
13
+ [:app_master, :app, :db, :util, :db_slave, :solo]
14
+ end
15
+
16
+ def self.availability_zones
17
+ ["us-east-1a", "us-east-1b", "us-east-1c", "us-east-1d"]
18
+ end
19
+
20
+ # the valid apps we can deploy
21
+ def self.apps
22
+ [:photos, :zza, :rollup]
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,87 @@
1
+ require "set"
2
+ require 'readline'
3
+
4
+ module Commands
5
+ class MultiSSHInstance
6
+
7
+ # holds the options that were passed
8
+ # you can set any initial defaults here
9
+ def options
10
+ @options ||= {
11
+ }
12
+ end
13
+
14
+ # required options
15
+ def required_options
16
+ @required_options ||= Set.new [
17
+ :group
18
+ ]
19
+ end
20
+
21
+ def register(opts, global_options)
22
+ opts.banner = "Usage: ssh [options]"
23
+ opts.description = "SSH into a server"
24
+
25
+ opts.on('-i', "--instances instance1,instance2,etc", Array, "The instance(s) to connect to.") do |v|
26
+ options[:instances] = v
27
+ end
28
+
29
+ opts.on('-r', "--role role", MetaOptions.roles, "Role to look for.") do |v|
30
+ options[:role] = v
31
+ end
32
+
33
+ opts.on('-g', "--group deploy_group", "Required: Group to look for.") do |v|
34
+ options[:group] = v
35
+ end
36
+
37
+ opts.on('-f', "--file file", "A file that contains the commands to execute.") do |v|
38
+ options[:file] = 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
+
50
+ file_path = options[:file]
51
+ if file_path.nil?
52
+ if ARGV.length != 1
53
+ raise "Must include the remote command to run. Make sure you quote it so it appears as one argument"
54
+ end
55
+ remote_cmd = ARGV[0]
56
+ else
57
+ remote_cmd = File.open(file_path, 'r') {|f| f.read }
58
+ end
59
+
60
+ group_name = options[:group]
61
+ deploy_group = amazon.find_deploy_group(group_name)
62
+ group_config = deploy_group.config
63
+
64
+ user_instances = options[:instances]
65
+ if user_instances.nil?
66
+ instances = amazon.find_and_sort_named_instances(options[:group], options[:role])
67
+ else
68
+ instances = [] # build the instances wanted here
69
+ server_instances = amazon.find_and_sort_named_instances(nil, nil, false)
70
+ server_instances.each do |server_instance|
71
+ server_instance_id = server_instance[:resource_id]
72
+ if user_instances.include?(server_instance_id)
73
+ instances << server_instance
74
+ end
75
+ end
76
+ end
77
+
78
+ if instances.empty?
79
+ raise "No instances matched your search criteria."
80
+ end
81
+
82
+ multi = MultiSSH.new(amazon, group_name, deploy_group)
83
+ multi.run_instances(instances, remote_cmd)
84
+ multi.output_tracked_data_to_files("multi_ssh", options[:result_path])
85
+ end
86
+ end
87
+ end