opsworks_ship 1.1.0 → 1.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7a82c2264db6584c292d2c0060ecc0b46dc28ffd
4
- data.tar.gz: 002965652242e5dadc6b36084192c21198a20d9d
3
+ metadata.gz: 203d4494b6a5ea1e8c6ed33a5ec2697532ad1616
4
+ data.tar.gz: 43e10d97b1a2c41d7dd2776fee6ab725038a8513
5
5
  SHA512:
6
- metadata.gz: a57e958ca83ea4abbb5f9e4aad5e14c7f6c513cfbbad347ce4ed6d163a95ba31d8f32b71f51b46d67cbcc07d56f97e51c2f3094748344577b73351450f7118c0
7
- data.tar.gz: 0733312598621d288bc3533844383d4a25cc2f0d88f6f82c253f6140f69adf46b2790afb341ce97d364a78b0525d7f08f878be4966a6f5c99d754e01fe833692
6
+ metadata.gz: 69627d390ec62e075dd85944b4af454928cc0b47e1eaaba8c52b66be8973832cd5142b0907584015a6df2a975657991fa184691d595b060942ee9b0e4010b543
7
+ data.tar.gz: 1096f646ca7a0d6aeb2c337e8e35b0fdd5624035b6a0df3b305828656a8bf50dc0f61540c7434a573bf1ddab2c71afdd7a535b1042dfea58630ca45ab90c30e4
data/README.md CHANGED
@@ -20,6 +20,8 @@ Or install it yourself as:
20
20
 
21
21
  $ gem install opsworks_ship
22
22
 
23
+ ### Shipping app code
24
+
23
25
  Create a script (probably in `bin/deploy`) similar to the following:
24
26
 
25
27
  #!/usr/bin/env ruby
@@ -27,18 +29,34 @@ Create a script (probably in `bin/deploy`) similar to the following:
27
29
  gem 'opsworks_ship'
28
30
  require 'opsworks_ship/deploy'
29
31
 
30
- initialize(stack_name, revision, app_type, app_layer_name_regex, hipchat_auth_token = nil, hipchat_room_id = nil)
32
+ OpsworksShip::Deploy.new(stack_name: ARGV[0], revision: ARGV[1], app_type: 'rails', app_layer_name_regex: 'rails|sidekiq', hipchat_auth_token: 'my_hipchat_auth_token', hipchat_room_id: 12345).deploy
33
+
34
+ ### Shipping and executing chef code
35
+
36
+ Create a script (probably in `bin/chef`) similar to the following:
37
+
38
+ #!/usr/bin/env ruby
39
+
40
+ require 'opsworks_ship/chef'
31
41
 
32
- args = [ARGV[0], ARGV[1], 'rails', 'rails|sidekiq', 'abcde_this_is_a_token', 12345].compact
33
- OpsworksShip::Deploy.new(*args).deploy
42
+ OpsworksShip::Chef.new(stack_name: ARGV[0], revision: ARGV[1], app_type: 'rails', app_layer_name_regex: 'rails|sidekiq', hipchat_auth_token: 'my_hipchat_auth_token', hipchat_room_id: 12345).deploy
43
+
34
44
 
35
45
  ## Usage examples
36
46
 
47
+ ### App code
48
+
37
49
  * `./bin/deploy help`
38
50
  * `./bin/deploy staging heads/master rails rails|sidekiq`
39
51
  * `./bin/deploy staging heads/master rails rails|sidekiq my_hipchat_token my_hipchat_room`
40
52
  * `./bin/deploy production heads/master java "Java App Server" my_hipchat_token my_hipchat_room`
41
53
 
54
+ ### Chef code
55
+
56
+ * `./bin/chef help`
57
+ * `./bin/chef staging heads/master rails rails|sidekiq`
58
+ * `./bin/chef staging heads/my_test_branch rails rails|sidekiq`
59
+
42
60
  ## Development
43
61
 
44
62
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -53,4 +71,3 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/NEWECX
53
71
  ## License
54
72
 
55
73
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
56
-
@@ -2,4 +2,5 @@ require "opsworks_ship/version"
2
2
 
3
3
  module OpsworksShip
4
4
  autoload :Deploy, 'opsworks_ship/deploy'
5
+ autoload :Chef, 'opsworks_ship/chef'
5
6
  end
@@ -0,0 +1,69 @@
1
+ module AwsDataHelper
2
+
3
+ def layer_instance_ids(layer_ids)
4
+ layer_ids.map do |layer_id|
5
+ JSON.parse(`aws opsworks describe-instances --layer-id=#{layer_id}`)['Instances'].map{|i| i['InstanceId']}
6
+ end.flatten
7
+ end
8
+
9
+ def relevant_instance_ids(stack_id)
10
+ layer_instance_ids(relevant_app_layer_ids(stack_id))
11
+ end
12
+
13
+ def relevant_app_layer_ids(stack_id)
14
+ stack_layers(stack_id).select{|l| l['Name'] =~ /#{@app_layer_name_regex}/i}.map{|layer_data| layer_data['LayerId']}
15
+ end
16
+
17
+ def stack_layers(stack_id)
18
+ JSON.parse(`aws opsworks describe-layers --stack-id=#{stack_id}`)['Layers']
19
+ end
20
+
21
+ def opsworks_app_types
22
+ [
23
+ 'aws-flow-ruby',
24
+ 'java',
25
+ 'rails',
26
+ 'php',
27
+ 'nodejs',
28
+ 'static',
29
+ 'other',
30
+ ]
31
+ end
32
+
33
+ def all_stack_names
34
+ @all_stack_names ||= stack_data.map{|s| s['Name']}.sort
35
+ end
36
+
37
+ def stack_data
38
+ @stack_data ||= begin
39
+ JSON.parse(`aws opsworks describe-stacks`)['Stacks']
40
+ end
41
+ end
42
+
43
+ def stack_id
44
+ stack = stack_data.select{|s| s['Name'].downcase == @stack_name.downcase}.first
45
+ if stack
46
+ stack['StackId']
47
+ else
48
+ raise "Stack not found. Available opsworks stacks: #{all_stack_names}"
49
+ end
50
+ end
51
+
52
+ def app_id(stack_id)
53
+ JSON.parse(`aws opsworks describe-apps --stack-id=#{stack_id}`)['Apps'].select{|a| a['Type'] == @app_type}.first['AppId']
54
+ end
55
+
56
+ def describe_deployments(deployment_id)
57
+ cmd = "aws opsworks describe-deployments --deployment-ids #{deployment_id}"
58
+ JSON.parse(`#{cmd}`)
59
+ end
60
+
61
+ def per_server_status_descriptions(deployment_id)
62
+ JSON.parse(`aws opsworks describe-commands --deployment-id #{deployment_id}`)['Commands'].map{|deploy| "#{instance_name(deploy['InstanceId'])} - Status: #{deploy['Status']} - Log: #{deploy['LogUrl']}"}
63
+ end
64
+
65
+ def instance_name(instance_id)
66
+ JSON.parse(`aws opsworks describe-instances --instance-id #{instance_id}`)['Instances'].first['Hostname']
67
+ end
68
+
69
+ end
@@ -0,0 +1,64 @@
1
+ module AwsDeploymentHelper
2
+
3
+ def timestamped_puts(str)
4
+ puts "#{Time.now} #{str}"
5
+ end
6
+
7
+ def deployed_by
8
+ git_user = `git config --global user.name`.chomp
9
+ git_email = `git config --global user.email`.chomp
10
+ "#{git_user} (#{git_email})"
11
+ end
12
+
13
+ def syntax
14
+ puts "Arguments: #{method(:initialize).parameters.map{|p| "#{p.last} (#{p.first})"}.join(' ')}"
15
+ puts "\n"
16
+ puts "Valid stacks: #{all_stack_names}}"
17
+ end
18
+
19
+ def monitor_deployment(deployment_id)
20
+ deployment_finished = false
21
+ status = ""
22
+ while !deployment_finished
23
+ response = describe_deployments(deployment_id)
24
+ response["Deployments"].each do |deployment|
25
+ next if deployment["DeploymentId"] != deployment_id
26
+ status = deployment["Status"]
27
+ timestamped_puts "Status: #{status}"
28
+ deployment_finished = true if deployment["Status"].downcase != "running"
29
+ end
30
+ sleep(15) unless deployment_finished
31
+ end
32
+ timestamped_puts "Deployment #{status}"
33
+ output_per_server_status_descriptions(deployment_id)
34
+ status
35
+ end
36
+
37
+ def start_deployment(command)
38
+ app_id = app_id(stack_id)
39
+
40
+ cmd = "aws opsworks create-deployment --app-id #{app_id} " +
41
+ "--stack-id #{stack_id} " +
42
+ "--command \"#{command}\" " +
43
+ "--instance-ids #{relevant_instance_ids(stack_id).join(' ')} " +
44
+ deploy_comment
45
+
46
+ timestamped_puts "Running command... #{command}"
47
+ timestamped_puts cmd
48
+
49
+ response = JSON.parse(`#{cmd}`)
50
+ response["DeploymentId"]
51
+ end
52
+
53
+ def deploy_comment
54
+ "--comment \"rev. #{@revision}, deployed by #{deployed_by}\" "
55
+ end
56
+
57
+ def output_per_server_status_descriptions(deployment_id)
58
+ per_server_status_descriptions(deployment_id).each do |description|
59
+ timestamped_puts description
60
+ puts ''
61
+ end
62
+ end
63
+
64
+ end
@@ -0,0 +1,72 @@
1
+ require 'json'
2
+
3
+ require "opsworks_ship/aws_data_helper"
4
+ include AwsDataHelper
5
+
6
+ require "opsworks_ship/aws_deployment_helper"
7
+ include AwsDeploymentHelper
8
+
9
+ require "opsworks_ship/hipchat_helper"
10
+ include HipchatHelper
11
+
12
+ module OpsworksShip
13
+ class Chef
14
+ def initialize(stack_name:, revision:, app_type:, app_layer_name_regex:, hipchat_auth_token: nil, hipchat_room_id: nil)
15
+ @stack_name = stack_name
16
+ raise "Invalid stack name, valid stacks are: #{all_stack_names}" unless all_stack_names.any?{|available_name| available_name == stack_name}
17
+
18
+ @revision = revision
19
+
20
+ @app_type = app_type
21
+ raise "Invalid app type #{@app_type}" unless opsworks_app_types.include?(@app_type)
22
+
23
+ @app_layer_name_regex = app_layer_name_regex
24
+
25
+ @hipchat_auth_token = hipchat_auth_token
26
+ @hipchat_room_id = hipchat_room_id
27
+ raise "Must supply both or neither hipchat params" if [@hipchat_auth_token, @hipchat_room_id].compact.size == 1
28
+ end
29
+
30
+ def deploy
31
+ start_time = Time.now
32
+
33
+ puts "\n-------------------------------------"
34
+ puts "Full chef run started"
35
+ puts "Revision: #{@revision}"
36
+ puts "Stack: #{@stack_name}"
37
+ puts "-------------------------------------\n\n"
38
+
39
+ set_revision_for_stack
40
+
41
+ [
42
+ 'update_custom_cookbooks',
43
+ 'update_dependencies',
44
+ 'setup',
45
+ 'configure',
46
+ ].each{|command| run_and_monitor(command)}
47
+
48
+ msg = "#{@app_type} -- Full chef run successful! Layers #{@app_layer_name_regex} now on #{@revision} deployed to #{@stack_name} by #{deployed_by}"
49
+ post_deployment_to_hipchat(msg)
50
+
51
+ run_time_seconds = Time.now.to_i - start_time.to_i
52
+ timestamped_puts "Deployment time #{run_time_seconds / 60}:%02i" % (run_time_seconds % 60)
53
+ end
54
+
55
+ def run_and_monitor(command)
56
+ deployment_id = start_deployment({ :Name => command }.to_json.gsub('"', "\\\""))
57
+ final_status = monitor_deployment(deployment_id)
58
+
59
+ unless final_status.downcase =~ /successful/
60
+ raise "Deployment failed, status: #{final_status}"
61
+ end
62
+ end
63
+
64
+ def set_revision_for_stack
65
+ timestamped_puts "Setting revision #{@revision}"
66
+ cmd = "aws opsworks update-stack --stack-id #{stack_id} --custom-cookbooks-source Revision=#{@revision}"
67
+ timestamped_puts "#{cmd}"
68
+ `#{cmd}`
69
+ end
70
+
71
+ end
72
+ end
@@ -1,5 +1,14 @@
1
1
  require 'json'
2
2
 
3
+ require "opsworks_ship/aws_data_helper"
4
+ include AwsDataHelper
5
+
6
+ require "opsworks_ship/aws_deployment_helper"
7
+ include AwsDeploymentHelper
8
+
9
+ require "opsworks_ship/hipchat_helper"
10
+ include HipchatHelper
11
+
3
12
  module OpsworksShip
4
13
  class Deploy
5
14
  def initialize(stack_name:, revision:, app_type:, app_layer_name_regex:, hipchat_auth_token: nil, hipchat_room_id: nil)
@@ -18,12 +27,6 @@ module OpsworksShip
18
27
  raise "Must supply both or neither hipchat params" if [@hipchat_auth_token, @hipchat_room_id].compact.size == 1
19
28
  end
20
29
 
21
- def syntax
22
- puts "Arguments: #{method(:initialize).parameters.map{|p| "#{p.last} (#{p.first})"}.join(' ')}"
23
- puts "\n"
24
- puts "Valid stacks: #{all_stack_names}}"
25
- end
26
-
27
30
  def deploy
28
31
  start_time = Time.now
29
32
 
@@ -35,7 +38,7 @@ module OpsworksShip
35
38
 
36
39
  set_revision_in_opsworks_app
37
40
 
38
- deployment_id = start_deployment
41
+ deployment_id = start_deployment({ :Name => "deploy" }.to_json.gsub('"', "\\\""))
39
42
  final_status = monitor_deployment(deployment_id)
40
43
 
41
44
  if final_status.downcase =~ /successful/
@@ -49,25 +52,6 @@ module OpsworksShip
49
52
  timestamped_puts "Deployment time #{run_time_seconds / 60}:%02i" % (run_time_seconds % 60)
50
53
  end
51
54
 
52
- def all_stack_names
53
- @all_stack_names ||= stack_data.map{|s| s['Name']}.sort
54
- end
55
-
56
- def stack_data
57
- @stack_data ||= begin
58
- JSON.parse(`aws opsworks describe-stacks`)['Stacks']
59
- end
60
- end
61
-
62
- def stack_id
63
- stack = stack_data.select{|s| s['Name'].downcase == @stack_name.downcase}.first
64
- if stack
65
- stack['StackId']
66
- else
67
- raise "Stack not found. Available opsworks stacks: #{all_stack_names}"
68
- end
69
- end
70
-
71
55
  def set_revision_in_opsworks_app
72
56
  timestamped_puts "Setting revision #{@revision}"
73
57
  cmd = "aws opsworks update-app --app-id #{app_id(stack_id)} --app-source Revision=#{@revision}"
@@ -75,118 +59,5 @@ module OpsworksShip
75
59
  `#{cmd}`
76
60
  end
77
61
 
78
- def app_id(stack_id)
79
- JSON.parse(`aws opsworks describe-apps --stack-id=#{stack_id}`)['Apps'].select{|a| a['Type'] == @app_type}.first['AppId']
80
- end
81
-
82
- def timestamped_puts(str)
83
- puts "#{Time.now} #{str}"
84
- end
85
-
86
- def deployed_by
87
- git_user = `git config --global user.name`.chomp
88
- git_email = `git config --global user.email`.chomp
89
- "#{git_user} (#{git_email})"
90
- end
91
-
92
- def deploy_comment
93
- "--comment \"rev. #{@revision}, deployed by #{deployed_by}\" "
94
- end
95
-
96
- def deploy_command
97
- { :Name => "deploy" }.to_json.gsub('"', "\\\"")
98
- end
99
-
100
- def monitor_deployment(deployment_id)
101
- deployment_finished = false
102
- status = ""
103
- while !deployment_finished
104
- response = describe_deployments(deployment_id)
105
- response["Deployments"].each do |deployment|
106
- next if deployment["DeploymentId"] != deployment_id
107
- status = deployment["Status"]
108
- timestamped_puts "Status: #{status}"
109
- deployment_finished = true if deployment["Status"].downcase != "running"
110
- end
111
- sleep(15) unless deployment_finished
112
- end
113
- timestamped_puts "Deployment #{status}"
114
- status
115
- end
116
-
117
- def describe_deployments(deployment_id)
118
- cmd = "aws opsworks describe-deployments --deployment-ids #{deployment_id}"
119
- JSON.parse(`#{cmd}`)
120
- end
121
-
122
- def start_deployment
123
- app_id = app_id(stack_id)
124
-
125
- cmd = "aws opsworks create-deployment --app-id #{app_id} " +
126
- "--stack-id #{stack_id} " +
127
- "--command \"#{deploy_command}\" " +
128
- "--instance-ids #{relevant_instance_ids(stack_id).join(' ')} " +
129
- deploy_comment
130
-
131
- timestamped_puts "Starting deployment..."
132
- timestamped_puts cmd
133
-
134
- response = JSON.parse(`#{cmd}`)
135
- response["DeploymentId"]
136
- end
137
-
138
- def post_deployment_to_hipchat(msg)
139
- room_id = (@hipchat_room_id || ENV['HIPCHAT_ROOM_ID']).to_i
140
- auth_token = @hipchat_auth_token || ENV['HIPCHAT_AUTH_TOKEN']
141
-
142
- return unless room_id > 0 && auth_token
143
-
144
- post_data = {
145
- :name => "Deployments",
146
- :privacy => 'private',
147
- :is_archived => false,
148
- :is_guest_accessible => true,
149
- :topic => 'curl',
150
- :message => msg,
151
- :color => 'green',
152
- :owner => { :id => 5 }
153
- }
154
-
155
- url = "https://api.hipchat.com/v2/room/#{room_id}/notification"
156
- cmd = "curl --header \"content-type: application/json\" --header \"Authorization: Bearer #{auth_token}\" -X POST -d \"#{post_data.to_json.gsub('"', '\\"')}\" #{url}"
157
- puts cmd
158
- `#{cmd}`
159
- end
160
-
161
- def layer_instance_ids(layer_ids)
162
- layer_ids.map do |layer_id|
163
- JSON.parse(`aws opsworks describe-instances --layer-id=#{layer_id}`)['Instances'].map{|i| i['InstanceId']}
164
- end.flatten
165
- end
166
-
167
- def relevant_instance_ids(stack_id)
168
- layer_instance_ids(relevant_app_layer_ids(stack_id))
169
- end
170
-
171
- def relevant_app_layer_ids(stack_id)
172
- stack_layers(stack_id).select{|l| l['Name'] =~ /#{@app_layer_name_regex}/i}.map{|layer_data| layer_data['LayerId']}
173
- end
174
-
175
- def stack_layers(stack_id)
176
- JSON.parse(`aws opsworks describe-layers --stack-id=#{stack_id}`)['Layers']
177
- end
178
-
179
- def opsworks_app_types
180
- [
181
- 'aws-flow-ruby',
182
- 'java',
183
- 'rails',
184
- 'php',
185
- 'nodejs',
186
- 'static',
187
- 'other',
188
- ]
189
- end
190
-
191
62
  end
192
63
  end
@@ -0,0 +1,26 @@
1
+ module HipchatHelper
2
+
3
+ def post_deployment_to_hipchat(msg)
4
+ room_id = (@hipchat_room_id || ENV['HIPCHAT_ROOM_ID']).to_i
5
+ auth_token = @hipchat_auth_token || ENV['HIPCHAT_AUTH_TOKEN']
6
+
7
+ return unless room_id > 0 && auth_token
8
+
9
+ post_data = {
10
+ :name => "Deployments",
11
+ :privacy => 'private',
12
+ :is_archived => false,
13
+ :is_guest_accessible => true,
14
+ :topic => 'curl',
15
+ :message => msg,
16
+ :color => 'green',
17
+ :owner => { :id => 5 }
18
+ }
19
+
20
+ url = "https://api.hipchat.com/v2/room/#{room_id}/notification"
21
+ cmd = "curl --header \"content-type: application/json\" --header \"Authorization: Bearer #{auth_token}\" -X POST -d \"#{post_data.to_json.gsub('"', '\\"')}\" #{url}"
22
+ puts cmd
23
+ `#{cmd}`
24
+ end
25
+
26
+ end
@@ -1,3 +1,3 @@
1
1
  module OpsworksShip
2
- VERSION = "1.1.0"
2
+ VERSION = "1.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opsworks_ship
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Seth Ringling
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-08-30 00:00:00.000000000 Z
11
+ date: 2017-09-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -82,7 +82,11 @@ files:
82
82
  - bin/console
83
83
  - bin/setup
84
84
  - lib/opsworks_ship.rb
85
+ - lib/opsworks_ship/aws_data_helper.rb
86
+ - lib/opsworks_ship/aws_deployment_helper.rb
87
+ - lib/opsworks_ship/chef.rb
85
88
  - lib/opsworks_ship/deploy.rb
89
+ - lib/opsworks_ship/hipchat_helper.rb
86
90
  - lib/opsworks_ship/version.rb
87
91
  - opsworks_ship.gemspec
88
92
  homepage: https://github.com/NEWECX/opsworks_ship