cloudfoundry_blue_green_deploy 0.0.1
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 +7 -0
- data/.gitignore +22 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +184 -0
- data/Rakefile +2 -0
- data/cloudfoundry_blue_green_deploy.gemspec +25 -0
- data/lib/cloudfoundry_blue_green_deploy.rb +8 -0
- data/lib/cloudfoundry_blue_green_deploy/app.rb +10 -0
- data/lib/cloudfoundry_blue_green_deploy/blue_green_deploy.rb +144 -0
- data/lib/cloudfoundry_blue_green_deploy/blue_green_deploy_config.rb +108 -0
- data/lib/cloudfoundry_blue_green_deploy/blue_green_deploy_error.rb +3 -0
- data/lib/cloudfoundry_blue_green_deploy/cloudfoundry.rb +90 -0
- data/lib/cloudfoundry_blue_green_deploy/command_line.rb +15 -0
- data/lib/cloudfoundry_blue_green_deploy/railtie.rb +11 -0
- data/lib/cloudfoundry_blue_green_deploy/route.rb +10 -0
- data/lib/cloudfoundry_blue_green_deploy/tasks/cf.rake +28 -0
- data/lib/cloudfoundry_blue_green_deploy/version.rb +3 -0
- data/spec/blue_green_deploy_config_spec.rb +140 -0
- data/spec/blue_green_deploy_spec.rb +287 -0
- data/spec/cloudfoundry_fake.rb +105 -0
- data/spec/cloudfoundry_spec.rb +161 -0
- data/spec/command_line_spec.rb +21 -0
- data/spec/manifest.yml +51 -0
- data/spec/route_spec.rb +13 -0
- data/spec/spec_helper.rb +7 -0
- metadata +134 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 54397c469bcc873ba16da8358aaf7659e5e0a7cd
|
4
|
+
data.tar.gz: 9f27071ae08a3dda2347f6371dddb242a6d8405e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 924f6dfc9bf0c9e813629c0c5adccd0e6671be57c9d9c7c068c1074b8ecb4751b6348227c064e3b1b344f1fc1c97a9f1c3be38082dfbab9182210a9fa68d9c6d
|
7
|
+
data.tar.gz: e90a718e563b61cc450bc0c486c561812e26f09f5a84082a00e417bc4b145e29dc282072fac517d393dfbf1ccd31d5e4b6d3a3b79b3aa18b563926ed1a5d28bd
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 John Ryan and Mariana Lenetis
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,184 @@
|
|
1
|
+
# Overview
|
2
|
+
|
3
|
+
Using a simple deployment process, one can introduce significant (even if planned) downtime of your application.
|
4
|
+
|
5
|
+
If you want to minimize this impact to your site's availability, you might opt to use the Blue/Green deployment approach: http://docs.gopivotal.com/pivotalcf/devguide/deploy-apps/blue-green.html
|
6
|
+
|
7
|
+
This gem provides a Rake task to automate Blue/Green deployment to a Cloud Foundry installation.
|
8
|
+
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
gem 'cloudfoundry_blue_green_deploy'
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install cloudfoundry_blue_green_deploy
|
23
|
+
|
24
|
+
## For first deployment - where app to deploy needs to be specified
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
1. define the blue and green instances of your application(s) in your Cloud Foundry Manifest. (see rules in the next section)
|
29
|
+
2. run
|
30
|
+
|
31
|
+
$ bundle exec rake cf:blue_green_deploy[web-app-name]
|
32
|
+
|
33
|
+
Where "web-app-name" is the "name" attribute in your manifest.yml.
|
34
|
+
The default color for first deployment is blue.
|
35
|
+
You may optionally specify the color that you would like to be the "live" instance on the first deployment.
|
36
|
+
|
37
|
+
### manifest.yml
|
38
|
+
|
39
|
+
Your Cloud Foundry manifest file must comply with the following requirements:
|
40
|
+
|
41
|
+
1. name: two application instances are required. One name ending with "-green" and another ending with "-blue"
|
42
|
+
2. host: the url. One ending with "-green" and the other ending with "-blue"
|
43
|
+
3. domain: required
|
44
|
+
4. command: bundle exec rake cf:on_first_instance db:migrate && bundle exec rails s -p $PORT -e $RAILS_ENV
|
45
|
+
5. services: Optional, only required if there are services that need to be bound
|
46
|
+
|
47
|
+
#### Bare Minimum Example:
|
48
|
+
|
49
|
+
In this example:
|
50
|
+
- Our web application is known to Cloud Foundry as "carrot-soup".
|
51
|
+
- "carrot-soup" has a database service known as "oyster-cracker".
|
52
|
+
|
53
|
+
---
|
54
|
+
applications:
|
55
|
+
|
56
|
+
- name: carrot-soup-green
|
57
|
+
host: la-pong-green
|
58
|
+
domain: cfapps.io
|
59
|
+
command: bundle exec rake cf:on_first_instance db:migrate && bundle exec rails s -p $PORT -e $RAILS_ENV
|
60
|
+
services:
|
61
|
+
- oyster-cracker
|
62
|
+
|
63
|
+
- name: carrot-soup-blue
|
64
|
+
host: la-pong-blue
|
65
|
+
domain: cfapps.io
|
66
|
+
command: bundle exec rake cf:on_first_instance db:migrate && bundle exec rails s -p $PORT -e $RAILS_ENV
|
67
|
+
services:
|
68
|
+
- oyster-cracker
|
69
|
+
|
70
|
+
And perform a blue/green deploy like this:
|
71
|
+
|
72
|
+
$ bundle exec rake cf:blue_green_deploy[carrot-soup]
|
73
|
+
|
74
|
+
## Workers
|
75
|
+
|
76
|
+
Non-trivial applications often require background processes to perform asynchronous jobs (e.g. sending email, importing data from external systems, etc.).
|
77
|
+
If these applications' code are to stay in sync with the web application, they need blue/green treatment as well.
|
78
|
+
|
79
|
+
This Rake task natively supports worker application instances.
|
80
|
+
|
81
|
+
### Usage (with Workers)
|
82
|
+
|
83
|
+
1. define the blue and green instances of your application(s) and workers in your Cloud Foundry Manifest. (see rules in the next section)
|
84
|
+
2. run:
|
85
|
+
|
86
|
+
$ bundle exec rake cf:blue_green_deploy[web-app-name,worker-name,another-worker-name]
|
87
|
+
|
88
|
+
Note:
|
89
|
+
The "web-app-name" is the "name" attribute (without a color) detailed in your manifest.yml
|
90
|
+
The "worker-name" and "another-worker-name" are "name" attributes for 2 separate worker apps as detailed in your manifest.yml
|
91
|
+
Multiple worker apps can be specified as long as they comply with the blue/green deployment requirements in the manifest.yml
|
92
|
+
|
93
|
+
### manifest.yml
|
94
|
+
|
95
|
+
For web application deployment (see requirements above)
|
96
|
+
|
97
|
+
For worker applications
|
98
|
+
1. name: two application instances are required. One name ending with "-green" and another ending with "-blue"
|
99
|
+
2. command:
|
100
|
+
3. path: Relative to the current working directory
|
101
|
+
4. services: Optional, only required if there are services that need to be bound
|
102
|
+
|
103
|
+
#### Example with Workers
|
104
|
+
|
105
|
+
In this example:
|
106
|
+
- Our web application is known to Cloud Foundry as "carrot-soup".
|
107
|
+
- The app "carrot-soup" has a database service known as "oyster-cracker".
|
108
|
+
- We have a worker application named "relish", whose database is known as "creme-fraiche".
|
109
|
+
|
110
|
+
---
|
111
|
+
applications:
|
112
|
+
|
113
|
+
- name: relish-green
|
114
|
+
command: bundle exec rails s -p $PORT -e $RAILS_ENV
|
115
|
+
path: ../relish
|
116
|
+
services:
|
117
|
+
- creme-fraiche
|
118
|
+
|
119
|
+
- name: carrot-soup-green
|
120
|
+
host: la-pong-green
|
121
|
+
domain: cfapps.io
|
122
|
+
size: 1GB
|
123
|
+
path: .
|
124
|
+
command: bundle exec rails s -p $PORT -e $RAILS_ENV
|
125
|
+
services:
|
126
|
+
- oyster-cracker
|
127
|
+
|
128
|
+
- name: carrot-soup-blue
|
129
|
+
host: la-pong-blue
|
130
|
+
domain: cfapps.io
|
131
|
+
size: 1GB
|
132
|
+
path: .
|
133
|
+
command: bundle exec rails s -p $PORT -e $RAILS_ENV
|
134
|
+
services:
|
135
|
+
- oyster-cracker
|
136
|
+
|
137
|
+
- name: relish-blue
|
138
|
+
command: bundle exec rails s -p $PORT -e $RAILS_ENV
|
139
|
+
path: ../relish
|
140
|
+
services:
|
141
|
+
- creme-fraiche
|
142
|
+
|
143
|
+
And perform the blue/green deploy like this:
|
144
|
+
$ bundle exec rake cf:blue_green_deploy[carrot-soup,relish]
|
145
|
+
|
146
|
+
|
147
|
+
# Blue/Green with Shutter
|
148
|
+
|
149
|
+
For blue/green deployments that require a database migration this tool provides the ability to automatically shutter the app during the required downtime. To use this feature, create a shutter app and configure your manifest.yml.
|
150
|
+
|
151
|
+
## Creating a Minimal Shutter App
|
152
|
+
|
153
|
+
1. Add the following to your manifest.yml. Note that the name must match the name of your production application and end in -shutter.
|
154
|
+
|
155
|
+
- name: carrot-soup-shutter
|
156
|
+
command: bundle exec rackup config.ru -p $PORT -E $RACK_ENV
|
157
|
+
path: shutter-app
|
158
|
+
|
159
|
+
2. Create a directory named "shutter-app". In that directory:
|
160
|
+
1. create a Rack config (config.ru):
|
161
|
+
|
162
|
+
class Message
|
163
|
+
def call(env)
|
164
|
+
[200, {"Content-Type" => "text/plain"}, ["Temporarily down for maintenance. Please check back shortly."]]
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
run Message.new
|
169
|
+
|
170
|
+
2. create a minimal Gemfile:
|
171
|
+
|
172
|
+
source 'https://rubygems.org'
|
173
|
+
ruby '2.0.0'
|
174
|
+
|
175
|
+
gem 'rack'
|
176
|
+
|
177
|
+
3. create the Gemfile.lock by running Bundler in the "shutter-app" directory:
|
178
|
+
|
179
|
+
$ bundle install
|
180
|
+
|
181
|
+
|
182
|
+
- Note: as of 05/09/14 deployment using Cloud Foundry's buildpack does not appear to be compatible with ruby version 2.1.0.
|
183
|
+
- Our fail-fast philosophy. We recommend understanding deployment on Cloud Foundry before using this tool.
|
184
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'cloudfoundry_blue_green_deploy/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'cloudfoundry_blue_green_deploy'
|
8
|
+
spec.version = CloudfoundryBlueGreenDeploy::VERSION
|
9
|
+
spec.authors = ['John Ryan and Mariana Lenetis']
|
10
|
+
spec.email = ['jryan@pivotallabs.com', 'mlenetis@pivotallabs.com']
|
11
|
+
spec.summary = %q{Blue-green deployment tool for Cloud Foundry.}
|
12
|
+
spec.description = %q{Blue-green deployment tool for Cloud Foundry. Please see readme.}
|
13
|
+
spec.homepage = ''
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
22
|
+
spec.add_development_dependency 'rake'
|
23
|
+
spec.add_development_dependency 'rspec'
|
24
|
+
spec.add_development_dependency 'awesome_print'
|
25
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
require_relative 'cloudfoundry_blue_green_deploy/app'
|
2
|
+
require_relative 'cloudfoundry_blue_green_deploy/route'
|
3
|
+
require_relative 'cloudfoundry_blue_green_deploy/command_line'
|
4
|
+
require_relative 'cloudfoundry_blue_green_deploy/cloudfoundry'
|
5
|
+
require_relative 'cloudfoundry_blue_green_deploy/blue_green_deploy_error'
|
6
|
+
require_relative 'cloudfoundry_blue_green_deploy/blue_green_deploy_config'
|
7
|
+
require_relative 'cloudfoundry_blue_green_deploy/blue_green_deploy'
|
8
|
+
require_relative 'cloudfoundry_blue_green_deploy/railtie' if defined?(Rails)
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require_relative 'route'
|
2
|
+
require_relative 'cloudfoundry'
|
3
|
+
require_relative 'blue_green_deploy_error'
|
4
|
+
require_relative 'blue_green_deploy_config'
|
5
|
+
|
6
|
+
module CloudfoundryBlueGreenDeploy
|
7
|
+
class InvalidRouteStateError < BlueGreenDeployError; end
|
8
|
+
class InvalidWorkerStateError < BlueGreenDeployError; end
|
9
|
+
|
10
|
+
class BlueGreenDeploy
|
11
|
+
def self.cf
|
12
|
+
Cloudfoundry
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.make_it_so(app_name, worker_apps, deploy_config)
|
16
|
+
hot_app_name = get_hot_web_app(deploy_config.hot_url)
|
17
|
+
both_invalid_and_valid_hot_worker_names = get_hot_worker_names
|
18
|
+
|
19
|
+
is_first_deploy = first_deploy?(hot_app_name, both_invalid_and_valid_hot_worker_names)
|
20
|
+
|
21
|
+
deploy_config.target_color = set_target_color(is_first_deploy, hot_app_name)
|
22
|
+
|
23
|
+
ready_for_takeoff(hot_app_name, both_invalid_and_valid_hot_worker_names, deploy_config)
|
24
|
+
|
25
|
+
with_shutter = deploy_config.with_shutter
|
26
|
+
hot_url = deploy_config.hot_url
|
27
|
+
cold_app = deploy_config.target_web_app_name
|
28
|
+
domain = deploy_config.domain
|
29
|
+
shutter_app_name = deploy_config.shutter_app_name
|
30
|
+
|
31
|
+
if with_shutter
|
32
|
+
cf.push(shutter_app_name)
|
33
|
+
cf.map_route(shutter_app_name, domain, hot_url)
|
34
|
+
cf.unmap_route(hot_app_name, domain, hot_url) if hot_app_name
|
35
|
+
end
|
36
|
+
|
37
|
+
cf.push(cold_app)
|
38
|
+
|
39
|
+
deploy_config.target_worker_app_names.each do |worker_app_name|
|
40
|
+
cf.push(worker_app_name)
|
41
|
+
unless is_first_deploy
|
42
|
+
to_be_cold_worker = BlueGreenDeployConfig.toggle_app_color(worker_app_name)
|
43
|
+
cf.stop(to_be_cold_worker)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
if with_shutter
|
48
|
+
cf.map_route(cold_app, domain, hot_url)
|
49
|
+
cf.unmap_route(shutter_app_name, domain, hot_url)
|
50
|
+
else
|
51
|
+
make_hot(app_name, deploy_config)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.set_target_color(is_first_deploy, hot_app_name)
|
56
|
+
if is_first_deploy
|
57
|
+
'blue'
|
58
|
+
else
|
59
|
+
determine_target_color(hot_app_name) unless hot_app_name.nil?
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
def self.ready_for_takeoff(hot_app_name, both_invalid_and_valid_hot_worker_names, deploy_config)
|
65
|
+
unless first_deploy?(hot_app_name, both_invalid_and_valid_hot_worker_names)
|
66
|
+
ensure_there_is_a_hot_instance(deploy_config, hot_app_name)
|
67
|
+
ensure_hot_instance_is_not_target(deploy_config, hot_app_name)
|
68
|
+
ensure_hot_workers_are_not_target(deploy_config)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.first_deploy?(hot_app_name, both_invalid_and_valid_hot_worker_names)
|
73
|
+
hot_app_name.nil? && both_invalid_and_valid_hot_worker_names.empty?
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.ensure_there_is_a_hot_instance(deploy_config, hot_app_name)
|
77
|
+
if hot_app_name.nil?
|
78
|
+
raise InvalidRouteStateError.new(
|
79
|
+
"There is no route mapped from #{deploy_config.hot_url} to an app.")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.ensure_hot_instance_is_not_target(deploy_config, hot_app_name)
|
84
|
+
if deploy_config.is_in_target?(hot_app_name)
|
85
|
+
raise InvalidRouteStateError.new(
|
86
|
+
"The app \"#{hot_app_name}\" is already hot (target color is #{deploy_config.target_color}).")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.ensure_hot_workers_are_not_target(deploy_config)
|
91
|
+
apps = cf.apps
|
92
|
+
|
93
|
+
deploy_config.target_worker_app_names.each do |hot_worker|
|
94
|
+
if deploy_config.is_in_target?(hot_worker) && invalid_worker?(hot_worker, apps)
|
95
|
+
raise InvalidWorkerStateError.new(
|
96
|
+
"Worker #{hot_worker} is already hot (target color is #{deploy_config.target_color}).")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.invalid_worker?(hot_worker, apps)
|
102
|
+
apps.each do |app|
|
103
|
+
if app.name == hot_worker && app.state == 'started'
|
104
|
+
return true
|
105
|
+
end
|
106
|
+
end
|
107
|
+
return false
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.get_color_stem(hot_app_name)
|
111
|
+
hot_app_name.slice((hot_app_name.rindex('-') + 1)..(hot_app_name.length))
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.determine_target_color(hot_app_name)
|
115
|
+
target_color = get_color_stem(hot_app_name)
|
116
|
+
BlueGreenDeployConfig.toggle_color(target_color)
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.make_hot(app_name, deploy_config)
|
120
|
+
hot_url = deploy_config.hot_url
|
121
|
+
hot_app = get_hot_web_app(hot_url)
|
122
|
+
cold_app = deploy_config.target_web_app_name
|
123
|
+
domain = deploy_config.domain
|
124
|
+
|
125
|
+
cf.map_route(cold_app, domain, hot_url)
|
126
|
+
cf.unmap_route(hot_app, domain, hot_url) if hot_app
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.get_hot_web_app(hot_url)
|
130
|
+
cf_routes = cf.routes
|
131
|
+
hot_route = cf_routes.find { |route| route.host == hot_url }
|
132
|
+
hot_route.nil? ? nil : hot_route.app
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.get_hot_worker_names
|
136
|
+
cf_apps = cf.apps
|
137
|
+
hot_names = []
|
138
|
+
cf_apps.each do |app|
|
139
|
+
hot_names << app.name if app.state == 'started'
|
140
|
+
end
|
141
|
+
hot_names
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require_relative 'blue_green_deploy_error'
|
2
|
+
|
3
|
+
module CloudfoundryBlueGreenDeploy
|
4
|
+
class InvalidManifestError < BlueGreenDeployError; end
|
5
|
+
|
6
|
+
class BlueGreenDeployConfig
|
7
|
+
attr_reader :hot_url, :worker_app_names, :domain, :with_shutter
|
8
|
+
attr_accessor :target_color
|
9
|
+
|
10
|
+
def initialize(cf_manifest, web_app_name, worker_app_names, with_shutter = nil)
|
11
|
+
manifest = cf_manifest['applications']
|
12
|
+
|
13
|
+
self.class.valid_name_check(web_app_name, worker_app_names, manifest)
|
14
|
+
|
15
|
+
item = manifest.find { |item| self.class.strip_color(item['name']) == web_app_name }
|
16
|
+
if item.nil?
|
17
|
+
raise InvalidManifestError.new("Could not find \"#{web_app_name}-green\" nor \"#{web_app_name}-blue\" in the Cloud Foundry manifest:\n" +
|
18
|
+
"#{cf_manifest.inspect}")
|
19
|
+
end
|
20
|
+
|
21
|
+
host = item['host']
|
22
|
+
if host.nil?
|
23
|
+
raise InvalidManifestError.new(
|
24
|
+
"Could not find the \"host\" property associated with the \"#{item['name']}\" application in the Cloud Foundry manifest:\n" +
|
25
|
+
"#{cf_manifest.inspect}")
|
26
|
+
end
|
27
|
+
|
28
|
+
@domain = item['domain']
|
29
|
+
|
30
|
+
if @domain.nil?
|
31
|
+
raise InvalidManifestError.new(
|
32
|
+
"Could not find the \"domain\" property associated with the \"#{item['name']}\" application in the Cloud Foundry manifest:\n" +
|
33
|
+
"#{cf_manifest.inspect}")
|
34
|
+
end
|
35
|
+
|
36
|
+
@web_app_name = web_app_name
|
37
|
+
@hot_url = host.slice(0, host.rindex('-'))
|
38
|
+
@worker_app_names = worker_app_names
|
39
|
+
@with_shutter = with_shutter
|
40
|
+
@target_color = nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def shutter_app_name
|
44
|
+
"#{@web_app_name}-shutter"
|
45
|
+
end
|
46
|
+
|
47
|
+
def target_web_app_name
|
48
|
+
"#{@web_app_name}-#{@target_color}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def is_in_target?(app)
|
52
|
+
self.class.get_color_stem(app) == @target_color
|
53
|
+
end
|
54
|
+
|
55
|
+
def target_worker_app_names
|
56
|
+
@worker_app_names.map do |app|
|
57
|
+
"#{app}-#{@target_color}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
def self.valid_name_check(web_app_name, worker_app_names, manifest)
|
63
|
+
all_apps = all_app_names(web_app_name, worker_app_names)
|
64
|
+
all_apps.each do |app_name|
|
65
|
+
if manifest.none? { |record| record['name'] == app_name }
|
66
|
+
raise InvalidManifestError.new("Could not find \"#{app_name}\" in the Cloud Foundry manifest:\n" +
|
67
|
+
"#{manifest}")
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.strip_color(app_name_with_color)
|
74
|
+
app_name_with_color.slice((0..app_name_with_color.rindex('-') - 1))
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.toggle_app_color(target_app_name)
|
78
|
+
new_color = toggle_color(get_color_stem(target_app_name))
|
79
|
+
new_app = target_app_name.slice(0..(target_app_name.rindex('-') - 1))
|
80
|
+
new_app = "#{new_app}-#{new_color}"
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.get_color_stem(app_name)
|
84
|
+
app_name.slice((app_name.rindex('-') + 1)..(app_name.length))
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.toggle_color(target_color)
|
88
|
+
target_color == 'green' ? 'blue' : 'green'
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.colorize_name(app_name, color)
|
92
|
+
"#{app_name}-#{color}"
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.all_app_names(web_app_name, worker_app_names)
|
96
|
+
all_app_names = []
|
97
|
+
all_app_names << colorize_name(web_app_name, 'blue')
|
98
|
+
all_app_names << colorize_name(web_app_name, 'green')
|
99
|
+
|
100
|
+
worker_app_names.each do |app|
|
101
|
+
all_app_names << colorize_name(app, 'green')
|
102
|
+
all_app_names << colorize_name(app, 'blue')
|
103
|
+
end
|
104
|
+
|
105
|
+
all_app_names
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|