takeoff 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/Gemfile +3 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +268 -0
  6. data/Rakefile +1 -0
  7. data/bin/takeoff +6 -0
  8. data/example.Launchfile +86 -0
  9. data/lib/takeoff/cli.rb +46 -0
  10. data/lib/takeoff/configuration.rb +58 -0
  11. data/lib/takeoff/ext/middleware_builder.rb +54 -0
  12. data/lib/takeoff/helpers.rb +44 -0
  13. data/lib/takeoff/plan/base.rb +62 -0
  14. data/lib/takeoff/plan/default.rb +36 -0
  15. data/lib/takeoff/plan/heroku.rb +41 -0
  16. data/lib/takeoff/stage/base.rb +17 -0
  17. data/lib/takeoff/stage/checkout_development_branch.rb +24 -0
  18. data/lib/takeoff/stage/heroku/disable_preboot.rb +37 -0
  19. data/lib/takeoff/stage/heroku/enable_maintenance_mode.rb +27 -0
  20. data/lib/takeoff/stage/heroku/migrate_database.rb +52 -0
  21. data/lib/takeoff/stage/heroku/precompile_and_sync_assets.rb +45 -0
  22. data/lib/takeoff/stage/heroku/push_to_server.rb +16 -0
  23. data/lib/takeoff/stage/heroku/remember_commits.rb +19 -0
  24. data/lib/takeoff/stage/heroku/scale_down_workers.rb +33 -0
  25. data/lib/takeoff/stage/heroku/verify_server_not_already_up_to_date.rb +17 -0
  26. data/lib/takeoff/stage/heroku/verify_staging_up_to_date.rb +28 -0
  27. data/lib/takeoff/stage/log.rb +28 -0
  28. data/lib/takeoff/stage/look_out_for_danger.rb +15 -0
  29. data/lib/takeoff/stage/point_checkpoint_to_development.rb +22 -0
  30. data/lib/takeoff/stage/push_to_github.rb +14 -0
  31. data/lib/takeoff/stage/stash_changes.rb +31 -0
  32. data/lib/takeoff/stage/verify_circle_ci_status.rb +46 -0
  33. data/lib/takeoff/stage/verify_github_up_to_date.rb +18 -0
  34. data/lib/takeoff/version.rb +3 -0
  35. data/lib/takeoff.rb +44 -0
  36. data/takeoff.gemspec +23 -0
  37. 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
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
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
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.expand_path("../../lib", __FILE__)
4
+ require "takeoff/cli"
5
+
6
+ Takeoff::CLI.start(ARGV)
@@ -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
@@ -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