├── .ruby-version ├── lib ├── cf-deploy.rb └── cf │ ├── deploy │ ├── version.rb │ ├── commands.rb │ ├── config.rb │ ├── blue_green.rb │ └── env_config.rb │ └── deploy.rb ├── .rspec ├── Gemfile ├── spec ├── manifests │ ├── test.yml │ ├── staging.yml │ ├── production_blue.yml │ ├── production_green.yml │ └── staging_with_runtime.yml ├── spec_helper.rb ├── env_config_spec.rb ├── stop_idle_task_spec.rb ├── login_task_spec.rb ├── rake_tasks_spec.rb ├── blue_green_task_spec.rb ├── flip_task_spec.rb └── deploy_task_spec.rb ├── Rakefile ├── .gitignore ├── .travis.yml ├── cf-deploy.gemspec ├── LICENSE └── README.md /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.2.1 2 | -------------------------------------------------------------------------------- /lib/cf-deploy.rb: -------------------------------------------------------------------------------- 1 | require 'cf/deploy' 2 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format documentation 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gemspec 3 | -------------------------------------------------------------------------------- /spec/manifests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - name: test-app 4 | -------------------------------------------------------------------------------- /spec/manifests/staging.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - name: staging-app 4 | -------------------------------------------------------------------------------- /lib/cf/deploy/version.rb: -------------------------------------------------------------------------------- 1 | module CF 2 | class Deploy 3 | VERSION = '0.1.8' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'codeclimate-test-reporter' 2 | CodeClimate::TestReporter.start 3 | 4 | -------------------------------------------------------------------------------- /spec/manifests/production_blue.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - name: production-blue-app 4 | - name: production-blue-background 5 | -------------------------------------------------------------------------------- /spec/manifests/production_green.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - name: production-green-app 4 | - name: production-green-background 5 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rspec/core/rake_task' 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | task :default => :spec 6 | -------------------------------------------------------------------------------- /spec/manifests/staging_with_runtime.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - name: staging-app 4 | runtime_memory: '256M' 5 | runtime_instances: 2 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | spec/tmp 16 | tmp 17 | .DS_Store 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - ruby-head 4 | - 2.2 5 | - 2.1 6 | - 1.9.3 7 | - jruby 8 | before_script: 9 | - export CODECLIMATE_REPO_TOKEN=593e1a9d737b016a64d59eeb26f3b934aaec8d958fdd0bf45abdc69d37ae915e 10 | notifications: 11 | slack: madetechteam:be3g1qE6so2p2UcqQiOGBRUs 12 | -------------------------------------------------------------------------------- /spec/env_config_spec.rb: -------------------------------------------------------------------------------- 1 | describe CF::Deploy::EnvConfig do 2 | let(:env_config) { described_class.new(:staging, 'assets:precompile', ['spec/manifests/staging_with_runtime.yml']) } 3 | 4 | context 'when reading application names' do 5 | subject { env_config[:deployments].first[:app_names] } 6 | it { is_expected.to include('staging-app') } 7 | end 8 | 9 | context 'when reading application level config' do 10 | subject { env_config[:deployments].first[:apps] } 11 | it { is_expected.to include(a_hash_including(name: 'staging-app', runtime_memory: '256M')) } 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /cf-deploy.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('../lib', __FILE__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require 'cf/deploy/version' 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = 'cf-deploy' 7 | spec.version = CF::Deploy::VERSION 8 | spec.authors = ['Luke Morton', 'Chris Blackburn'] 9 | spec.email = ['luke@madetech.co.uk', 'chris@madetech.co.uk'] 10 | spec.summary = %q{Rake tasks for deploying to CloudFoundry v6+} 11 | spec.homepage = 'https://github.com/madebymade/cf-deploy' 12 | spec.license = 'MIT' 13 | 14 | spec.files = Dir['{lib,spec}/**/*.rb'] + ['LICENSE', 'README.md'] 15 | spec.test_files = ['spec'] 16 | spec.require_paths = ['lib'] 17 | 18 | spec.add_dependency 'rake' 19 | 20 | spec.add_development_dependency 'bundler', '~> 1.5' 21 | spec.add_development_dependency 'rspec', '~> 3.0.0' 22 | spec.add_development_dependency 'simplecov', '~> 0.7.1' 23 | spec.add_development_dependency 'codeclimate-test-reporter' 24 | end 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Made Tech 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. 23 | -------------------------------------------------------------------------------- /spec/stop_idle_task_spec.rb: -------------------------------------------------------------------------------- 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 'Suspend idle app' do 11 | let :rake_tasks! do 12 | described_class.rake_tasks! do 13 | environment :production do 14 | route 'yourwebsite.com', flip: true 15 | end 16 | end 17 | end 18 | 19 | it 'should stop blue if green is currently mapped' do 20 | Dir.chdir('spec/') do 21 | rake_tasks! 22 | expect(Kernel).to receive(:system).with('cf login').ordered 23 | expect(Kernel).to receive(:system).with('cf stop production-blue-app').ordered 24 | expect(Kernel).to receive(:system).with('cf stop production-blue-background').ordered 25 | Rake::Task['cf:deploy:production:stop_idle'].invoke 26 | end 27 | end 28 | 29 | it 'should stop green if blue is currently mapped' do 30 | Dir.chdir('spec/') do 31 | rake_tasks! 32 | expect(Kernel).to receive(:system).with('cf login').ordered 33 | expect(IO).to receive(:popen).with("cf routes | grep 'yourwebsite.com'") { double(read: 'production-blue-app', close: nil) } 34 | expect(Kernel).to receive(:system).with('cf stop production-green-app').ordered 35 | expect(Kernel).to receive(:system).with('cf stop production-green-background').ordered 36 | Rake::Task['cf:deploy:production:stop_idle'].invoke 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/cf/deploy/commands.rb: -------------------------------------------------------------------------------- 1 | module CF 2 | class Deploy 3 | class 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 stop(app_name) 19 | Kernel.system("cf stop #{app_name}") 20 | end 21 | 22 | def scale_memory(app_name, memory) 23 | Kernel.system("cf scale #{app_name} -f -m #{memory}") 24 | end 25 | 26 | def scale_instances(app_name, instances) 27 | Kernel.system("cf scale #{app_name} -i #{instances}") 28 | end 29 | 30 | def map_route(route, app_name) 31 | Kernel.system(route_cmd(:map, route, app_name)) 32 | end 33 | 34 | def unmap_route(route, app_name) 35 | Kernel.system(route_cmd(:unmap, route, app_name)) 36 | end 37 | 38 | def live_color(host) 39 | io = IO.popen("cf routes | grep '#{host}'") 40 | matches = /(blue|green)/.match(io.read) 41 | io.close 42 | return if matches.nil? 43 | matches[1].strip 44 | end 45 | 46 | private 47 | 48 | def route_cmd(method, route, app_name) 49 | map_cmd = "cf #{method}-route #{app_name} #{route[:domain]}" 50 | map_cmd = "#{map_cmd} -n #{route[:hostname]}" unless route[:hostname].nil? 51 | map_cmd 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/cf/deploy.rb: -------------------------------------------------------------------------------- 1 | require 'cf/deploy/version' 2 | require 'cf/deploy/config' 3 | require 'cf/deploy/env_config' 4 | require 'cf/deploy/commands' 5 | require 'cf/deploy/blue_green' 6 | require 'rake' 7 | 8 | module CF 9 | class Deploy 10 | class << self 11 | def rake_tasks!(&block) 12 | new(config: Config.new(&block), commands: Commands.new).rake_tasks! 13 | end 14 | end 15 | 16 | attr_accessor :config_task, :config, :cf 17 | 18 | def initialize(config_task) 19 | @config_task = config_task 20 | @config = config_task[:config] 21 | @cf = config_task[:commands] 22 | end 23 | 24 | def rake_tasks! 25 | [define_login_task].concat(deploy_tasks) 26 | end 27 | 28 | def deploy_tasks 29 | config[:environments].map { |env| define_deploy_tasks(env) } 30 | end 31 | 32 | def define_login_task 33 | return Rake::Task['cf:login'] if Rake::Task.task_defined?('cf:login') 34 | 35 | task = Rake::Task.define_task('cf:login') { cf.login(config) } 36 | task.add_description('Login to cf command line') 37 | end 38 | 39 | def define_deploy_tasks(env) 40 | BlueGreen.new(env, config_task) if env[:deployments].size > 1 41 | 42 | env[:deployments].each do |deployment| 43 | define_deploy_task(env, deployment) 44 | end 45 | end 46 | 47 | def define_deploy_task(env, deployment) 48 | task = Rake::Task.define_task(deployment[:task_name] => env[:deps]) do 49 | unless cf.push(deployment[:manifest]) 50 | raise "Failed to deploy #{deployment}" 51 | end 52 | 53 | env[:routes].reject { |r| r[:flip] == true }.each do |route| 54 | deployment[:app_names].each do |app_name| 55 | cf.map_route(route, app_name) 56 | end 57 | end 58 | 59 | deployment[:apps].each do |app| 60 | unless env[:runtime_memory].nil? and app[:runtime_memory].nil? 61 | cf.scale_memory(app[:name], env[:runtime_memory] || app[:runtime_memory]) 62 | end 63 | 64 | unless env[:runtime_instances].nil? and app[:runtime_instances].nil? 65 | cf.scale_instances(app[:name], env[:runtime_instances] || app[:runtime_instances]) 66 | end 67 | end 68 | end 69 | 70 | task.add_description("Deploy #{deployment[:app_names].join(', ')}") 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /spec/login_task_spec.rb: -------------------------------------------------------------------------------- 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 73 | -------------------------------------------------------------------------------- /spec/rake_tasks_spec.rb: -------------------------------------------------------------------------------- 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_0 = Rake::Task.define_task('cf:login') 38 | expected_task_1 = Rake::Task.define_task('asset:precompile') 39 | expected_task_2 = Rake::Task.define_task(:clean) 40 | 41 | Dir.chdir('spec/') do 42 | described_class.rake_tasks! do 43 | environment staging: 'asset:precompile' 44 | environment test: ['asset:precompile', :clean] 45 | environment production: 'asset:precompile' do 46 | route 'app' 47 | end 48 | end 49 | end 50 | 51 | expect(Rake::Task['cf:deploy:staging'].prerequisite_tasks[1]).to be(expected_task_1) 52 | expect(Rake::Task['cf:deploy:test'].prerequisite_tasks[2]).to be(expected_task_2) 53 | expect(Rake::Task['cf:deploy:production'].prerequisite_tasks[1]).to be(expected_task_1) 54 | expect(Rake::Task['cf:deploy:production_blue'].prerequisite_tasks[1]).to be(expected_task_1) 55 | expect(Rake::Task['cf:deploy:production_green'].prerequisite_tasks[1]).to be(expected_task_1) 56 | expect(Rake::Task['cf:deploy:production:flip'].prerequisite_tasks[0]).to be(expected_task_0) 57 | expect(Rake::Task['cf:deploy:production:stop_idle'].prerequisite_tasks[0]).to be(expected_task_0) 58 | end 59 | 60 | it 'should have a configurable manifest glob options' do 61 | Dir.chdir('spec/') do 62 | described_class.rake_tasks! do 63 | manifest_glob 'manifests/staging.yml' 64 | end 65 | end 66 | 67 | expect(Rake::Task.tasks.count).to eq(2) 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/cf/deploy/config.rb: -------------------------------------------------------------------------------- 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 | env = manifest_env(manifest) 34 | envs[env] ||= [] 35 | envs[env] << manifest 36 | envs 37 | end 38 | end 39 | 40 | def manifest_env(manifest) 41 | if manifest =~ /_blue.yml$/ 42 | File.basename(manifest, '_blue.yml').to_sym 43 | elsif manifest =~ /_green.yml$/ 44 | File.basename(manifest, '_green.yml').to_sym 45 | else 46 | File.basename(manifest, '.yml').to_sym 47 | end 48 | end 49 | 50 | def environments(manifests_by_env) 51 | environments = [] 52 | 53 | environments_to_be_loaded.each do |(env, block)| 54 | if env.is_a?(Hash) 55 | name, deps = env.first 56 | deps = (['cf:login'] << deps).flatten 57 | else 58 | name = env 59 | deps = ['cf:login'] 60 | end 61 | 62 | manifests = manifests_by_env.delete(name) || [] 63 | environments << EnvConfig.new(name, deps, manifests, &block) 64 | end 65 | 66 | manifests_by_env.each do |(name, manifests)| 67 | environments << EnvConfig.new(name, ['cf:login'], manifests) 68 | end 69 | 70 | environments 71 | end 72 | 73 | # Config setter methods 74 | # 75 | def manifest_glob(glob) self[:manifest_glob] = glob end 76 | def api(api) self[:api] = api end 77 | def username(username) self[:username] = username end 78 | def password(password) self[:password] = password end 79 | def organisation(organisation) self[:organisation] = organisation end 80 | def space(space) self[:space] = space end 81 | def environment(env, &block) @environments_to_be_loaded << [env, block] end 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /lib/cf/deploy/blue_green.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | 3 | module CF 4 | class Deploy 5 | class BlueGreen 6 | attr_accessor :env, :config_task, :config, :cf 7 | 8 | def initialize(env, config_task) 9 | @env = env 10 | @config_task = config_task 11 | @config = config_task[:config] 12 | @cf = config_task[:commands] 13 | 14 | define_deployment_task 15 | define_flip_task 16 | define_stop_idle_task 17 | end 18 | 19 | private 20 | 21 | def define_deployment_task 22 | task = Rake::Task.define_task(env[:task_name] => env[:deps]) do 23 | task_name = EnvConfig.task_name("#{env[:name]}_#{idle_color(env)}") 24 | Rake::Task[task_name].invoke 25 | end 26 | 27 | task.add_description("Deploy #{env}") 28 | end 29 | 30 | def define_flip_task 31 | task = Rake::Task.define_task("#{env[:task_name]}:flip" => 'cf:login') do 32 | live_app_name = app_name_from_color(live_color(env)) 33 | idle_app_name = app_name_from_color(idle_color(env)) 34 | 35 | flip_routes(env).each do |route| 36 | cf.map_route(route, idle_app_name) 37 | cf.unmap_route(route, live_app_name) 38 | end 39 | end 40 | 41 | task.add_description('Flip routes to point at currently idle app') 42 | end 43 | 44 | def define_stop_idle_task 45 | task = Rake::Task.define_task("#{env[:task_name]}:stop_idle" => 'cf:login') do 46 | env.app_names_for_colour(idle_color(env)).each do |app_name| 47 | cf.stop(app_name) 48 | end 49 | end 50 | 51 | task.add_description('Stop currently idle app') 52 | end 53 | 54 | def app_name_from_color(colour) 55 | env.app_name_for_colour(colour) 56 | end 57 | 58 | def flip_routes(env) 59 | env[:routes].select { |r| r[:flip] == true } 60 | end 61 | 62 | def flip_routes_sorted_by_hostname(env) 63 | flip_routes(env).sort do |a, b| 64 | a[:hostname] && a[:hostname].length > 0 ? -1 : 1 65 | end 66 | end 67 | 68 | def match_flip_route_grep(env) 69 | if flip_routes(env).empty? 70 | raise 'Blue/green deploys require at least one flip_route' 71 | end 72 | 73 | flip_routes_sorted_by_hostname(env).first.values_at(:hostname, :domain).compact.join(' *') 74 | end 75 | 76 | def live_color(env) 77 | cf.live_color(match_flip_route_grep(env)) 78 | end 79 | 80 | def idle_color(env) 81 | if live_color(env) != 'blue' 82 | 'blue' 83 | else 84 | 'green' 85 | end 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /spec/blue_green_task_spec.rb: -------------------------------------------------------------------------------- 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 'yourwebsite.com', flip: true 16 | route 'yourwebsite.com', 'www', flip: true 17 | route 'yourwebsite.com', 'www-origin', flip: true 18 | end 19 | end 20 | end 21 | end 22 | 23 | it 'should exist if *_blue.yml and *_green.yml manifests exist' do 24 | rake_tasks! 25 | expect(Rake::Task['cf:deploy:production']).to be_a(Rake::Task) 26 | end 27 | 28 | it 'should deploy blue if not currently deployed' do 29 | rake_tasks! 30 | expect(Kernel).to receive(:system).with('cf login').ordered 31 | expect(IO).to receive(:popen).with(/cf routes \| grep '(www|www-origin) \*yourwebsite.com'/) { double(read: '', close: nil) } 32 | expect(Kernel).to receive(:system).with('cf push -f manifests/production_blue.yml').and_return(true).ordered 33 | Rake::Task['cf:deploy:production'].invoke 34 | end 35 | 36 | it 'should deploy blue if green currently deployed' do 37 | rake_tasks! 38 | expect(Kernel).to receive(:system).with('cf login').ordered 39 | expect(IO).to receive(:popen).with(/cf routes \| grep '(www|www-origin) \*yourwebsite.com'/) { double(read: 'production-green-app', close: nil) } 40 | expect(Kernel).to receive(:system).with('cf push -f manifests/production_blue.yml').and_return(true).ordered 41 | Rake::Task['cf:deploy:production'].invoke 42 | end 43 | 44 | it 'should deploy green if blue currently deployed' do 45 | rake_tasks! 46 | expect(Kernel).to receive(:system).with('cf login').ordered 47 | expect(IO).to receive(:popen).with(/cf routes \| grep '(www|www-origin) \*yourwebsite.com'/) { double(read: 'production-blue-app', close: nil) } 48 | expect(Kernel).to receive(:system).with('cf push -f manifests/production_green.yml').and_return(true).ordered 49 | Rake::Task['cf:deploy:production'].invoke 50 | end 51 | 52 | it 'should ignore flip_routes' do 53 | rake_tasks! 54 | expect(Kernel).to_not receive(:system).with('cf map-route production-blue-app yourwebsite.com -n www') 55 | expect(Kernel).to_not receive(:system).with('cf map-route production-blue-app yourwebsite.com -n www-origin') 56 | end 57 | 58 | it 'should throw exception if no routes defined for blue/green task' do 59 | Dir.chdir('spec/') do 60 | described_class.rake_tasks! do 61 | environment :production 62 | end 63 | end 64 | 65 | allow(Kernel).to receive(:system) 66 | expect { Rake::Task['cf:deploy:production'].invoke }.to raise_error 67 | end 68 | 69 | it 'should run prerequisite tasks' do 70 | rake_tasks! 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /spec/flip_task_spec.rb: -------------------------------------------------------------------------------- 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 'Flip production environments' do 11 | let :rake_tasks! do 12 | described_class.rake_tasks! do 13 | environment :production do 14 | route 'yourwebsite.com', flip: true 15 | route 'yourwebsite.com', 'www', flip: true 16 | route 'yourwebsite.com', 'www-origin', flip: true 17 | end 18 | end 19 | end 20 | 21 | it 'should map to blue if green is currently mapped' do 22 | Dir.chdir('spec/') do 23 | rake_tasks! 24 | expect(Kernel).to receive(:system).with('cf login').ordered 25 | 26 | expect(IO).to receive(:popen).with(/cf routes \| grep '(www|www-origin) \*yourwebsite.com'/) { double(read: 'production-green-app', close: nil) } 27 | expect(Kernel).to receive(:system).with('cf map-route production-blue-app yourwebsite.com').ordered 28 | expect(Kernel).to receive(:system).with('cf unmap-route production-green-app yourwebsite.com').ordered 29 | 30 | expect(IO).to receive(:popen).with(/cf routes \| grep '(www|www-origin) \*yourwebsite.com'/) { double(read: 'production-green-app', close: nil) } 31 | expect(Kernel).to receive(:system).with('cf map-route production-blue-app yourwebsite.com -n www').ordered 32 | expect(Kernel).to receive(:system).with('cf unmap-route production-green-app yourwebsite.com -n www').ordered 33 | 34 | expect(Kernel).to receive(:system).with('cf map-route production-blue-app yourwebsite.com -n www-origin').ordered 35 | expect(Kernel).to receive(:system).with('cf unmap-route production-green-app yourwebsite.com -n www-origin').ordered 36 | 37 | Rake::Task['cf:deploy:production:flip'].invoke 38 | end 39 | end 40 | 41 | it 'should map to green if blue is currently mapped' do 42 | Dir.chdir('spec/') do 43 | rake_tasks! 44 | expect(Kernel).to receive(:system).with('cf login').ordered 45 | 46 | expect(IO).to receive(:popen).with(/cf routes \| grep '(www|www-origin) \*yourwebsite.com'/) { double(read: 'production-blue-app', close: nil) } 47 | expect(Kernel).to receive(:system).with('cf map-route production-green-app yourwebsite.com').ordered 48 | expect(Kernel).to receive(:system).with('cf unmap-route production-blue-app yourwebsite.com').ordered 49 | 50 | expect(IO).to receive(:popen).with(/cf routes \| grep '(www|www-origin) \*yourwebsite.com'/) { double(read: 'production-blue-app', close: nil) } 51 | expect(Kernel).to receive(:system).with('cf map-route production-green-app yourwebsite.com -n www').ordered 52 | expect(Kernel).to receive(:system).with('cf unmap-route production-blue-app yourwebsite.com -n www').ordered 53 | 54 | expect(Kernel).to receive(:system).with('cf map-route production-green-app yourwebsite.com -n www-origin').ordered 55 | expect(Kernel).to receive(:system).with('cf unmap-route production-blue-app yourwebsite.com -n www-origin').ordered 56 | Rake::Task['cf:deploy:production:flip'].invoke 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/cf/deploy/env_config.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | require 'pathname' 3 | 4 | module CF 5 | class Deploy 6 | class EnvConfig < Hash 7 | def self.task_name(name) 8 | "cf:deploy:#{name}" 9 | end 10 | 11 | def initialize(name, deps, manifests, &block) 12 | merge!(name: name, 13 | task_name: EnvConfig.task_name(name), 14 | deps: deps, 15 | routes: [], 16 | runtime_memory: nil, 17 | runtime_instances: nil, 18 | manifests: manifests) 19 | 20 | instance_eval(&block) if block_given? 21 | 22 | raise "No manifests found for #{name}" if manifests.empty? 23 | 24 | self[:deployments] = deployments 25 | end 26 | 27 | def deployments 28 | self[:manifests].map { |manifest| deployment_for_manifest(manifest) } 29 | end 30 | 31 | def deployment_for_manifest(manifest) 32 | { task_name: deployment_task_name(manifest), 33 | manifest: manifest, 34 | app_names: app_names_for_manifest(manifest), 35 | apps: apps_for_manifest(manifest) } 36 | end 37 | 38 | def deployment_task_name(manifest) 39 | if self[:manifests].size > 1 40 | EnvConfig.task_name(File.basename(manifest, '.yml').to_sym) 41 | else 42 | self[:task_name] 43 | end 44 | end 45 | 46 | def apps_for_manifest(manifest) 47 | config = YAML.load_file(manifest) 48 | 49 | if config['applications'].nil? 50 | raise "No applications defined in YAML manifest #{manifest}" 51 | end 52 | 53 | config['applications'].map do |app| 54 | app.reduce({}) { |app, (k, v)| app.merge(k.to_sym => v) } 55 | end 56 | end 57 | 58 | def app_names_for_manifest(manifest) 59 | apps_for_manifest(manifest).map { |a| a[:name] } 60 | end 61 | 62 | def app_name_for_colour(colour) 63 | self[:manifests].map do |manifest| 64 | name = app_names_for_manifest(File.expand_path(manifest.to_s)).first 65 | return name if name.include?(colour) 66 | end 67 | end 68 | 69 | def app_names_for_colour(colour) 70 | self[:manifests].flat_map do |manifest| 71 | names = app_names_for_manifest(File.expand_path(manifest.to_s)) 72 | names if names.first.include?(colour) 73 | end.compact 74 | end 75 | 76 | # Environment config setter methods 77 | # 78 | def manifest(manifest) 79 | self[:manifests] << manifest 80 | end 81 | 82 | def manifests(manifests) 83 | self[:manifests].concat(manifests) 84 | end 85 | 86 | def runtime_memory(memory) 87 | self[:runtime_memory] = memory 88 | end 89 | 90 | def runtime_instances(instances) 91 | self[:runtime_instances] = instances 92 | end 93 | 94 | def route(domain, hostname_or_options = nil, options = nil) 95 | if options.nil? 96 | if hostname_or_options.nil? 97 | hostname = nil 98 | options = {} 99 | elsif hostname_or_options.is_a?(String) 100 | hostname = hostname_or_options 101 | options = {} 102 | else 103 | hostname = nil 104 | options = hostname_or_options 105 | end 106 | else 107 | hostname = hostname_or_options 108 | end 109 | 110 | self[:routes] << { domain: domain, hostname: hostname }.merge(options) 111 | end 112 | 113 | def flip_route(domain, hostname = nil) 114 | self[:routes] << { domain: domain, hostname: hostname, flip: true } 115 | end 116 | end 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /spec/deploy_task_spec.rb: -------------------------------------------------------------------------------- 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').and_return(true).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').and_return(true).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').and_return(true).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').and_return(true).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 | it 'should scale after deployment if runtime settings specified in manifest' do 69 | Dir.chdir('spec/') do 70 | described_class.rake_tasks! do 71 | environment :staging 72 | end 73 | end 74 | 75 | expect(Kernel).to receive(:system).with('cf login').ordered 76 | expect(Kernel).to receive(:system).with('cf push -f manifests/staging_with_runtime.yml').and_return(true).ordered 77 | expect(Kernel).to receive(:system).with('cf scale staging-app -f -m 256M').and_return(true).ordered 78 | expect(Kernel).to receive(:system).with('cf scale staging-app -i 2').and_return(true).ordered 79 | Rake::Task['cf:deploy:staging_with_runtime'].invoke 80 | end 81 | 82 | it 'should scale after deployment if runtime settings specified in cf:deploy config' do 83 | Dir.chdir('spec/') do 84 | described_class.rake_tasks! do 85 | environment :staging_with_runtime do 86 | runtime_memory '512M' 87 | runtime_instances 2 88 | end 89 | end 90 | end 91 | 92 | expect(Kernel).to receive(:system).with('cf login').ordered 93 | expect(Kernel).to receive(:system).with('cf push -f manifests/staging_with_runtime.yml').and_return(true).ordered 94 | expect(Kernel).to receive(:system).with('cf scale staging-app -f -m 512M').and_return(true).ordered 95 | expect(Kernel).to receive(:system).with('cf scale staging-app -i 2').and_return(true).ordered 96 | Rake::Task['cf:deploy:staging_with_runtime'].invoke 97 | end 98 | 99 | it 'should not map routes if push command fails' do 100 | Dir.chdir('spec/') do 101 | described_class.rake_tasks! do 102 | environment :test do 103 | route 'example.com' 104 | end 105 | end 106 | end 107 | 108 | expect(Kernel).to receive(:system).with('cf push -f manifests/test.yml').and_return(nil) 109 | expect(Kernel).to_not receive(:system).with('cf map-route test-app example.com') 110 | expect do 111 | Rake::Task['cf:deploy:test'].invoke 112 | end.to raise_error 113 | end 114 | 115 | it 'should not map routes if push command returns non-zero status' do 116 | Dir.chdir('spec/') do 117 | described_class.rake_tasks! do 118 | environment :test do 119 | route 'example.com' 120 | end 121 | end 122 | end 123 | 124 | expect(Kernel).to receive(:system).with('cf push -f manifests/test.yml').and_return(false) 125 | expect(Kernel).to_not receive(:system).with('cf map-route test-app example.com') 126 | expect do 127 | Rake::Task['cf:deploy:test'].invoke 128 | end.to raise_error 129 | end 130 | 131 | it 'should throw decent error if manifest does not exist' do 132 | expect do 133 | described_class.rake_tasks! do 134 | environment :undefined 135 | end 136 | end.to raise_error 137 | end 138 | 139 | it 'should throw decent error if manifest invalid' do 140 | expect do 141 | described_class.rake_tasks! do 142 | environment :invalid_manifest do 143 | manifest 'spec/spec_helper.rb' 144 | end 145 | end 146 | end.to raise_error 147 | end 148 | 149 | it 'should allow individual manifest to be specified' do 150 | Dir.chdir('spec/') do 151 | CF::Deploy.rake_tasks! do 152 | environment :custom_manifest do 153 | manifest 'manifests/staging.yml' 154 | end 155 | end 156 | end 157 | 158 | expect(Kernel).to receive(:system).with('cf login').ordered 159 | expect(Kernel).to receive(:system).with('cf push -f manifests/staging.yml').and_return(true).ordered 160 | Rake::Task['cf:deploy:custom_manifest'].invoke 161 | end 162 | end 163 | end 164 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CloudFoundry Rails Deployment 2 | 3 | [![Code Climate](https://codeclimate.com/github/madetech/cf-deploy/badges/gpa.svg)](https://codeclimate.com/github/madetech/cf-deploy) 4 | [![Build Status](https://travis-ci.org/madetech/cf-deploy.svg?branch=master)](https://travis-ci.org/madetech/cf-deploy) 5 | [![Test Coverage](https://codeclimate.com/github/madetech/cf-deploy/badges/coverage.svg)](https://codeclimate.com/github/madetech/cf-deploy/coverage) 6 | 7 | `cf-deploy` is the tool you use to deploy your rails app to 8 | [CloudFoundry][CloudFoundry] providers like [Pivotal][Pivotal]. It works with 9 | rails 4.2 and older versions as far back as rails 3. 10 | 11 | ``` 12 | rake cf:deploy:production 13 | ``` 14 | 15 | ## `cf-deploy` makes it easy to: 16 | 17 | * Deploy your rails app with one rake command 18 | * Implement blue/green deployments 19 | * Run asset precompiles before deploying your app 20 | * Automate your rails deploys using jenkins, circle-ci, codeship 21 | 22 | ## Getting Started 23 | 24 | The functionality comes in the shape of generated rake tasks. You require this 25 | gem in your `Rakefile` and call the `.rake_tasks!` setup method. 26 | 27 | ``` ruby 28 | require 'cf-deploy' 29 | CF::Deploy.rake_tasks! 30 | ``` 31 | 32 | By default tasks will be created for each manifest in your `manifests/` folder. 33 | If you have a `staging.yml` and `production.yml` you can now run the following 34 | commands: 35 | 36 | ``` sh 37 | bundle exec rake cf:deploy:staging 38 | bundle exec rake cf:deploy:production 39 | ``` 40 | 41 | You now have rake tasks that run `cf push -f manifests/staging.yml` and 42 | `cf push -f manifests/production.yml`. Things start to get more exciting 43 | when you define your environments in your `Rakefile` along with their task 44 | dependencies just like normal rake task syntax. 45 | 46 | ``` ruby 47 | require 'cf-deploy' 48 | 49 | CF::Deploy.rake_tasks! do 50 | environment staging: 'assets:precompile' 51 | environment production: [:clean, 'assets:precompile'] 52 | end 53 | ``` 54 | 55 | Now when running `cf:deploy:staging` and `cf:deploy:production` the prerequisite 56 | tasks will be run first. 57 | 58 | The next thing to talk about is route mapping. You can define a route in a an 59 | environment block like so: 60 | 61 | ``` ruby 62 | require 'cf-deploy' 63 | 64 | CF::Deploy.rake_tasks! do 65 | environment staging: 'assets:precompile' do 66 | route 'example.com', 'staging' 67 | end 68 | 69 | environment production: [:clean, 'assets:precompile'] do 70 | route 'example.com' 71 | route 'example.com', 'www' 72 | route 'example.com', 'www-origin' 73 | route 'example.com', 'admin' 74 | end 75 | end 76 | ``` 77 | 78 | As soon as an environment with routes is pushed successfully each of it's routes 79 | will be mapped to all the applications defined in the environment's manifest. 80 | 81 | And then things get super interesting when you start talking blue/green. 82 | 83 | ## What is blue/green deployment? 84 | 85 | Simply put, blue/green deployment allows you to deploy a new version of your 86 | app, test it on a private URL and then direct your traffic to the new version 87 | when you are ready. 88 | 89 | You have two applications for one environment, say production. One version is 90 | called green, the other is blue. The first time you deploy your environment 91 | either green or blue can be deployed. Thereafter, any changes you want to deploy 92 | you deploy to the color that doesn't have your production domain pointed at it. 93 | You test it on a private URL and then when you're happy you flip your domain to 94 | point at that. If something then goes wrong you can then flip your domain back 95 | to the last working version. 96 | 97 | This gem provides rake tasks for you to deploy using this methodology as well 98 | as the standard single app deployment process on a CloudFoundry provider. 99 | 100 | ### An example of blue/green 101 | 102 | Examples always help and this example is probably the most common use case. You 103 | might have a straight forward deployment for staging but use the blue/green 104 | strategy for production. Here is what your Rakefile might look like: 105 | 106 | ``` ruby 107 | require 'cf-deploy' 108 | 109 | CF::Deploy.rake_tasks! do 110 | environment staging: 'assets:precompile' 111 | 112 | environment production: 'assets:precompile' do 113 | route 'example-app.io', flip: true 114 | route 'example-app.io', 'www', flip: true 115 | route 'example-app.io', 'www-origin', flip: true 116 | 117 | route 'example-app.io', 'blue', blue: true 118 | route 'example-app.io', 'green', green: true 119 | end 120 | end 121 | ``` 122 | 123 | You should also have three manifests defined: 124 | 125 | - `manifests/staging.yml` 126 | - `manifests/production_blue.yml` 127 | - `manifests/production_green.yml` 128 | 129 | When you run `cf:deploy:production` for the first time (assuming neither 130 | `production_blue.yml` or `production_green.yml` are deployed) your blue app will 131 | be deployed and route setup. 132 | 133 | Running `cf:deploy:production` thereafter will deploy which ever version isn't 134 | currently deployed. Your route(s) will not be mapped automatically this time. 135 | Nows your chance to checkout your new deployment using an alternate route. When 136 | you're happy and want to map your route across run: 137 | 138 | ``` sh 139 | bundle exec rake cf:deploy:production:flip 140 | ``` 141 | 142 | ## Installation 143 | 144 | You need the `cf` command installed already. Grab the latest release from 145 | the [CloudFoundry CLI][cli] repo on github. 146 | 147 | You then need to install this gem in your project's `Gemfile`: 148 | 149 | ``` ruby 150 | gem 'cf-deploy', '0.1.4' 151 | ``` 152 | 153 | ### Defining CloudFoundry details in your Rakefile 154 | 155 | You can configure some or all of your CloudFoundry details when calling 156 | `CF::Deploy.rake_tasks!`. 157 | 158 | ``` ruby 159 | require 'cf-deploy' 160 | 161 | CF::Deploy.rake_tasks! do 162 | api 'api.run.pivotal.io' 163 | username 'example@example.com' 164 | password 'SOMETHING' 165 | organisation 'Made' 166 | space 'development' 167 | 168 | environment staging: 'assets:precompile' 169 | 170 | environment production: 'assets:precompile' do 171 | route 'yourwebsite.com', 'www', flip: true 172 | end 173 | end 174 | ``` 175 | 176 | All are optional. If you do not provide any you will be prompted when running 177 | the rake tasks. 178 | 179 | ### Defining CloudFoundry details using ENV variables 180 | 181 | Instead of defining your CloudFoundry login details in your Rakefile and 182 | committing them to your code repository you can instead provide them using 183 | ENV variables on your command line: 184 | 185 | ``` sh 186 | export CF_API=api.run.pivotal.io 187 | export CF_USERNAME=example@example.com 188 | export CF_PASSWORD=SOMETHING 189 | export CF_ORG=Made 190 | export CF_SPACE=development 191 | ``` 192 | 193 | Now you can run any of the `cf-deploy` rake tasks providing you have called 194 | `CF::Deploy.rake_tasks!` in your `Rakefile`. 195 | 196 | ## Commands 197 | 198 | ### Deploying an environment 199 | 200 | If you defined a staging environment in your Rakefile the following task will 201 | have been created: 202 | 203 | ``` 204 | bundle exec rake cf:deploy:staging 205 | ``` 206 | 207 | Run this to deploy out your staging environment. 208 | 209 | Any environment you define will have a task created named `cf:deploy:#{env}`. 210 | 211 | ### Deploy the next blue/green environment 212 | 213 | If you have defined CloudFoundry manifest files matching `manifests/*_blue.yml` 214 | and `manifests/*_green.yml` you will be able to call `rake cf:deploy:*` without 215 | the `_blue` or `_green`. For example with `production_blue.yml` and 216 | `production_green.yml` you can call the following: 217 | 218 | ``` 219 | bundle exec rake cf:deploy:production 220 | ``` 221 | 222 | Running the deploy task for an env with blue and green manifests will trigger a 223 | lookup to see which env is currently deployed. The task will then start 224 | deploying the other production color, so if green is currently deployed then 225 | blue will be deployed. If neither is currently deployed, blue will be deployed 226 | first. 227 | 228 | Once deployed your routing will still be pointing to the *previous deployment*. 229 | If you run the same task again, the same environment will be deployed. That is 230 | if green was deployed, and then you run the task, blue will be deployed, if you 231 | run the task again, blue will be deployed again. This is because we work out 232 | the current deployment based on where your routes are pointing and since the 233 | deploy command for blue green environments doesn't map routes the current 234 | deployment will not change. 235 | 236 | #### First time proviso 237 | 238 | This isn't the case for a first time deploy. The first time you deploy your 239 | blue environment will be deployed and any defined routes will be mapped to all 240 | apps defined in your blue manifest. 241 | 242 | ### Switch routes over to new environment 243 | 244 | In order to flip your routes from blue to green or vice-versa you need to run 245 | the following task. 246 | 247 | ``` 248 | bundle exec rake cf:deploy:production:flip 249 | ``` 250 | 251 | This will go ahead and map routes to whatever color the routes aren't mapped to 252 | and then unmap the other color. At this point your new production will be 253 | deployed and live. 254 | 255 | ### Turn off idle app 256 | 257 | Once your new production has been flipped you may want to turn off your idle 258 | application. There is a task for this too: 259 | 260 | ``` 261 | bundle exec rake cf:deploy:production:stop_idle 262 | ``` 263 | 264 | ## Credits 265 | 266 | [![made](https://s3-eu-west-1.amazonaws.com/made-assets/googleapps/google-apps.png)][made] 267 | 268 | Developed and maintained by [Made Tech][made]. Key contributions: 269 | 270 | * [Luke Morton](https://github.com/DrPheltRight) 271 | * [Chris Blackburn](https://github.com/chrisblackburn) 272 | 273 | ## License 274 | 275 | Copyright © 2014 Made Tech Ltd. It is free software, and may be 276 | redistributed under the terms specified in the [MIT-LICENSE][license] file. 277 | 278 | [CloudFoundry]: http://www.cloudfoundry.org/ 279 | [Pivotal]: https://run.pivotal.io/ 280 | [cli]: https://github.com/cloudfoundry/cli/releases 281 | [made]: http://www.madetech.co.uk?ref=github&repo=cf-deploy 282 | [license]: https://github.com/madebymade/cf-deploy/blob/master/LICENSE 283 | --------------------------------------------------------------------------------