takeoff 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +268 -0
- data/Rakefile +1 -0
- data/bin/takeoff +6 -0
- data/example.Launchfile +86 -0
- data/lib/takeoff/cli.rb +46 -0
- data/lib/takeoff/configuration.rb +58 -0
- data/lib/takeoff/ext/middleware_builder.rb +54 -0
- data/lib/takeoff/helpers.rb +44 -0
- data/lib/takeoff/plan/base.rb +62 -0
- data/lib/takeoff/plan/default.rb +36 -0
- data/lib/takeoff/plan/heroku.rb +41 -0
- data/lib/takeoff/stage/base.rb +17 -0
- data/lib/takeoff/stage/checkout_development_branch.rb +24 -0
- data/lib/takeoff/stage/heroku/disable_preboot.rb +37 -0
- data/lib/takeoff/stage/heroku/enable_maintenance_mode.rb +27 -0
- data/lib/takeoff/stage/heroku/migrate_database.rb +52 -0
- data/lib/takeoff/stage/heroku/precompile_and_sync_assets.rb +45 -0
- data/lib/takeoff/stage/heroku/push_to_server.rb +16 -0
- data/lib/takeoff/stage/heroku/remember_commits.rb +19 -0
- data/lib/takeoff/stage/heroku/scale_down_workers.rb +33 -0
- data/lib/takeoff/stage/heroku/verify_server_not_already_up_to_date.rb +17 -0
- data/lib/takeoff/stage/heroku/verify_staging_up_to_date.rb +28 -0
- data/lib/takeoff/stage/log.rb +28 -0
- data/lib/takeoff/stage/look_out_for_danger.rb +15 -0
- data/lib/takeoff/stage/point_checkpoint_to_development.rb +22 -0
- data/lib/takeoff/stage/push_to_github.rb +14 -0
- data/lib/takeoff/stage/stash_changes.rb +31 -0
- data/lib/takeoff/stage/verify_circle_ci_status.rb +46 -0
- data/lib/takeoff/stage/verify_github_up_to_date.rb +18 -0
- data/lib/takeoff/version.rb +3 -0
- data/lib/takeoff.rb +44 -0
- data/takeoff.gemspec +23 -0
- metadata +152 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e3926055dcd312f00ff2b570266495751db03c81
|
4
|
+
data.tar.gz: e6417f962f54e9b664f1f848fef91e8463dcbc2e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 65712714c380c23484e9d3867e023f52494c90f95729e87930ad8e20bcbd957f7d4beab70aab76a79dc5deb92135224f5fc8dc89f1e0542f0820f24c922ab81c
|
7
|
+
data.tar.gz: df3d2bed3ff906d523d68abae44a259287f84086f855e1a61a8a7c298de1885fdfdb1fe9c272f0bd2a484af5f260fe3bd9427bcd4ccf9f94cfe0962e37b2abec
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Douwe Maan
|
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,268 @@
|
|
1
|
+
# <img src="https://assets-cdn.github.com/images/icons/emoji/unicode/1f680.png" height="32" width="32" alt="🚀" /> Takeoff
|
2
|
+
|
3
|
+
#### Sit back, relax and let Takeoff deploy your app.
|
4
|
+
|
5
|
+
Takeoff is a command line tool that helps you deploy your web applications. Configure it once, and from then on Takeoff will take care of responsibly deploying your app, giving you time to <del>[practice your swordfighting](http://xkcd.com/303/)</del> <del>catch up on sleep</del> get more coffee.
|
6
|
+
|
7
|
+
Takeoff was built with Git, Ruby, Rails and Heroku in mind, but is ultimately SCM, language, framework and hosting platform agnostic—it all comes down to how you configure it.
|
8
|
+
|
9
|
+
Takeoff is built around the concepts of **launch plans** and **stages**.
|
10
|
+
|
11
|
+
Each Takeoff launch plan describes a particular type of deployment (launch), divided up into bite-sized steps (stages) that together make up the launch. Since you'd want to deploy to at least one server, every app has at least one launch plan, consisting of at least one stage: the actual "push to server".
|
12
|
+
|
13
|
+
Different launch plans can be used for different hosting platforms and ways of pushing (Git-based like Heroku, SSH-based like any regular VPS, or FTP-based like cheap PHP hosts) and for the different environments your app can work in (production, staging, etc.). In each of these cases, you'd use different (or differently configured) stages, tailored to the specifics of the launch you're doing.
|
14
|
+
|
15
|
+
Out of the box, Takeoff comes with a bare bones default launch plan as well as one adapted to Heroku, but you can easily create your own by picking and choosing from the different stages already included or if you so wish, by building your own.
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
|
19
|
+
(Not convinced yet? Continue to [Configuration](#configuration) and read on.)
|
20
|
+
|
21
|
+
Takeoff can be added to your Ruby application by adding the following to your Gemfile and running `bundle install`:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
gem "takeoff", require: false, group: :development
|
25
|
+
```
|
26
|
+
|
27
|
+
To use Takeoff with a non-Ruby project or one without a Gemfile, install it from the command line:
|
28
|
+
|
29
|
+
```sh
|
30
|
+
gem install takeoff
|
31
|
+
```
|
32
|
+
|
33
|
+
## Configuration
|
34
|
+
|
35
|
+
A launch plan's stages are defined using the Middleware pattern popularized by Rack and Rails. Takeoff stages explicitly hand over control to the next stage in the launch plan and are given back control after that stage has finished, which means they can have effect both before and after all stages "down stream" have run. As an example, this allows you to plan a "maintenance mode" stage before the final "push to server" stage, which will enable maintenance mode, hand over control, wait for pushing to finish, get back control and finally disable maintenance mode, all from the one stage definition.
|
36
|
+
|
37
|
+
In terms of code, a stage is a class with the following layout, which should look familiar if you've ever written Rails middleware:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
require "takeoff/stage/base"
|
41
|
+
|
42
|
+
class MyStage < Takeoff::Stage::Base
|
43
|
+
def call(env)
|
44
|
+
# `env` is a hash containing information about the the current launch.
|
45
|
+
# You can write to it if you think you have something stages downstream might
|
46
|
+
# be interested in, or read from it if you're interested in info added upstream.
|
47
|
+
|
48
|
+
# Here, you can do things before the next stage runs.
|
49
|
+
|
50
|
+
# This call hands over control to the next stage in line, represented by @app
|
51
|
+
# which is set by the inherited `Takeoff::Stage::Base#initialize`.
|
52
|
+
@app.call(env)
|
53
|
+
|
54
|
+
# Here, you can do things after the stage and all stages following it have run.
|
55
|
+
|
56
|
+
# If you want to ensure the code to be run after is even run if any of the
|
57
|
+
# following stages raises an error, you can wrap `@app.call(env)` and the code
|
58
|
+
# to be run after in a begin/ensure/end block.
|
59
|
+
end
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
The default launch plan (aptly named `:default`) is very bare bones. It's made up off the following stages:
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
# Logs "Ready for takeoff!" before and "Takeoff (un)successful." after.
|
67
|
+
use Stage::Log
|
68
|
+
|
69
|
+
# Checks if there are any dangerous stages (like an unsafe migration) that
|
70
|
+
# other stages might want to put up protection for (like a maintenance mode window).
|
71
|
+
# Adds arrays `:stages`, `:dangerous_stages` and boolean `:dangerous` to the env hash.
|
72
|
+
use Stage::LookOutForDanger
|
73
|
+
|
74
|
+
# Stashes uncommitted changes so Takeoff can safely move between branches.
|
75
|
+
use Stage::StashChanges
|
76
|
+
|
77
|
+
# Checks out the development branch with the code to be deployed.
|
78
|
+
use Stage::CheckoutDevelopmentBranch
|
79
|
+
|
80
|
+
# Verifies the development branch has been pushed to GitHub.
|
81
|
+
use Stage::VerifyGithubUpToDate
|
82
|
+
|
83
|
+
# Updates the deployment checkpoint branch to point to the development branch.
|
84
|
+
use Stage::PointCheckpointToDevelopment
|
85
|
+
|
86
|
+
# Pushes the deployment checkpoint branch to GitHub.
|
87
|
+
use Stage::PushToGithub
|
88
|
+
```
|
89
|
+
|
90
|
+
As you can see, this launch plan is missing an actual "push to server" or deploy stage. This is because this part of the launch plan is very setup-specific and without a default solution. You can configure the non-default stages of your launch plan in your Takeoff configuration file, which we'll get to in a moment.
|
91
|
+
|
92
|
+
Out of the box, Takeoff comes with a launch plan for Heroku (called `:heroku`), which adds onto `:default` as you can see below. Even if you're not on Heroku, this is interesting to read as you'll want to configure your own launch plans in a similar fashion.
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
# Adds `:deployed_commit` and `:new_commit` to the env hash, so that other stages
|
96
|
+
# can reference and use them.
|
97
|
+
insert_after Stage::Log,
|
98
|
+
Stage::Heroku::RememberCommits
|
99
|
+
|
100
|
+
# Verifies the Heroku server isn't already up to date with the commit to be deployed.
|
101
|
+
insert_after Stage::VerifyGithubUpToDate,
|
102
|
+
Stage::Heroku::VerifyServerNotAlreadyUpToDate
|
103
|
+
|
104
|
+
insert_after Stage::PointCheckpointToDevelopment, [
|
105
|
+
# Disables Heroku preboot if this is a dangerous deploy
|
106
|
+
# according to `env[:dangerous]`, set by `LookOutForDanger`.
|
107
|
+
Stage::Heroku::DisablePreboot,
|
108
|
+
|
109
|
+
# Scales down Heroku workers if this is a dangerous deploy.
|
110
|
+
Stage::Heroku::ScaleDownWorkers,
|
111
|
+
|
112
|
+
# Enables Heroku maintenance mode if this is a dangerous deploy.
|
113
|
+
Stage::Heroku::EnableMaintenanceMode,
|
114
|
+
|
115
|
+
# Pushes deployment checkpoint branch to Heroku.
|
116
|
+
Stage::Heroku::PushToServer,
|
117
|
+
|
118
|
+
# Runs database migrations on Heroku if any are in the diff.
|
119
|
+
# This makes this deploy a dangerous one, which `LookOutForDanger` will pick up on
|
120
|
+
# and which `DisablePreboot`, `ScaleDownWorkers` and `EnableMaintenanceMode`
|
121
|
+
# will put up appropriate protection for.
|
122
|
+
Stage::Heroku::MigrateDatabase
|
123
|
+
]
|
124
|
+
```
|
125
|
+
|
126
|
+
If you're on Heroku, chances are this `:heroku` launch plan is exactly what you're looking for. In your configuration file, you can configure Takeoff to use the `:heroku` plan by default, instead of the plan _named_ `:default`, which, as mentioned, does very little.
|
127
|
+
|
128
|
+
Takeoff loads configuration from either `./Launchfile` or `./config/takeoff.rb` by default, relative from your project directory, so use whichever you prefer. If neither is to your liking, you can override the config file location when you run `takeoff`.
|
129
|
+
|
130
|
+
Let's set `:heroku` as the default plan:
|
131
|
+
|
132
|
+
```rb
|
133
|
+
require "takeoff/plan/heroku"
|
134
|
+
|
135
|
+
default_plan :heroku
|
136
|
+
|
137
|
+
# By default, the Heroku launch plan looks for these Git branches and remotes,
|
138
|
+
# as specified in its default env hash. If any of these are different for you,
|
139
|
+
# you can override them from here—your configuration file.
|
140
|
+
# env.merge!(
|
141
|
+
# # The Rails/Rack environment you're deploying to.
|
142
|
+
# environment: "production",
|
143
|
+
#
|
144
|
+
# # The Git remote that points to your GitHub repo.
|
145
|
+
# github_remote: "github",
|
146
|
+
#
|
147
|
+
# # The branch that you want deployed, typically the branch you develop in.
|
148
|
+
# development_branch: "develop",
|
149
|
+
#
|
150
|
+
# # The branch that functions as a checkpoint for commits deployed to the server.
|
151
|
+
# checkpoint_branch: "master",
|
152
|
+
#
|
153
|
+
# # The Git remote that points to your Heroku app.
|
154
|
+
# server_remote: "heroku"
|
155
|
+
# )
|
156
|
+
```
|
157
|
+
|
158
|
+
If you deploy to multiple servers (as you should), you'll want to create different launch plans with different a env configuration for each of them:
|
159
|
+
|
160
|
+
```rb
|
161
|
+
require "takeoff/plan/heroku"
|
162
|
+
|
163
|
+
require "takeoff/stage/heroku/verify_staging_up_to_date"
|
164
|
+
|
165
|
+
default_plan :heroku
|
166
|
+
|
167
|
+
# New plans are based on the default plan, `:heroku` in our case.
|
168
|
+
plan :staging do
|
169
|
+
# We want to override the Heroku env defaults listed above.
|
170
|
+
env.merge!(
|
171
|
+
environment: "staging",
|
172
|
+
checkpoint_branch: "staging",
|
173
|
+
server_remote: "staging"
|
174
|
+
)
|
175
|
+
end
|
176
|
+
|
177
|
+
# You can override the parent plan by adding a `:based_on` option.
|
178
|
+
# Instead of `default_plan :heroku`, we could also have used `based_on: :heroku`
|
179
|
+
# above, to explicitly base `:staging` off of that.
|
180
|
+
plan :production, based_on: :staging do
|
181
|
+
# These first two are the defaults for the `:heroku` plan, but since we're based
|
182
|
+
# on `:staging` we need to reset them to those defaults.
|
183
|
+
env.merge!(
|
184
|
+
environment: "production",
|
185
|
+
checkpoint_branch: "master",
|
186
|
+
server_remote: "production"
|
187
|
+
)
|
188
|
+
|
189
|
+
# Your new launch plan can add stages if you so wish. The one below is included
|
190
|
+
# with Takeoff but is not part of either the default or Heroku launch plans.
|
191
|
+
# It verifies that the staging server is up to date before deploying to production.
|
192
|
+
# To know where to check, it requires a Heroku-based launch plan called `:staging`.
|
193
|
+
stages do
|
194
|
+
insert_before Takeoff::Stage::VerifyServerNotAlreadyUpToDate,
|
195
|
+
Takeoff::Stage::Heroku::VerifyStagingUpToDate
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# Because we don't want a plain `takeoff launch` command to use the unconfigured
|
200
|
+
# `:heroku` plan, we set `:staging` to be the default.
|
201
|
+
default_plan :staging
|
202
|
+
```
|
203
|
+
|
204
|
+
Other stages included with Takeoff that are not part of either the default or Heroku launch plans, are `Takeoff::Stage::VerifyCircleCiStatus` and `Takeoff::Stage::Heroku::PrecompileAndSyncAssets`. The former verifies that the commit to be pushed has been tested successfully on [Circle CI](http://circleci.com); the latter precompiles Rails assets and syncs them to Amazon S3 using the [AssetSync](https://github.com/rumblelabs/asset_sync) gem. Both would typically be inserted before `Takeoff::Stage::PointCheckpointToDevelopment`, which is itself before the "push to server" stage.
|
205
|
+
|
206
|
+
To see an example of an actual production Launchfile, namely the one we use to deploy [Stinngo](https://www.stinngo.com), check out [`example.Launchfile`](example.Launchfile).
|
207
|
+
|
208
|
+
## Usage
|
209
|
+
|
210
|
+
To let Takeoff deploy your app, use the `takeoff` command on the command line, like so:
|
211
|
+
|
212
|
+
```sh
|
213
|
+
takeoff [launch [PLAN]] [--config CONFIG] [--skip SKIP]
|
214
|
+
```
|
215
|
+
|
216
|
+
- `PLAN` can be any launch plan that comes out of the box or that you've specified in your configuration file. `PLAN` defaults to the contents of the `RACK_ENV` or `RAILS_ENV` environment variable or the default plan set in your configuration file.
|
217
|
+
- `CONFIG`: Path to your Takeoff configuration file. Defaults to `./Launchfile` or `./config/takeoff.rb`.
|
218
|
+
- `SKIP`: Space-seperated list of full or partial names of stages to skip. Example: `-s VerifyServerNotAlreadyUpToDate` if you want to re-do a deployment.
|
219
|
+
|
220
|
+
For the best result, Takeoff should be run in a terminal simulator that supports Emoji, like OS X's Terminal.app. However, as long as your terminal app doesn't break when it comes across Emoji, it's no big deal if it doesn't actually render the images.
|
221
|
+
|
222
|
+
## Attribution
|
223
|
+
|
224
|
+
Takeoff was built at [Stinngo](http://www.stinngo.com) by [Douwe Maan](http://github.com/DouweM), who was unhappy with the hacked-together deploy scripts they had been using before and wanted to settle the matter once and for all.
|
225
|
+
|
226
|
+
Takeoff took inspiration from [envato/heroku-deploy](https://github.com/envato/heroku-deploy), [mattpolito/paratrooper](https://github.com/mattpolito/paratrooper) and numerous `deploy.sh` scripts and `rake deploy` tasks floating around the web.
|
227
|
+
|
228
|
+
## To Do
|
229
|
+
|
230
|
+
Takeoff has been in use at Stinngo for a while now, but we realize there's still work to be done to make it a viable option for everyone. Hence, this to do list:
|
231
|
+
|
232
|
+
- [ ] Properly handle "first deploy", when the expected Heroku app, remote and checkpoint branches don't exist or are empty.
|
233
|
+
- [ ] Do all of the Git-shuffling in a seperate (temporary) folderm so you can keep working in your development one while Takeoff is deploying.
|
234
|
+
- [ ] Add more out of the box launch plans and stages:
|
235
|
+
- [ ] SSH push
|
236
|
+
- [ ] FTP push
|
237
|
+
- [ ] generic Git push
|
238
|
+
- [ ] Rails assets precompilation without S3 sync
|
239
|
+
- [ ] Colorize log messages.
|
240
|
+
- [ ] Raise nicer error when command fails.
|
241
|
+
- [ ] Add banner?
|
242
|
+
|
243
|
+
If you like Takeoff, feel free to pick any one of these up—I'll gladly accept pull requests.
|
244
|
+
|
245
|
+
## License
|
246
|
+
|
247
|
+
Copyright (c) 2014 Douwe Maan
|
248
|
+
|
249
|
+
MIT License
|
250
|
+
|
251
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
252
|
+
a copy of this software and associated documentation files (the
|
253
|
+
"Software"), to deal in the Software without restriction, including
|
254
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
255
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
256
|
+
permit persons to whom the Software is furnished to do so, subject to
|
257
|
+
the following conditions:
|
258
|
+
|
259
|
+
The above copyright notice and this permission notice shall be
|
260
|
+
included in all copies or substantial portions of the Software.
|
261
|
+
|
262
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
263
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
264
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
265
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
266
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
267
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
268
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/takeoff
ADDED
data/example.Launchfile
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# Require the non-default launch plans and stages we want to use.
|
2
|
+
require "takeoff/plan/heroku"
|
3
|
+
|
4
|
+
require "takeoff/stage/verify_circle_ci_status"
|
5
|
+
require "takeoff/stage/heroku/precompile_and_sync_assets"
|
6
|
+
require "takeoff/stage/heroku/verify_staging_up_to_date"
|
7
|
+
|
8
|
+
# Require custom stages that aren't included with Takeoff.
|
9
|
+
require "./config/takeoff/update_database_indexes"
|
10
|
+
require "./config/takeoff/seed_database"
|
11
|
+
|
12
|
+
# Our app is hosted on Heroku, so base plans on Heroku by default.
|
13
|
+
default_plan :heroku
|
14
|
+
|
15
|
+
stages do
|
16
|
+
# Add simple custom stages in-line as lambdas.
|
17
|
+
# This one uses https://github.com/bkeepers/dotenv-deployment to load
|
18
|
+
# environment variables based on the environment we're deploying to, like
|
19
|
+
# like `ENV["GITHUB_OAUTH_TOKEN"]` which is used by `VerifyCircleCiStatus`.
|
20
|
+
insert_after Takeoff::Stage::Log, (lambda do |env|
|
21
|
+
ENV["RACK_ENV"] = env[:environment]
|
22
|
+
|
23
|
+
require "dotenv/deployment"
|
24
|
+
end)
|
25
|
+
|
26
|
+
# Add stages that come out of the box but aren't included by default.
|
27
|
+
# `VerifyCircleCiStatus` verifies that the commit to be pushed has been tested successfully on Circle CI;
|
28
|
+
# `PrecompileAndSyncAssets` precompiles Rails assets and syncs them to Amazon S3 using the AssetSync gem.
|
29
|
+
insert_before Takeoff::Stage::PointCheckpointToDevelopment, [
|
30
|
+
Takeoff::Stage::VerifyCircleCiStatus,
|
31
|
+
Takeoff::Stage::Heroku::PrecompileAndSyncAssets
|
32
|
+
]
|
33
|
+
|
34
|
+
# Add custom stages defined in dedicated classes.
|
35
|
+
# `UpdateDatabaseIndexes` updates our MongoDB indexes if necessary;
|
36
|
+
# `SeedDatabase` seeds our database with bootstrap data.
|
37
|
+
insert_after Takeoff::Stage::Heroku::MigrateDatabase, [
|
38
|
+
Stinngo::Takeoff::UpdateDatabaseIndexes,
|
39
|
+
Stinngo::Takeoff::SeedDatabase
|
40
|
+
]
|
41
|
+
end
|
42
|
+
|
43
|
+
# Set the GitHub repo the `VerifyCircleCiStatus stage should check the status for.
|
44
|
+
env[:github_repo] = "toopia/Stinngo-Rails"
|
45
|
+
|
46
|
+
# We have a dedicated "playground" Heroku app where we test large features
|
47
|
+
# that are still far from ready for staging or production.
|
48
|
+
plan :playground do
|
49
|
+
env.merge!(
|
50
|
+
environment: "playground",
|
51
|
+
development_branch: "playground",
|
52
|
+
checkpoint_branch: "playground",
|
53
|
+
server_remote: "playground"
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Staging is pretty straightforward.
|
58
|
+
plan :staging do
|
59
|
+
env.merge!(
|
60
|
+
environment: "staging",
|
61
|
+
checkpoint_branch: "staging",
|
62
|
+
server_remote: "staging"
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
# And here's production.
|
67
|
+
plan :production do
|
68
|
+
# Except for `server_remote`, these are the defaults for `:heroku`, but I like to list them here for clarity.
|
69
|
+
env.merge!(
|
70
|
+
environment: "production",
|
71
|
+
checkpoint_branch: "master",
|
72
|
+
server_remote: "production"
|
73
|
+
)
|
74
|
+
|
75
|
+
stages do
|
76
|
+
# Another optional stage that comes out of the box:
|
77
|
+
# It verifies that the staging server is up to date before deploying to production.
|
78
|
+
# To know where to check, it requires a Heroku-based launch plan called `:staging`, which we happen to have.
|
79
|
+
insert_before Takeoff::Stage::Heroku::PrecompileAndSyncAssets,
|
80
|
+
Takeoff::Stage::Heroku::VerifyStagingUpToDate
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Because we don't want a plain `takeoff launch` command to use the unconfigured
|
85
|
+
# `:heroku` plan, we set `:staging` to be the default.
|
86
|
+
default_plan :staging
|
data/lib/takeoff/cli.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require "thor"
|
2
|
+
|
3
|
+
require "takeoff"
|
4
|
+
|
5
|
+
module Takeoff
|
6
|
+
class CLI < Thor
|
7
|
+
default_task :launch
|
8
|
+
|
9
|
+
class_option :config, aliases: "-c", desc: "Path to your Takeoff configuration file. Defaults to ./Launchfile or ./config/takeoff.rb"
|
10
|
+
|
11
|
+
desc "launch [PLAN]", "Deploy using the specified launch plan."
|
12
|
+
|
13
|
+
long_desc <<-LONG_DESC
|
14
|
+
Deploys using the specified launch plan.
|
15
|
+
|
16
|
+
PLAN can be any launch plan that comes out of the box or that you've specified in your configuration file.
|
17
|
+
PLAN defaults to the contents of the `RACK_ENV` or `RAILS_ENV` environment variable or the default plan set in your configuration file.
|
18
|
+
LONG_DESC
|
19
|
+
|
20
|
+
option :skip, type: :array, aliases: "-s", desc: "Space-seperated list of full or partial names of stages to skip. Example: `-s VerifyServerNotAlreadyUpToDate` if you want to re-do a deployment."
|
21
|
+
|
22
|
+
def launch(plan = nil)
|
23
|
+
setup
|
24
|
+
|
25
|
+
plan ||= ENV["RACK_ENV"] || ENV["RAILS_ENV"]
|
26
|
+
plan = Takeoff[plan]
|
27
|
+
|
28
|
+
options[:skip].each do |name|
|
29
|
+
plan.stages.delete(name)
|
30
|
+
end if options[:skip]
|
31
|
+
|
32
|
+
plan.launch
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def setup
|
37
|
+
config_path = options[:config]
|
38
|
+
config_path ||= "./Launchfile" if File.exist?("./Launchfile")
|
39
|
+
config_path ||= "./config/takeoff.rb" if File.exist?("./config/takeoff.rb")
|
40
|
+
|
41
|
+
raise "Config file not found!" unless config_path
|
42
|
+
|
43
|
+
Takeoff.configure(File.read(config_path), config_path)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "active_support"
|
2
|
+
require "active_support/hash_with_indifferent_access"
|
3
|
+
require "logger"
|
4
|
+
require "middleware"
|
5
|
+
|
6
|
+
module Takeoff
|
7
|
+
class Configuration
|
8
|
+
def plans
|
9
|
+
@plans ||= HashWithIndifferentAccess.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](name)
|
13
|
+
name ||= default_plan
|
14
|
+
|
15
|
+
plan = name.is_a?(Class) ? name : plans[name]
|
16
|
+
|
17
|
+
if plan.nil?
|
18
|
+
raise "Plan '#{name}' is unknown"
|
19
|
+
end
|
20
|
+
|
21
|
+
unless plan <= Plan::Base
|
22
|
+
raise "Plan '#{name}' doesn't inherit from Takeoff::Plan::Base"
|
23
|
+
end
|
24
|
+
|
25
|
+
plan
|
26
|
+
end
|
27
|
+
|
28
|
+
def plan?(name)
|
29
|
+
self[name] rescue nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def default_plan(name = nil, &block)
|
33
|
+
self.default_plan = name if name
|
34
|
+
default_plan.instance_eval(&block) if block
|
35
|
+
|
36
|
+
@default_plan ||= Plan::Default
|
37
|
+
end
|
38
|
+
|
39
|
+
def default_plan=(plan)
|
40
|
+
@default_plan = self[plan]
|
41
|
+
end
|
42
|
+
|
43
|
+
delegate :stages, :env, to: :default_plan
|
44
|
+
|
45
|
+
def plan(name, plan = nil, based_on: default_plan, &block)
|
46
|
+
if plan?(name)
|
47
|
+
plan ||= self[name]
|
48
|
+
else
|
49
|
+
plan ||= Plan.const_set(name.to_s.camelize, Class.new(self[based_on]))
|
50
|
+
plans[name] = plan
|
51
|
+
end
|
52
|
+
|
53
|
+
plan.instance_eval(&block) if block
|
54
|
+
|
55
|
+
plan
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require "middleware"
|
2
|
+
|
3
|
+
module Takeoff
|
4
|
+
module Ext
|
5
|
+
module MiddlewareBuilder
|
6
|
+
def middlewares
|
7
|
+
self.stack.map(&:first)
|
8
|
+
end
|
9
|
+
|
10
|
+
def index(object)
|
11
|
+
stack.each_with_index do |item, i|
|
12
|
+
klass = item[0]
|
13
|
+
return i if klass == object || (klass.respond_to?(:name) && klass.name.split("::").last == object)
|
14
|
+
end
|
15
|
+
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def insert(index, middlewares, *args, &block)
|
20
|
+
index = self.index(index) unless index.is_a?(Integer)
|
21
|
+
|
22
|
+
if middlewares.is_a?(Array)
|
23
|
+
middlewares.each_with_index do |middleware, i|
|
24
|
+
super(index + i, middleware, *args, &block)
|
25
|
+
end
|
26
|
+
else
|
27
|
+
super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
alias_method :insert_before, :insert
|
32
|
+
|
33
|
+
def insert_after(index, middlewares, *args, &block)
|
34
|
+
index = self.index(index) unless index.is_a?(Integer)
|
35
|
+
|
36
|
+
if middlewares.is_a?(Array)
|
37
|
+
middlewares.each_with_index do |middleware, i|
|
38
|
+
super(index + i, middleware, *args, &block)
|
39
|
+
end
|
40
|
+
else
|
41
|
+
super
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def delete(index)
|
46
|
+
index = self.index(index) unless index.is_a?(Integer)
|
47
|
+
raise "no such middleware to delete: #{index.inspect}" unless index
|
48
|
+
stack.delete_at(index)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
Middleware::Builder.prepend Takeoff::Ext::MiddlewareBuilder
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "shellwords"
|
2
|
+
|
3
|
+
module Takeoff
|
4
|
+
module Helpers
|
5
|
+
def log(message = nil)
|
6
|
+
puts
|
7
|
+
puts "🚀 #{message}" if message
|
8
|
+
end
|
9
|
+
|
10
|
+
def execute(command)
|
11
|
+
puts "$ #{command}"
|
12
|
+
value = `#{command}`
|
13
|
+
puts value
|
14
|
+
|
15
|
+
raise unless $?.success?
|
16
|
+
|
17
|
+
value
|
18
|
+
end
|
19
|
+
|
20
|
+
def latest_commit(branch)
|
21
|
+
`git rev-parse --verify #{branch}`.strip
|
22
|
+
end
|
23
|
+
|
24
|
+
def diff(from, to, files, name_only: false)
|
25
|
+
files.select! { |file| File.exist?(file) }
|
26
|
+
`git diff #{from}..#{to} -U0 #{"--name-only" if name_only} #{files.join(" ")}`
|
27
|
+
end
|
28
|
+
|
29
|
+
def file_has_changed_locally?(file)
|
30
|
+
`git ls-files -o -m -d --exclude-standard | grep -E #{Shellwords.escape(file)}`
|
31
|
+
$?.success?
|
32
|
+
end
|
33
|
+
|
34
|
+
def files_have_changed?(from, to, files)
|
35
|
+
!diff(from, to, files, name_only: true).blank?
|
36
|
+
end
|
37
|
+
|
38
|
+
def branches_up_to_date?(a, b)
|
39
|
+
latest_commit(a) == latest_commit(b)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
extend self
|
44
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require "active_support"
|
2
|
+
require "active_support/core_ext"
|
3
|
+
require "middleware"
|
4
|
+
require "takeoff/ext/middleware_builder"
|
5
|
+
|
6
|
+
module Takeoff
|
7
|
+
module Plan
|
8
|
+
class Base
|
9
|
+
class << self
|
10
|
+
def build_plan(name, &block)
|
11
|
+
plan = Class.new(self, &block)
|
12
|
+
Plan.const_set(name.to_s.camelize, plan)
|
13
|
+
end
|
14
|
+
|
15
|
+
def stages(&block)
|
16
|
+
@stages ||= default_stages
|
17
|
+
|
18
|
+
@stages.instance_eval(&block) if block
|
19
|
+
|
20
|
+
@stages
|
21
|
+
end
|
22
|
+
|
23
|
+
def env
|
24
|
+
@env ||= default_env
|
25
|
+
end
|
26
|
+
|
27
|
+
def launch(env = {})
|
28
|
+
new.launch(env)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def base?
|
33
|
+
self.superclass == ::Object
|
34
|
+
end
|
35
|
+
|
36
|
+
def default_stages
|
37
|
+
if base?
|
38
|
+
Middleware::Builder.new
|
39
|
+
else
|
40
|
+
parent_stages = self.superclass.stages
|
41
|
+
Middleware::Builder.new { use parent_stages }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def default_env
|
46
|
+
env = if base?
|
47
|
+
{}
|
48
|
+
else
|
49
|
+
self.superclass.env.dup
|
50
|
+
end
|
51
|
+
|
52
|
+
env.merge!(plan: self)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def launch(env = {})
|
57
|
+
env = self.class.env.merge(env)
|
58
|
+
self.class.stages.call(env)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|