cf-deploy 0.1.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.
- data/LICENSE +22 -0
- data/README.md +251 -0
- data/lib/cf-deploy.rb +1 -0
- data/lib/cf/deploy.rb +70 -0
- data/lib/cf/deploy/commands.rb +33 -0
- data/lib/cf/deploy/config.rb +81 -0
- data/lib/cf/deploy/env_config.rb +57 -0
- data/lib/cf/deploy/version.rb +5 -0
- data/spec/blue_green_task_spec.rb +60 -0
- data/spec/deploy_task_spec.rb +91 -0
- data/spec/login_task_spec.rb +72 -0
- data/spec/rake_tasks_spec.rb +61 -0
- data/spec/spec_helper.rb +9 -0
- metadata +145 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Made by Made Ltd
|
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,251 @@
|
|
1
|
+
# Rake tasks for deploying to CloudFoundry v6+
|
2
|
+
|
3
|
+
This gem provides the functionality you need to deploy your rails application to
|
4
|
+
a [CloudFoundry][CloudFoundry] provider like [Pivotal][Pivotal].
|
5
|
+
|
6
|
+
With `cf-deploy` you can:
|
7
|
+
|
8
|
+
* Define your CloudFoundry connection details in your Rakefile or using
|
9
|
+
environment variables
|
10
|
+
* Implement blue/green deployment
|
11
|
+
* Hook into your existing rake tasks for preparing deploys/syncing assets
|
12
|
+
|
13
|
+
## Basics
|
14
|
+
|
15
|
+
The functionality comes in the shape of generated rake tasks. You require this
|
16
|
+
gem in your `Rakefile` and call the `.rake_tasks!` setup method.
|
17
|
+
|
18
|
+
``` ruby
|
19
|
+
require 'cf-deploy'
|
20
|
+
CF::Deploy.rake_tasks!
|
21
|
+
```
|
22
|
+
|
23
|
+
By default tasks will be created for each manifest in your `manifests/` folder.
|
24
|
+
If you have a `staging.yml` and `production.yml` you can now run the following
|
25
|
+
commands:
|
26
|
+
|
27
|
+
``` sh
|
28
|
+
bundle exec rake cf:deploy:staging
|
29
|
+
bundle exec rake cf:deploy:production
|
30
|
+
```
|
31
|
+
|
32
|
+
This however mimics the commands `cf push -f manifests/staging.yml` and
|
33
|
+
`cf push -f manifests/production.yml`. Not really anything helpful or new.
|
34
|
+
Things start to get more exciting when you define your environments in your
|
35
|
+
`Rakefile` along with their task dependencies just like normal rake task syntax.
|
36
|
+
|
37
|
+
``` ruby
|
38
|
+
require 'cf-deploy'
|
39
|
+
|
40
|
+
CF::Deploy.rake_tasks! do
|
41
|
+
environment :staging => 'assets:precompile'
|
42
|
+
environment :production => [:clean, 'assets:precompile']
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
Now when running `cf:deploy:staging` and `cf:deploy:production` the prerequisite
|
47
|
+
tasks will be run first.
|
48
|
+
|
49
|
+
The next thing to talk about is route mapping. You can define a route in a an
|
50
|
+
environment block like so:
|
51
|
+
|
52
|
+
``` ruby
|
53
|
+
require 'cf-deploy'
|
54
|
+
|
55
|
+
CF::Deploy.rake_tasks! do
|
56
|
+
environment :staging => 'assets:precompile' do
|
57
|
+
route 'example.com', 'staging'
|
58
|
+
end
|
59
|
+
|
60
|
+
environment :production => [:clean, 'assets:precompile'] do
|
61
|
+
route 'example.com'
|
62
|
+
route 'example.com', 'admin'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
Any time you deploy an environment with one or more routes defined the routes
|
68
|
+
will be mapped to all applications in the environment's manifest.
|
69
|
+
|
70
|
+
And then things get super interesting when you start talking blue/green.
|
71
|
+
|
72
|
+
## What is blue/green deployment?
|
73
|
+
|
74
|
+
Simply put, blue/green deployment allows you to deploy a new version of your
|
75
|
+
app, test it on a private URL and then direct your traffic to the new version
|
76
|
+
when you are ready.
|
77
|
+
|
78
|
+
You have two applications for one environment, say production. One version is
|
79
|
+
called green, the other is blue. The first time you deploy your app you either
|
80
|
+
go to blue or green. Thereafter, any changes you want to deploy you send to the
|
81
|
+
color that doesn't have your production domain pointed at it. You test it on
|
82
|
+
a private URL and then when you're happy you flip your domain to point at that.
|
83
|
+
If something then goes wrong you can then flip your domain back to the last
|
84
|
+
working version.
|
85
|
+
|
86
|
+
This gem provides rake tasks for you to deploy using this methodology as well
|
87
|
+
as the standard single app deployment process on a CloudFoundry provider.
|
88
|
+
|
89
|
+
For example you might have a straight forward deployment for staging but use
|
90
|
+
the blue/green strategy for production. Here is what your Rakefile might look
|
91
|
+
like:
|
92
|
+
|
93
|
+
``` ruby
|
94
|
+
require 'cf-deploy'
|
95
|
+
|
96
|
+
CF::Deploy.rake_tasks! do
|
97
|
+
environment :staging => 'assets:precompile'
|
98
|
+
|
99
|
+
environment :production => 'assets:precompile' do
|
100
|
+
route 'example-app.io'
|
101
|
+
end
|
102
|
+
end
|
103
|
+
```
|
104
|
+
|
105
|
+
You should also have `manifests/production_blue.yml` and
|
106
|
+
`manifests/production_green.yml` defined.
|
107
|
+
|
108
|
+
When you run `cf:deploy:production` for the first time (assuming neither
|
109
|
+
`production_blue.yml` or `production_green.yml` are deployed) your blue app will
|
110
|
+
be deployed and route setup.
|
111
|
+
|
112
|
+
Running `cf:deploy:production` thereafter will deploy which ever version isn't
|
113
|
+
currently deployed. Your route(s) will not be mapped automatically this time.
|
114
|
+
Nows your chance to checkout your new deployment using an alternate route. When
|
115
|
+
you're happy and want to map your route across run:
|
116
|
+
|
117
|
+
``` sh
|
118
|
+
bundle exec rake cf:deploy:production:flip
|
119
|
+
```
|
120
|
+
|
121
|
+
## Installation
|
122
|
+
|
123
|
+
You need the `cf` command installed already. Grab the latest release from
|
124
|
+
the [CloudFoundry CLI][cli] repo on github.
|
125
|
+
|
126
|
+
You then need to install this gem in your project's `Gemfile`:
|
127
|
+
|
128
|
+
``` ruby
|
129
|
+
gem 'cf-deploy', '0.1.0'
|
130
|
+
```
|
131
|
+
|
132
|
+
### Defining CloudFoundry details in your Rakefile
|
133
|
+
|
134
|
+
You can configure some or all of your CloudFoundry details when calling
|
135
|
+
`CF::Deploy.rake_tasks!`.
|
136
|
+
|
137
|
+
``` ruby
|
138
|
+
require 'cf-deploy'
|
139
|
+
|
140
|
+
CF::Deploy.rake_tasks! do
|
141
|
+
api 'api.run.pivotal.io'
|
142
|
+
username 'example@example.com'
|
143
|
+
password 'SOMETHING'
|
144
|
+
organisation 'Made'
|
145
|
+
space 'development'
|
146
|
+
|
147
|
+
environment :staging => 'assets:precompile'
|
148
|
+
|
149
|
+
environment :production => 'assets:precompile' do
|
150
|
+
route 'example-app.io'
|
151
|
+
end
|
152
|
+
end
|
153
|
+
```
|
154
|
+
|
155
|
+
All are optional. If you do not provide any you will be prompted when running
|
156
|
+
the rake tasks.
|
157
|
+
|
158
|
+
### Defining CloudFoundry details using ENV variables
|
159
|
+
|
160
|
+
Instead of defining your CloudFoundry login details in your Rakefile and
|
161
|
+
committing them to your code repository you can instead provide them using
|
162
|
+
ENV variables on your command line:
|
163
|
+
|
164
|
+
``` sh
|
165
|
+
export CF_API=api.run.pivotal.io
|
166
|
+
export CF_USERNAME=example@example.com
|
167
|
+
export CF_PASSWORD=SOMETHING
|
168
|
+
export CF_ORG=Made
|
169
|
+
export CF_SPACE=development
|
170
|
+
```
|
171
|
+
|
172
|
+
Now you can run any of the `cf-deploy` rake tasks providing you have called
|
173
|
+
`CF::Deploy.rake_tasks!` in your `Rakefile`.
|
174
|
+
|
175
|
+
## Commands
|
176
|
+
|
177
|
+
### Deploying an environment
|
178
|
+
|
179
|
+
If you defined a staging environment in your Rakefile the following task will
|
180
|
+
have been created:
|
181
|
+
|
182
|
+
```
|
183
|
+
bundle exec rake cf:deploy:staging
|
184
|
+
```
|
185
|
+
|
186
|
+
Run this to deploy out your staging environment.
|
187
|
+
|
188
|
+
Any environment you define will have a task created named `cf:deploy:#{env}`.
|
189
|
+
|
190
|
+
### Deploy the next blue/green environment
|
191
|
+
|
192
|
+
If you have defined CloudFoundry manifest files matching `manifests/*_blue.yml`
|
193
|
+
and `manifests/*_green.yml` you will be able to call `rake cf:deploy:*` without
|
194
|
+
the `_blue` or `_green`. For example with `production_blue.yml` and
|
195
|
+
`production_green.yml` you can call the following:
|
196
|
+
|
197
|
+
```
|
198
|
+
bundle exec rake cf:deploy:production
|
199
|
+
```
|
200
|
+
|
201
|
+
Running the deploy task for an env with blue and green manifests will trigger a
|
202
|
+
lookup to see which env is currently deployed. The task will then start
|
203
|
+
deploying the other production color, so if green is currently deployed then
|
204
|
+
blue will be deployed. If neither is currently deployed, blue will be deployed
|
205
|
+
first.
|
206
|
+
|
207
|
+
Once deployed your routing will still be pointing to the *previous deployment*.
|
208
|
+
If you run the same task again, the same environment will be deployed. That is
|
209
|
+
if green was deployed, and then you run the task, blue will be deployed, if you
|
210
|
+
run the task again, blue will be deployed again. This is because we work out
|
211
|
+
the current deployment based on where your routes are pointing and since the
|
212
|
+
deploy command for blue green environments doesn't map routes the current
|
213
|
+
deployment will not change.
|
214
|
+
|
215
|
+
#### First time proviso
|
216
|
+
|
217
|
+
This isn't the case for a first time deploy. The first time you deploy your
|
218
|
+
blue environment will be deployed and any defined routes will be mapped to all
|
219
|
+
apps defined in your blue manifest.
|
220
|
+
|
221
|
+
### Switch routes over to new environment
|
222
|
+
|
223
|
+
In order to flip your routes from blue to green or vice-versa you need to run
|
224
|
+
the following task.
|
225
|
+
|
226
|
+
```
|
227
|
+
bundle exec rake cf:deploy:production:flip
|
228
|
+
```
|
229
|
+
|
230
|
+
This will go ahead and map routes to whatever color the routes aren't mapped to
|
231
|
+
and then unmap the other color. At this point your new production will be
|
232
|
+
deployed and live.
|
233
|
+
|
234
|
+
## Credits
|
235
|
+
|
236
|
+
[][made]
|
237
|
+
|
238
|
+
Developed and maintained by [Made][made]. Key contributions:
|
239
|
+
|
240
|
+
* [Luke Morton](https://github.com/DrPheltRight)
|
241
|
+
|
242
|
+
## License
|
243
|
+
|
244
|
+
Copyright © 2014 Made by Made Ltd. It is free software, and may be
|
245
|
+
redistributed under the terms specified in the [MIT-LICENSE][license] file.
|
246
|
+
|
247
|
+
[CloudFoundry]: http://www.cloudfoundry.org/
|
248
|
+
[Pivotal]: https://run.pivotal.io/
|
249
|
+
[cli]: https://github.com/cloudfoundry/cli/releases
|
250
|
+
[made]: http://www.madetech.co.uk?ref=github&repo=ydtd_frontend
|
251
|
+
[license]: https://github.com/madebymade/cf-deploy/blob/master/LICENSE
|
data/lib/cf-deploy.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'cf/deploy'
|
data/lib/cf/deploy.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'cf/deploy/version'
|
2
|
+
require 'cf/deploy/config'
|
3
|
+
require 'cf/deploy/env_config'
|
4
|
+
require 'cf/deploy/commands'
|
5
|
+
require 'rake'
|
6
|
+
|
7
|
+
module CF
|
8
|
+
class Deploy
|
9
|
+
class << self
|
10
|
+
def rake_tasks!(&block)
|
11
|
+
new(Config.new(&block)).tasks
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
include Commands
|
16
|
+
|
17
|
+
attr_accessor :config
|
18
|
+
|
19
|
+
def initialize(config)
|
20
|
+
@config = config
|
21
|
+
end
|
22
|
+
|
23
|
+
def tasks
|
24
|
+
[define_login_task].concat(deploy_tasks)
|
25
|
+
end
|
26
|
+
|
27
|
+
def deploy_tasks
|
28
|
+
config[:environments].map { |env| define_deploy_task(env) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def define_login_task
|
32
|
+
return Rake::Task['cf:login'] if Rake::Task.task_defined?('cf:login')
|
33
|
+
|
34
|
+
Rake::Task.define_task('cf:login') do
|
35
|
+
login(config)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def define_deploy_task(env)
|
40
|
+
blue_green_task(env) if env[:deployments].size > 1
|
41
|
+
|
42
|
+
env[:deployments].each do |deployment|
|
43
|
+
Rake::Task.define_task(deployment[:task_name] => env[:deps]) do
|
44
|
+
push(deployment[:manifest])
|
45
|
+
|
46
|
+
env[:routes].each do |route|
|
47
|
+
deployment[:app_names].each do |app_name|
|
48
|
+
map_route(route, app_name)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def first_domain(env)
|
56
|
+
env[:routes].first.values_at(:host, :domain).compact.join('.')
|
57
|
+
end
|
58
|
+
|
59
|
+
def next_production(env)
|
60
|
+
current_production(first_domain(env)) != 'blue' ? 'blue' : 'green'
|
61
|
+
end
|
62
|
+
|
63
|
+
def blue_green_task(env)
|
64
|
+
Rake::Task.define_task(env[:task_name] => env[:deps]) do
|
65
|
+
task_name = EnvConfig.task_name("#{env[:name]}_#{next_production(env)}")
|
66
|
+
Rake::Task[task_name].invoke
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module CF
|
2
|
+
class Deploy
|
3
|
+
module Commands
|
4
|
+
def login(config)
|
5
|
+
login_cmd = ['cf login']
|
6
|
+
|
7
|
+
login_cmd << Config::VALID_CF_KEYS
|
8
|
+
.reject { |key| config[key].nil? }
|
9
|
+
.map { |key| "-#{key.to_s[0]} #{config[key]}" }
|
10
|
+
|
11
|
+
Kernel.system(login_cmd.flatten.join(' '))
|
12
|
+
end
|
13
|
+
|
14
|
+
def push(manifest)
|
15
|
+
Kernel.system("cf push -f #{manifest}")
|
16
|
+
end
|
17
|
+
|
18
|
+
def map_route(route, app_name)
|
19
|
+
map_cmd = "cf map-route #{app_name} #{route[:domain]}"
|
20
|
+
map_cmd = "#{map_cmd} -n #{route[:hostname]}" unless route[:hostname].nil?
|
21
|
+
Kernel.system(map_cmd)
|
22
|
+
end
|
23
|
+
|
24
|
+
def current_production(domain)
|
25
|
+
io = IO.popen("cf routes | grep #{domain}")
|
26
|
+
matches = /(blue|green)/.match(io.read)
|
27
|
+
io.close
|
28
|
+
return if matches.nil?
|
29
|
+
matches[1].strip
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module CF
|
2
|
+
class Deploy
|
3
|
+
class Config < Hash
|
4
|
+
VALID_CF_KEYS = [:api, :username, :password, :organisation, :space]
|
5
|
+
|
6
|
+
attr_reader :environments_to_be_loaded
|
7
|
+
|
8
|
+
def initialize(&block)
|
9
|
+
@environments_to_be_loaded = []
|
10
|
+
|
11
|
+
merge!(:manifest_glob => 'manifests/*',
|
12
|
+
:api => nil,
|
13
|
+
:username => nil,
|
14
|
+
:password => nil,
|
15
|
+
:organisation => nil,
|
16
|
+
:space => nil)
|
17
|
+
|
18
|
+
instance_eval(&block) if block_given?
|
19
|
+
|
20
|
+
self[:environments] = environments(manifests_by_env)
|
21
|
+
end
|
22
|
+
|
23
|
+
def [](key)
|
24
|
+
from_env(key) || super
|
25
|
+
end
|
26
|
+
|
27
|
+
def from_env(key)
|
28
|
+
ENV["CF_#{key.upcase}"] if VALID_CF_KEYS.include?(key)
|
29
|
+
end
|
30
|
+
|
31
|
+
def manifests_by_env
|
32
|
+
Dir[self[:manifest_glob]].reduce({}) do |envs, manifest|
|
33
|
+
if manifest =~ /_blue.yml$/
|
34
|
+
env = File.basename(manifest, '_blue.yml').to_sym
|
35
|
+
elsif manifest =~ /_green.yml$/
|
36
|
+
env = File.basename(manifest, '_green.yml').to_sym
|
37
|
+
else
|
38
|
+
env = File.basename(manifest, '.yml').to_sym
|
39
|
+
end
|
40
|
+
|
41
|
+
envs[env] ||= []
|
42
|
+
envs[env] << manifest
|
43
|
+
envs
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def environments(manifests_by_env)
|
48
|
+
environments = []
|
49
|
+
|
50
|
+
environments_to_be_loaded.each do |(env, block)|
|
51
|
+
if env.is_a?(Hash)
|
52
|
+
name, deps = env.first
|
53
|
+
deps = (['cf:login'] << deps).flatten
|
54
|
+
else
|
55
|
+
name = env
|
56
|
+
deps = ['cf:login']
|
57
|
+
end
|
58
|
+
|
59
|
+
manifests = manifests_by_env.delete(name) || []
|
60
|
+
environments << EnvConfig.new(name, deps, manifests, &block)
|
61
|
+
end
|
62
|
+
|
63
|
+
manifests_by_env.each do |(name, manifests)|
|
64
|
+
environments << EnvConfig.new(name, ['cf:login'], manifests)
|
65
|
+
end
|
66
|
+
|
67
|
+
environments
|
68
|
+
end
|
69
|
+
|
70
|
+
# Config setter methods
|
71
|
+
#
|
72
|
+
def manifest_glob(glob) self[:manifest_glob] = glob end
|
73
|
+
def api(api) self[:api] = api end
|
74
|
+
def username(username) self[:username] = username end
|
75
|
+
def password(password) self[:password] = password end
|
76
|
+
def organisation(organisation) self[:organisation] = organisation end
|
77
|
+
def space(space) self[:space] = space end
|
78
|
+
def environment(env, &block) @environments_to_be_loaded << [env, block] end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module CF
|
4
|
+
class Deploy
|
5
|
+
class EnvConfig < Hash
|
6
|
+
def self.task_name(name)
|
7
|
+
"cf:deploy:#{name}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(name, deps, manifests, &block)
|
11
|
+
merge!(:name => name,
|
12
|
+
:task_name => EnvConfig.task_name(name),
|
13
|
+
:deps => deps,
|
14
|
+
:routes => [],
|
15
|
+
:manifests => manifests)
|
16
|
+
|
17
|
+
instance_eval(&block) if block_given?
|
18
|
+
|
19
|
+
self[:deployments] = deployments
|
20
|
+
end
|
21
|
+
|
22
|
+
def deployments
|
23
|
+
self[:manifests].map { |manifest| deployment_for_manifest(manifest) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def deployment_for_manifest(manifest)
|
27
|
+
if self[:manifests].size > 1
|
28
|
+
task_name = EnvConfig.task_name(File.basename(manifest, '.yml').to_sym)
|
29
|
+
else
|
30
|
+
task_name = self[:task_name]
|
31
|
+
end
|
32
|
+
|
33
|
+
{:task_name => task_name,
|
34
|
+
:manifest => manifest,
|
35
|
+
:app_names => app_names_for_manifest(manifest)}
|
36
|
+
end
|
37
|
+
|
38
|
+
def app_names_for_manifest(manifest)
|
39
|
+
YAML.load_file(manifest)['applications'].map { |a| a['name'] }
|
40
|
+
end
|
41
|
+
|
42
|
+
# Environment config setter methods
|
43
|
+
#
|
44
|
+
def manifest(manifest)
|
45
|
+
self[:manifests] << manifest
|
46
|
+
end
|
47
|
+
|
48
|
+
def manifests(manifests)
|
49
|
+
self[:manifests].concat(manifests)
|
50
|
+
end
|
51
|
+
|
52
|
+
def route(domain, hostname = nil)
|
53
|
+
self[:routes] << {:domain => domain, :hostname => hostname}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'cf-deploy'
|
3
|
+
require 'rake'
|
4
|
+
|
5
|
+
describe CF::Deploy do
|
6
|
+
before :each do
|
7
|
+
Rake::Task.clear
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'Blue/green deployment task' do
|
11
|
+
let :rake_tasks! do
|
12
|
+
Dir.chdir('spec/') do
|
13
|
+
described_class.rake_tasks! do
|
14
|
+
environment :production do
|
15
|
+
route 'example.com'
|
16
|
+
route 'example.com', '2'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should exist if *_blue.yml and *_green.yml manifests exist' do
|
23
|
+
rake_tasks!
|
24
|
+
expect(Rake::Task['cf:deploy:production']).to be_a(Rake::Task)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should deploy blue if not currently deployed' do
|
28
|
+
rake_tasks!
|
29
|
+
expect(Kernel).to receive(:system).with('cf login').ordered
|
30
|
+
expect(IO).to receive(:popen).with('cf routes | grep example.com') { double(:read => '', :close => nil) }
|
31
|
+
expect(Kernel).to receive(:system).with('cf push -f manifests/production_blue.yml').ordered
|
32
|
+
expect(Kernel).to receive(:system).with('cf map-route production-blue-app example.com').ordered
|
33
|
+
expect(Kernel).to receive(:system).with('cf map-route production-blue-app example.com -n 2').ordered
|
34
|
+
Rake::Task['cf:deploy:production'].invoke
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should deploy blue if green currently deployed' do
|
38
|
+
rake_tasks!
|
39
|
+
expect(Kernel).to receive(:system).with('cf login').ordered
|
40
|
+
expect(IO).to receive(:popen).with('cf routes | grep example.com') { double(:read => 'production-green-app', :close => nil) }
|
41
|
+
expect(Kernel).to receive(:system).with('cf push -f manifests/production_blue.yml').ordered
|
42
|
+
expect(Kernel).to receive(:system).with('cf map-route production-blue-app example.com').ordered
|
43
|
+
expect(Kernel).to receive(:system).with('cf map-route production-blue-app example.com -n 2').ordered
|
44
|
+
Rake::Task['cf:deploy:production'].invoke
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should deploy green if blue currently deployed' do
|
48
|
+
rake_tasks!
|
49
|
+
expect(Kernel).to receive(:system).with('cf login').ordered
|
50
|
+
expect(IO).to receive(:popen).with('cf routes | grep example.com') { double(:read => 'production-blue-app', :close => nil) }
|
51
|
+
expect(Kernel).to receive(:system).with('cf push -f manifests/production_green.yml').ordered
|
52
|
+
expect(Kernel).to receive(:system).with('cf map-route production-green-app example.com').ordered
|
53
|
+
expect(Kernel).to receive(:system).with('cf map-route production-green-app example.com -n 2').ordered
|
54
|
+
Rake::Task['cf:deploy:production'].invoke
|
55
|
+
end
|
56
|
+
|
57
|
+
xit 'should throw exception if no routes defined for blue/green task' do
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'cf-deploy'
|
3
|
+
require 'rake'
|
4
|
+
|
5
|
+
describe CF::Deploy do
|
6
|
+
before :each do
|
7
|
+
Rake::Task.clear
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'Rake::Task[cf:deploy:XX]' do
|
11
|
+
it 'should run a manifest' do
|
12
|
+
Dir.chdir('spec/') do
|
13
|
+
described_class.rake_tasks!
|
14
|
+
end
|
15
|
+
|
16
|
+
expect(Kernel).to receive(:system).with('cf login').ordered
|
17
|
+
expect(Kernel).to receive(:system).with('cf push -f manifests/staging.yml').ordered
|
18
|
+
Rake::Task['cf:deploy:staging'].invoke
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should setup a route if defined after pushing manifest' do
|
22
|
+
Dir.chdir('spec/') do
|
23
|
+
described_class.rake_tasks! do
|
24
|
+
environment :test do
|
25
|
+
route 'testexample.com'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
expect(Kernel).to receive(:system).with('cf login').ordered
|
31
|
+
expect(Kernel).to receive(:system).with('cf push -f manifests/test.yml').ordered
|
32
|
+
expect(Kernel).to receive(:system).with('cf map-route test-app testexample.com').ordered
|
33
|
+
Rake::Task['cf:deploy:test'].invoke
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should setup a route with a hostname if defined' do
|
37
|
+
Dir.chdir('spec/') do
|
38
|
+
described_class.rake_tasks! do
|
39
|
+
environment :test do
|
40
|
+
route 'example.com', 'test'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
expect(Kernel).to receive(:system).with('cf login').ordered
|
46
|
+
expect(Kernel).to receive(:system).with('cf push -f manifests/test.yml').ordered
|
47
|
+
expect(Kernel).to receive(:system).with('cf map-route test-app example.com -n test')
|
48
|
+
Rake::Task['cf:deploy:test'].invoke
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should setup multiple routes if defined' do
|
52
|
+
Dir.chdir('spec/') do
|
53
|
+
described_class.rake_tasks! do
|
54
|
+
environment :test do
|
55
|
+
route 'example.com'
|
56
|
+
route 'example.com', '2'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
expect(Kernel).to receive(:system).with('cf login').ordered
|
62
|
+
expect(Kernel).to receive(:system).with('cf push -f manifests/test.yml').ordered
|
63
|
+
expect(Kernel).to receive(:system).with('cf map-route test-app example.com').ordered
|
64
|
+
expect(Kernel).to receive(:system).with('cf map-route test-app example.com -n 2').ordered
|
65
|
+
Rake::Task['cf:deploy:test'].invoke
|
66
|
+
end
|
67
|
+
|
68
|
+
xit 'should not map routes if push fails' do
|
69
|
+
end
|
70
|
+
|
71
|
+
xit 'should throw decent error if manifest does not exist' do
|
72
|
+
end
|
73
|
+
|
74
|
+
xit 'should throw decent error if manifest invalid' do
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should allow individual manifest to be specified' do
|
78
|
+
Dir.chdir('spec/') do
|
79
|
+
CF::Deploy.rake_tasks! do
|
80
|
+
environment :custom_manifest do
|
81
|
+
manifest 'manifests/staging.yml'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
expect(Kernel).to receive(:system).with('cf login').ordered
|
87
|
+
expect(Kernel).to receive(:system).with('cf push -f manifests/staging.yml').ordered
|
88
|
+
Rake::Task['cf:deploy:custom_manifest'].invoke
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'cf-deploy'
|
3
|
+
require 'rake'
|
4
|
+
|
5
|
+
describe CF::Deploy do
|
6
|
+
before :each do
|
7
|
+
Rake::Task.clear
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'Rake::Task[cf:login]' do
|
11
|
+
it 'should run `cf login` without arguments if none provided' do
|
12
|
+
described_class.rake_tasks!
|
13
|
+
expect(Kernel).to receive(:system).with('cf login')
|
14
|
+
Rake::Task['cf:login'].invoke
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should include defined details' do
|
18
|
+
described_class.rake_tasks! do
|
19
|
+
api 'api.run.pivotal.io'
|
20
|
+
end
|
21
|
+
|
22
|
+
expect(Kernel).to receive(:system).with('cf login -a api.run.pivotal.io')
|
23
|
+
Rake::Task['cf:login'].invoke
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should include all defined details' do
|
27
|
+
described_class.rake_tasks! do
|
28
|
+
api 'api'
|
29
|
+
username 'test'
|
30
|
+
password 'pass'
|
31
|
+
organisation 'org'
|
32
|
+
space 'space'
|
33
|
+
end
|
34
|
+
|
35
|
+
expect(Kernel).to receive(:system).with('cf login -a api -u test -p pass -o org -s space')
|
36
|
+
Rake::Task['cf:login'].invoke
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should include all details provided in ENV' do
|
40
|
+
{'CF_API' => 'api',
|
41
|
+
'CF_USERNAME' => 'test',
|
42
|
+
'CF_PASSWORD' => 'pass',
|
43
|
+
'CF_ORGANISATION' => 'org',
|
44
|
+
'CF_SPACE' => 'space'}.each do |(k, v)|
|
45
|
+
expect(ENV).to receive(:[]).with(k).and_return(v).at_least(:once)
|
46
|
+
end
|
47
|
+
|
48
|
+
expect(Kernel).to receive(:system).with('cf login -a api -u test -p pass -o org -s space')
|
49
|
+
described_class.rake_tasks!
|
50
|
+
Rake::Task['cf:login'].invoke
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should mix and match ENV and defined details with ENV having precedence' do
|
54
|
+
{'CF_API' => nil,
|
55
|
+
'CF_USERNAME' => 'test',
|
56
|
+
'CF_PASSWORD' => 'pass',
|
57
|
+
'CF_ORGANISATION' => 'org',
|
58
|
+
'CF_SPACE' => nil}.each do |(k, v)|
|
59
|
+
expect(ENV).to receive(:[]).with(k).and_return(v).at_least(:once)
|
60
|
+
end
|
61
|
+
|
62
|
+
expect(Kernel).to receive(:system).with('cf login -a api -u test -p pass -o org')
|
63
|
+
|
64
|
+
described_class.rake_tasks! do
|
65
|
+
api 'api'
|
66
|
+
organisation 'will be overridden by ENV[CF_ORGANISATION]'
|
67
|
+
end
|
68
|
+
|
69
|
+
Rake::Task['cf:login'].invoke
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'cf-deploy'
|
3
|
+
require 'rake'
|
4
|
+
|
5
|
+
describe CF::Deploy do
|
6
|
+
before :each do
|
7
|
+
Rake::Task.clear
|
8
|
+
end
|
9
|
+
|
10
|
+
context '.rake_tasks!' do
|
11
|
+
it 'should install task for each manifest' do
|
12
|
+
Dir.chdir('spec/') do
|
13
|
+
described_class.rake_tasks!
|
14
|
+
end
|
15
|
+
|
16
|
+
expect(Rake::Task['cf:deploy:staging']).to be_a(Rake::Task)
|
17
|
+
expect(Rake::Task['cf:deploy:test']).to be_a(Rake::Task)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should install login task' do
|
21
|
+
Dir.chdir('spec/') do
|
22
|
+
described_class.rake_tasks!
|
23
|
+
end
|
24
|
+
|
25
|
+
expect(Rake::Task['cf:login']).to be_a(Rake::Task)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should install a login task as a prerequisite for deploy tasks' do
|
29
|
+
Dir.chdir('spec/') do
|
30
|
+
described_class.rake_tasks!
|
31
|
+
end
|
32
|
+
|
33
|
+
expect(Rake::Task['cf:deploy:staging'].prerequisite_tasks[0]).to be(Rake::Task['cf:login'])
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should install tasks with prerequisites' do
|
37
|
+
expected_task_1 = Rake::Task.define_task('asset:precompile')
|
38
|
+
expected_task_2 = Rake::Task.define_task(:clean)
|
39
|
+
|
40
|
+
Dir.chdir('spec/') do
|
41
|
+
described_class.rake_tasks! do
|
42
|
+
environment :staging => 'asset:precompile'
|
43
|
+
environment :test => ['asset:precompile', :clean]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
expect(Rake::Task['cf:deploy:staging'].prerequisite_tasks[1]).to be(expected_task_1)
|
48
|
+
expect(Rake::Task['cf:deploy:test'].prerequisite_tasks[2]).to be(expected_task_2)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should have a configurable manifest glob options' do
|
52
|
+
Dir.chdir('spec/') do
|
53
|
+
described_class.rake_tasks! do
|
54
|
+
manifest_glob 'manifests/staging.yml'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
expect(Rake::Task.tasks.count).to eq(2)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cf-deploy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Luke Morton
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-07-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: bundler
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '1.5'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '1.5'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 3.0.0
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 3.0.0
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: simplecov
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 0.7.1
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 0.7.1
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: coveralls
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 0.7.0
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 0.7.0
|
94
|
+
description:
|
95
|
+
email:
|
96
|
+
- luke@madebymade.co.uk
|
97
|
+
executables: []
|
98
|
+
extensions: []
|
99
|
+
extra_rdoc_files: []
|
100
|
+
files:
|
101
|
+
- lib/cf/deploy/commands.rb
|
102
|
+
- lib/cf/deploy/config.rb
|
103
|
+
- lib/cf/deploy/env_config.rb
|
104
|
+
- lib/cf/deploy/version.rb
|
105
|
+
- lib/cf/deploy.rb
|
106
|
+
- lib/cf-deploy.rb
|
107
|
+
- spec/blue_green_task_spec.rb
|
108
|
+
- spec/deploy_task_spec.rb
|
109
|
+
- spec/login_task_spec.rb
|
110
|
+
- spec/rake_tasks_spec.rb
|
111
|
+
- spec/spec_helper.rb
|
112
|
+
- LICENSE
|
113
|
+
- README.md
|
114
|
+
homepage: https://github.com/madebymade/cf-deploy
|
115
|
+
licenses:
|
116
|
+
- MIT
|
117
|
+
post_install_message:
|
118
|
+
rdoc_options: []
|
119
|
+
require_paths:
|
120
|
+
- lib
|
121
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - ! '>='
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
segments:
|
128
|
+
- 0
|
129
|
+
hash: 3630708785980553311
|
130
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
131
|
+
none: false
|
132
|
+
requirements:
|
133
|
+
- - ! '>='
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
segments:
|
137
|
+
- 0
|
138
|
+
hash: 3630708785980553311
|
139
|
+
requirements: []
|
140
|
+
rubyforge_project:
|
141
|
+
rubygems_version: 1.8.23
|
142
|
+
signing_key:
|
143
|
+
specification_version: 3
|
144
|
+
summary: Rake tasks for deploying to CloudFoundry v6+
|
145
|
+
test_files: []
|